2、基础概念:事务(Transaction)、锁(Lock)、隔离级别(Isolation Level)在审批场景中的映射

在深入多人审批并发冲突之前,必须将数据库领域的三大基础概念——事务、锁、隔离级别——映射到审批业务场景中。这些概念并非纯技术术语,而是描述“谁在什么时候看到什么数据、能否修改、修改后如何生效”的规则集合。

2.1 事务:审批操作的原子边界

事务(Transaction)是一组不可分割的操作单元。在审批流中,一个事务通常对应一次“审批动作”的完整生命周期。

事务特性(ACID) 审批场景映射 违反后果示例
原子性(Atomicity) 审批人点击“通过”后,状态更新、日志写入、通知发送要么全部成功,要么全部回滚 状态已改为“已通过”,但日志未记录,后续审计无法追溯
一致性(Consistency) 审批完成后,单据状态、审批意见、下一节点责任人必须符合业务规则 多人同时审批导致状态变为“已拒绝”但仍有后续节点在流转
隔离性(Isolation) 一个审批人的操作在提交前,不应被其他审批人看到中间状态 A审批中看到B已通过,但B的事务实际已回滚
持久性(Durability) 审批结果一旦提交,即使系统崩溃,重启后结果不丢失 审批通过后宕机,恢复后单据回到待审批状态
关键认知: 在多人审批场景中,事务的隔离性是最常被突破的防线。默认的数据库事务隔离级别(如 Read Committed)在并发审批时会产生“不可重复读”和“幻读”,直接导致审批结果错乱。

2.2 锁:控制并发访问的机制

锁(Lock)是数据库和业务系统用来防止多个事务同时修改同一数据的机制。在审批流中,锁的粒度直接影响并发能力和数据正确性。

2.2.1 锁的类型与审批映射

锁类型 数据库实现 审批场景映射 适用条件
共享锁(S锁) SELECT ... LOCK IN SHARE MODE 审批人读取单据信息,允许其他人同时读取,但不允许修改 仅查看审批详情,不执行审批操作
排他锁(X锁) SELECT ... FOR UPDATE 审批人点击“通过/拒绝”时,锁定整条单据记录,阻止其他审批人同时操作 执行审批动作,需要保证唯一决策
行锁 InnoDB 行级锁 只锁定当前审批单据行,其他单据的审批不受影响 高并发场景,不同单据可并行审批
表锁 LOCK TABLES 锁定整个审批记录表,所有审批操作串行化 极低并发或维护期,不推荐生产使用
乐观锁 版本号(version)或时间戳 审批提交时检查版本号是否变化,若变化则重试或提示冲突 读多写少、冲突概率低的审批场景
悲观锁 SELECT ... FOR UPDATE 审批人打开审批页面时即锁定记录,直到提交或取消 冲突概率高、业务不允许重试的场景
常见陷阱: 许多系统在“会签”场景(多人必须全部通过)中错误地使用乐观锁。当最后一人提交时,版本号可能已被前一人修改,导致提交失败。此时应使用悲观锁或分布式锁来保证决策的原子性。

2.2.2 锁的粒度选择策略

  1. 按单据粒度: 锁定整张审批单据(推荐)。适用于大多数审批流,锁开销适中,冲突范围可控。
  2. 按节点粒度: 锁定当前审批节点记录。适用于复杂多路分支审批,但需要额外维护节点锁状态。
  3. 按字段粒度: 仅锁定“审批状态”字段。极少使用,因为业务上通常需要保证整条记录的一致性。

2.3 隔离级别:事务间的可见性规则

隔离级别定义了事务之间数据可见的程度。在审批场景中,不同的隔离级别会导致截然不同的并发行为。

隔离级别 脏读 不可重复读 幻读 审批场景表现
Read Uncommitted 可能 可能 可能 审批人A能看到B未提交的“已通过”状态,B回滚后A看到错误数据
Read Committed 避免 可能 可能 A读取单据时状态为“待审批”,B提交后A再次读取变为“已通过”,导致A基于过期数据做决策
Repeatable Read 避免 避免 可能(InnoDB通过间隙锁避免) A在事务内多次读取同一单据,结果始终一致;但可能看到新增的审批记录(幻读)
Serializable 避免 避免 避免 所有审批操作串行执行,无并发冲突,但吞吐量极低
推荐配置: 审批流核心操作(状态变更、决策提交)应使用 Repeatable Read 隔离级别,配合行级排他锁(SELECT ... FOR UPDATE)。查询操作(查看审批列表、历史记录)可使用 Read Committed 提升并发性能。

2.4 概念映射关系图

下图展示了事务、锁、隔离级别在多人审批场景中的协同关系:

事务 · 锁 · 隔离级别在审批场景中的映射关系 事务(Transaction) • 原子性:审批动作全成功或全回滚 • 一致性:状态+日志+通知一致 • 隔离性:中间状态对其他事务不可见 • 持久性:审批结果不因崩溃丢失 → 映射:一次“审批动作”= 一个事务 锁(Lock) • 排他锁:阻止其他审批人同时操作 • 共享锁:允许并发读取,禁止修改 • 行锁:只锁定当前单据 • 乐观锁:版本号检测冲突 → 映射:控制“谁可以修改当前单据” 隔离级别 • Read Uncommitted:有脏读风险 • Read Committed:无脏读,有不可重复读 • Repeatable Read:事务内读一致 • Serializable:完全串行化 → 映射:定义“事务间数据可见规则” 保证 实现 多人审批并发场景 • 事务保证“一次审批”的原子性和一致性 • 锁控制“谁可以修改”,防止同时决策 • 隔离级别定义“审批人看到的数据版本” 核心结论: 事务提供边界,锁提供互斥,隔离级别提供可见性规则。三者协同才能保证多人审批的正确性。

2.5 实际代码示例:事务+锁的审批提交

以下伪代码展示了在 Repeatable Read 隔离级别下,使用排他锁实现安全的审批提交:

-- 开启事务
START TRANSACTION;

-- 设置隔离级别为 Repeatable Read
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;

-- 使用排他锁锁定当前审批单据
SELECT * FROM approval_record
WHERE id = 1001
FOR UPDATE;

-- 检查当前状态是否允许审批
-- 若状态已变更(被其他事务修改),则回滚
IF (status != 'PENDING') THEN
    ROLLBACK;
    RETURN '审批状态已变更,请刷新后重试';
END IF;

-- 执行审批操作:更新状态、写入日志
UPDATE approval_record
SET status = 'APPROVED', approver = 'user_A', approved_at = NOW()
WHERE id = 1001;

INSERT INTO approval_log (record_id, action, operator, created_at)
VALUES (1001, 'APPROVE', 'user_A', NOW());

-- 提交事务
COMMIT;
关键点: SELECT ... FOR UPDATE 会在读取时立即加排他锁,其他事务的 FOR UPDATE 或 UPDATE 操作会等待当前事务提交或回滚。这保证了在多人同时点击“通过”时,只有一个事务能成功修改状态。

2.6 概念对比总结

维度 事务 锁 隔离级别
核心问题 操作是否完整执行 谁可以修改数据 事务间数据可见性
审批映射 一次审批动作的成败 防止多人同时修改同一单据 审批人看到的数据版本
并发影响 事务越长,锁持有越久 锁粒度越细,并发越高 级别越高,并发越低
常见错误 事务内包含无关操作 未加锁导致覆盖更新 使用 Read Committed 导致不可重复读
推荐实践 审批动作事务尽量短小 行级排他锁 + 乐观锁兜底 核心操作使用 Repeatable Read