Delphi:多客户端环境下如何实现乐观锁?
针对Delphi FireDAC + Postgres遗留ERP的并发控制解决方案
现有方案的优化思路
1. 互斥锁(MUTEX)的优化
你当前的数据库互斥锁扩展性差,但可以做针对性优化:
- 缩小锁粒度:不要给整个方法加锁,只锁定关键数据行,比如操作库存时仅锁定对应物料ID的行,而非全表或整个模块。Postgres可通过
SELECT ... FOR UPDATE SKIP LOCKED实现行级锁,避免无意义等待。 - 动态调整重试策略:根据并发情况调整重试间隔和次数,比如初始间隔100ms,重试2次后间隔翻倍,避免并发风暴。
2. 版本控制(乐观锁)的轻量化改造
全量改造应用成本高,但可分步推进:
- 先给高并发核心表(如物料库存、采购单)添加
version字段,用触发器自动维护版本号,无需修改所有更新逻辑,仅在关键操作后检查受影响行数。 - 利用FireDAC的
TFDCommand.RowsAffected属性快速判断更新是否生效,封装通用检查函数减少重复代码。
其他可行方案
1. Postgres行级锁结合FireDAC事务配置
FireDAC原生支持通过事务隔离级别和锁语句实现并发控制,具体配置方式:
- 设置事务隔离级别:将
TFDConnection.Transaction.IsolationLevel设为ilReadCommitted(Postgres默认)或ilRepeatableRead,避免脏读和不可重复读。 - 显式行级锁:查询数据时直接加锁,示例SQL:
执行该查询后,其他会话操作该行会被阻塞,直到当前事务提交/回滚。若不想阻塞,可改用SELECT * FROM material_stock WHERE id = :MatID FOR UPDATE;FOR UPDATE SKIP LOCKED跳过已锁定行,适合批量操作场景。 - FireDAC事务配置:开启
TFDTransaction.AutoCommit为False,操作完成后手动Commit,异常时Rollback,确保锁的范围在事务内。
2. 利用Postgres的SERIALIZABLE隔离级别
这是Postgres最强隔离级别,会自动检测并发冲突并回滚冲突事务,应用层只需捕获40001错误码(序列化失败)并重试即可。该方案无需修改表结构,只需:
- 在FireDAC中设置事务隔离级别为
ilSerializable - 封装通用事务重试逻辑,示例Delphi代码:
该方案对应用侵入性极低,适合遗留系统快速改造,但需注意高并发下重试次数过多的问题,可根据业务调整重试阈值。function ExecuteWithRetry(ADACall: TFunc<Boolean>): Boolean; var RetryCount: Integer; begin RetryCount := 0; Result := False; while RetryCount < 3 do begin try FDConnection.StartTransaction; Result := ADACall(); FDConnection.Commit; Break; except on E: EFDDBEngineException do begin FDConnection.Rollback; // 捕获Postgres序列化失败错误码 if E.ErrorCode = 40001 then begin Inc(RetryCount); Sleep(100 * RetryCount); end else Raise; end; end; end; end;
3. 基于Postgres Advisory Locks(咨询锁)
咨询锁是数据库级别的自定义锁,可针对业务逻辑加锁,比如根据物料ID生成锁键:
-- 获取锁,超时5秒 SELECT pg_advisory_lock(:MatID); -- 释放锁 SELECT pg_advisory_unlock(:MatID);
FireDAC可直接执行这些SQL,相比表级锁更灵活,锁粒度可自定义,且不会阻塞无关操作,适合采购单审批等特定业务场景。
方案选型建议
- 若追求最小侵入性:优先选
SERIALIZABLE隔离级别+重试逻辑,无需改表,仅需封装事务重试。 - 若核心表并发极高:采用乐观锁(version字段)+行级锁结合,分步改造核心模块。
- 若特定业务逻辑需自定义锁:使用Postgres咨询锁+FireDAC事务控制。
内容的提问来源于stack exchange,提问作者Andrej
相关产品推荐
相关产品推荐

