ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

ERP系统并发控制与锁机制

ERP系统并发控制与锁机制 多用户同时操作ERP如果不做并发控制数据会乱。最常见的场景两个人同时编辑同一张订单后保存的覆盖先保存的库存扣减时两个出库单同时读取库存都认为够扣结果超扣。一、常见并发问题1. 丢失更新用户A和用户B同时打开订单001各改了不同字段。A先保存B后保存B的保存覆盖了A的修改。2. 脏读用户A正在编辑订单还没保存用户B查询到了A正在编辑的中间状态基于这个中间状态做了决策。3. 不可重复读用户A查询库存结果100过了一分钟再查变成80了因为用户B在这期间出了货。4. 幻读用户A查询某个价格区间的产品列表查到10个这时用户B新增了一个产品也在这个区间A再次查询变成11个。二、锁机制1. 乐观锁假设冲突概率低不加锁。保存时检查数据是否被修改过。-- 订单表增加版本号 ALTER TABLE sa_order ADD COLUMN version INT DEFAULT 0; -- 保存时检查版本 UPDATE sa_order SET customer_name ?, total_amount ?, version version 1 WHERE id ? AND version ?; -- 影响行数为0说明被别人改过了应用层处理public bool UpdateOrder(SalesOrder order) { int affected _db.Execute( UPDATE sa_order SET customer_name Name, total_amount Amount, version version 1 WHERE id Id AND version Version, new { order.Name, order.Amount, order.Id, order.Version } ); if (affected 0) throw new ConcurrencyException(订单已被其他人修改请刷新后重试); order.Version; return true; }乐观锁适合读多写少的场景冲突时提示用户让用户决定怎么处理。2. 悲观锁假设冲突概率高先锁住数据改完再释放。-- 查询时加排他锁 SELECT * FROM sa_order WHERE id ? FOR UPDATE; -- 修改 UPDATE sa_order SET customer_name ? WHERE id ?; -- 提交事务释放锁 COMMIT;悲观锁适合写多的场景。但锁等待时间长会影响性能。3. 什么时候用哪种场景锁类型原因订单编辑乐观锁冲突概率低库存扣减悲观锁必须准确凭证录入乐观锁冲突概率低序列号分配悲观锁不能重复报表查询不加锁只读三、库存并发控制库存是最容易出并发问题的地方。方案一数据库行锁-- 出库时先锁库存行 SELECT quantity FROM inv_stock WHERE warehouse_id ? AND material_id ? FOR UPDATE; -- 检查库存是否足够 -- 如果足够扣减 UPDATE inv_stock SET quantity quantity - ? WHERE warehouse_id ? AND material_id ?;这种方式简单可靠但并发量大了会有锁等待。方案二乐观锁 重试-- 不加锁直接更新但带条件 UPDATE inv_stock SET quantity quantity - ? WHERE warehouse_id ? AND material_id ? AND quantity ?; -- 影响行数为0要么库存不足要么被别人改了 -- 重试几次方案三预扣 确认-- 预扣库存下单时 UPDATE inv_stock SET available_quantity available_quantity - ?, locked_quantity locked_quantity ? WHERE warehouse_id ? AND material_id ? AND available_quantity ?; -- 确认扣减出库时 UPDATE inv_stock SET quantity quantity - ?, locked_quantity locked_quantity - ? WHERE warehouse_id ? AND material_id ?;available_quantity可用量 现存量 - 锁定量。下单时预扣出库时确认。如果订单取消释放预扣。四、单据编号并发单据编号不能重复。高并发下自增序列可能有间隙。方案一数据库序列CREATE SEQUENCE seq_order_no START WITH 1 INCREMENT BY 1; -- 取下一个编号 SELECT NEXT VALUE FOR seq_order_no;简单但有间隙事务回滚后编号跳过。方案二编号池-- 预分配编号段 INSERT INTO sys_number_pool (prefix, current_no, max_no) VALUES (SO, 1, 1000); -- 取编号 UPDATE sys_number_pool SET current_no current_no 1 WHERE prefix SO AND current_no max_no; SELECT current_no FROM sys_number_pool WHERE prefix SO;方案三日期 序号-- 编号格式SO20260513-001 SELECT CONCAT(SO, DATE_FORMAT(NOW(), %Y%m%d), -, LPAD(COALESCE(MAX(CAST(SUBSTRING(order_no, 12, 3) AS UNSIGNED)), 0) 1, 3, 0)) FROM sa_order WHERE order_date CURDATE();这种方式在并发下可能重复需要加锁或唯一约束兜底。五、分布式场景如果ERP部署在多台服务器上数据库锁不够用。分布式锁public class DistributedLock { private readonly RedisClient _redis; public bool TryLock(string key, TimeSpan expiry) { return _redis.Set(key, locked, expiry, onlyIfNotExists: true); } public void Unlock(string key) { _redis.Del(key); } } // 使用 if (_lock.TryLock($order:{orderId}, TimeSpan.FromSeconds(30))) { try { // 处理订单 } finally { _lock.Unlock($order:{orderId}); } } else { throw new BusyException(系统繁忙请稍后重试); }幂等性接口重复调用结果一样。防止网络超时导致的重复提交。[HttpPost] [Route(api/order/save)] public IActionResult SaveOrder([FromBody] OrderDto order, [FromHeader] string idempotencyKey) { // 检查是否已处理 var existing _idempotencyService.Get(idempotencyKey); if (existing ! null) return Ok(existing); // 返回之前的结果 // 处理订单 var result _orderService.Save(order); // 记录幂等键 _idempotencyService.Set(idempotencyKey, result, TimeSpan.FromHours(24)); return Ok(result); }六、总结并发控制没有银弹。根据场景选择合适的方案冲突概率低用乐观锁数据必须准确用悲观锁高并发库存用预扣确认分布式环境用分布式锁防重复提交用幂等键核心原则宁可慢一点也不能错。成都云策数链科技有限公司 | 用友四川授权服务中心 | 专注企业数字化转型
返回列表