事务隔离控制并发读写的可见性

从快照、并发异常和重试理解隔离级别,说明数据库隔离不能替业务不变量自动成立。

事务隔离控制并发读写的可见性

事务把一组数据库操作组织为提交或回滚的工作单元,但并发事务仍可能互相影响。隔离级别规定一个事务何时能看到其他事务的修改,以及数据库允许哪些并发结果。更高隔离通常减少异常,却可能增加等待、冲突和重试,不能脱离业务不变量机械选择。

Read Committed 为每条语句取得视图

PostgreSQL 默认的 Read Committed 让一条语句看到它开始前已经提交的数据。同一事务中的后一条查询可能看到在此期间由其他事务提交的新结果,因此两次相同查询不一定返回同一集合。

更新语句遇到并发修改时可能等待,并在对方提交后重新检查条件。业务代码若先查询再根据旧结果更新,多条语句之间仍可能被其他事务插入或修改。

该级别适合许多短事务,但“每条语句都读取已提交数据”不等于整个业务流程具有一致快照。需要跨多次读取保持一致时,应明确更高隔离或采用原子 SQL。

Repeatable Read 保持事务快照

Repeatable Read 让事务中的查询基于稳定快照,因此不会在后续查询中看到其他事务新提交的普通行变化。它便于生成一致报表或执行依赖多次读取的计算。

稳定快照并不意味着事务永远可以提交。并发更新同一数据时,数据库可能中止其中一个事务。应用必须把可重试错误当作正常并发结果,而不是未知系统故障。

不同数据库对同名隔离级别的实现可能不同。设计和测试要以实际数据库及版本文档为准,不能只依据 SQL 级别名称推断全部异常。

Serializable 要求结果等价于串行执行

Serializable 试图保证成功提交的并发事务结果等价于某种逐个执行顺序。数据库可以检测会破坏这种性质的读写依赖,并回滚一个事务以避免异常结果。

因此 Serializable 不是“所有事务排队执行”。正常情况下仍可并发,只是在危险组合出现时产生序列化失败。应用若不重试,用户会看到失败;若无限立即重试,又可能在高冲突下放大负载。

重试应覆盖整个事务逻辑,并设置次数、退避和幂等边界。只重试最后一条语句会沿用已经失效的读取决策。

数据库隔离不会自动表达业务不变量

“账户余额不能为负”“同一时段只能有一个预约”等规则需要约束、锁定或可序列化事务中的正确读写模式。仅提高隔离级别,若事务根本没有读取或写入相关约束数据,也无法让数据库知道规则。

唯一约束、外键和检查约束能在数据库层直接维护一部分不变量,通常比应用先查后写更可靠。跨多行或跨系统规则则需要明确协调策略。

事务只覆盖同一数据库中的受控操作。发送消息、调用支付接口或写对象存储不会因数据库回滚自动撤销,需要发件箱、补偿或幂等协议处理。

显式锁适用于明确竞争对象

行锁可以让事务在更新前锁定已存在记录,避免并发修改。锁定顺序不一致会产生死锁,数据库通常回滚一个事务解除循环,应用仍要处理重试。

若竞争对象尚不存在,锁一行无法阻止另一事务插入。可以依赖唯一约束、锁定稳定父记录或重新设计不变量表达。表级锁范围更大,应谨慎使用。

长事务持有快照和锁,会阻碍清理、增加冲突并耗尽连接。事务边界应围绕必须原子完成的数据库工作,不应包含用户等待和慢网络调用。

读取副本改变一致性边界

主库提交后,副本可能稍后才重放变更。应用若写主库后立即读副本,可能看不到自己的修改。数据库事务隔离无法跨异步复制自动提供读己之写。

路由策略可以在一段时间内读主库、携带提交位置等待副本,或接受明确的陈旧窗口。选择取决于用户体验和延迟成本。

跨服务各自拥有数据库时,本地 Serializable 也不能形成全局串行化。服务间一致性需要消息、工作流或分布式协议,并定义中间状态。

选择隔离级别要从异常出发

先写出并发场景:哪些事务同时运行,读取什么,写入什么,什么结果不可接受。随后用约束、原子语句、锁或隔离级别建立保证,并用并发测试验证。

监控应记录锁等待、死锁、序列化失败、事务时长和重试结果。冲突突然增加可能来自流量,也可能来自新的访问顺序或索引缺失。

事务隔离控制的是并发可见性,不是通用业务正确性开关。可靠设计把数据库保证、业务不变量和应用重试一起说明,才能判断在竞争发生时系统会得到什么结果。

资料依据