一个数据库说自己支持 Read Committed、Repeatable Read、Serializable,很多人会下意识把它当成一份清楚契约。

麻烦就在这里:这份契约并不清楚。

Phil Eaton 最近介绍了一个新开源工具 Monastery。它来自 The Consensus Labs,目标是把 Hermitage 风格的事务隔离异常测试自动化,拿去跑 MySQL、MariaDB 等数据库,看同一个隔离级别在不同实现里到底会发生什么。

这事最值得注意的点,不是 MySQL 和 MariaDB 谁赢谁输。至少从目前材料看,原文主要给出方法和例子,后半部分被订阅墙截断,并没有完整测试矩阵,也没有最终判决。

真正的问题更老,也更硬:SQL 隔离级别的名字听起来像标准,落到并发事务里,经常只是入口,不是答案。

SQL 给了四个名字,但行为要靠测试

SQL 标准常用几个隔离级别描述事务行为:Read Uncommitted、Read Committed、Repeatable Read、Serializable。

这些名字很熟。问题是定义长期有歧义。数据库圈早就知道,ANSI SQL 对隔离异常的描述不够完整;Phil Eaton 的文章也提到,2023 版 SQL 标准仍被批评没有把这件事说透。

压缩成一张表:

隔离级别名字暗示真正要看什么
Read Uncommitted最松,可能读到未提交数据是否允许 Dirty Reads
Read Committed只读已提交数据每条语句看到的快照和锁语义
Repeatable Read同一事务内读值更稳定MVCC、间隙锁、冲突检测是否一致
Serializable理论上接近串行执行成本、阻塞、回滚和异常边界

Monastery 做的事,就是把这些抽象异常变成可执行 SQL 测试。不是拿文档吵架,而是让数据库自己交卷。

这对两类人最直接。

数据库内核和基础设施工程师,要把它当成回归测试和兼容性测试工具看。升级版本、切换引擎、调整默认隔离级别之前,先跑事务异常用例,而不是只读 release note。

后端工程师和技术决策者,要把它当成选型前的风险清单。账务、库存、订单状态机、额度扣减这些场景,不能只问“支持不支持 Repeatable Read”。更该问:我的事务会不会读到脏数据?会不会出现丢失更新?冲突是被锁住、被回滚,还是悄悄滑过去?

采购可以晚一点拍板。迁移可以多跑一组测试。这个成本很低,但比事后补账便宜。

保龄球鞋这个例子,比术语诚实

原文用了 Lorin Hochstein 关于 Dirty Writes 的思想实验:保龄球馆里,一双鞋有左脚和右脚。两个并发事务不能把一双鞋拆开,各拿一只。

换成数据库,就是一张 shoes 表,里面有 left_shoeright_shoeshoe_id

事务 t1 想把左右鞋都写成 Lin。事务 t2 想把左右鞋都写成 Carlos。最后结果应该是一双完整的鞋:要么 Lin/Lin,要么 Carlos/Carlos。

如果一个事务覆盖了另一个还没提交事务的写入,这叫 Dirty Writes。主流数据库通常不允许 Dirty Writes。即便 SQL 标准的语义空间里留有荒唐余地,真实系统也很少敢放开这个口子。

Dirty Reads 是另一件事。它指一个事务读到了另一个还没提交事务的写入。部分数据库在 Read Uncommitted 下允许这种行为。PostgreSQL 没有真正意义上的 Read Uncommitted;即便你设置这个级别,它也不会按最松的脏读语义执行。

这里最容易出错的是把两者混在一起。

中间 SELECT 看到不一致,更可能说明脏读,不必然说明脏写。要判断 Dirty Writes,关键看未提交写入是否被另一个事务覆盖,以及最终提交后有没有出现不可能状态。

这就是 Monastery 的价值。它把“左右鞋不能被拆走”这种直觉翻译成 SQL,再把 SQL 放到不同数据库和不同隔离级别里跑。

术语会骗人。最终状态不会。

我更信可复现实验,不信隔离级别的门匾

数据库一致性最怕名词信仰。

很多团队排障时,会把隔离级别当成产品标签:MySQL 的 Repeatable Read 是什么,MariaDB 的 Read Committed 是什么,ORM 默认又是什么。听起来像在讨论契约,实际常常是在讨论印象。

真正起作用的是锁语义、MVCC 细节、语句执行顺序、冲突检测和回滚规则。名字一样,不代表后果一样。名字相近,也不代表风险相近。

这有点像早期铁路公司各修各的轨距。都叫铁路,都能跑车。平时差别藏在工程细节里,一到联运、调度、换线,差异就变成治理问题。

数据库隔离级别也是这样。平时是文档差异。事故时就是账务错乱、库存穿透、状态机失真。

“名不正,则言不顺。”这句话放在这里很准。隔离级别的名字如果不能准确约束行为,工程师只能回到实验、日志和可复现用例。

我不太买账“某数据库支持某隔离级别,所以天然安全”这类说法。更靠谱的问法只有几个:

  • 我的核心事务会遇到哪些异常?
  • 当前数据库是阻塞、回滚,还是允许异常发生?
  • ORM 默认隔离级别有没有把风险藏起来?
  • 版本升级或迁移后,这些行为有没有变?

接下来真正该观察的,也不是谁在文档里把名字写得更漂亮。

要看 Monastery 能不能覆盖更多数据库、更多隔离级别。要看它的用例能不能被团队纳入 CI、升级验证和迁移评估。还要看 MySQL、MariaDB 这类实现差异,最后会落到哪些具体异常、锁行为和业务后果上。

目前不能替它们下结论。证据还不够。

但这件事已经说明一点:数据库不会因为隔离级别写得体面,就在并发压力下自动变得可靠。它只会按自己的实现规则运行。

文档是门匾。事务结果才是家法。