MySQL 的 Repeatable-Read 隔离级别在实现和概念上存在一些误解。最早SQL标准对隔离级别的定义,基于几个异象,如脏读、不可重复读和幻读,这些定义并不明确。后来,随着 snapshot isolation 的出现,发现它能够解决脏读、不可重复读和幻读问题,因此 snapshot isolation 和 Repeatable-read 隔离级别在实现上变得相似,从而 MySQL、PostgreSQL 等数据库系统采用了 snapshot isolation,并将其称为 Repeatable-read 隔离级别。 snapshot isolation 的定义是:事务在执行过程中,读取的始终是事务开始时刻的快照版本,事务的写操作(更新、插入、删除)同样会被反映在快照中,如果事务再次访问数据,会读取最新的快照版本。然而,MySQL 的 InnoDB 存储引擎在读取操作时遵循 snapshot isolation,但在写入操作时则读取已提交的最新版本,这与 snapshot isolation 的定义不符。 在 snapshot isolation 模式下,若事务 T1 将要提交,会获得一个 Commit-Timestamp,只有在该 Commit-Timestamp 大于所有已存在的 Start-Timestamp 和 Commit-Timestamp 时,事务才能成功提交。若在此期间,其他事务 T2 的 Commit-Timestamp 落在 T1 的执行区间内,并修改了 T1 也修改过的数据,且 T2 已成功提交,则 T1 将被中止,这被称为 first-committer-wins。然而,InnoDB 并不遵循此规则,它在 Repeatable-Read 模式下,如果多个事务 T1 和 T2 同时修改同一行数据,且 T2 先提交,T1 的提交将直接覆盖 T2 的结果。 MySQL 开发者指出,InnoDB 的 Repeatable-Read 实现具有不同于其他数据库的特点,允许在 UPDATE 或 DELETE 操作中发生非重复读取。InnoDB 不仅会读取当前读视图中的数据,还会等待其他可能加入读视图的记录,并在更新或删除操作完成后包含这些记录。此外,InnoDB 结合隐式记录和间隙锁定,实际上添加了可序列化的组件到 Repeatable-Read 隔离级别。 PostgreSQL 社区解释了其 Repeatable-Read 实现:UPDATE、DELETE、SELECT FOR UPDATE 和 SELECT FOR SHARE 命令在查找目标行时,仅查找事务开始时间已提交的记录。如果目标行在查找时已被其他并发事务更新(或删除或锁定),则 Repeatable-Read 事务会等待第一个更新事务提交或回滚。如果第一个更新者回滚,则其效果被抵消,Repeatable-Read 事务可以继续更新原找到的行。若第一个更新者提交(实际更新或删除了行),则 Repeatable-Read 事务将被回滚并返回错误消息。 MySQL 选择这种实现方式的原因是为了避免在小事务场景下多次重试导致的性能问题,特别是在处理大量修改时。MySQL 的 Repeatable-Read 实现通过读取已提交的最新版本来更新数据,避免了事务回滚的需求,从而提高了效率。然而,这种实现方式不能完全避免丢失更新的情况发生。 总的来说,MySQL InnoDB 的 Repeatable-Read 实现与常见的 snapshot isolation 实现有区别,各有优缺点。MySQL 的实现更侧重于提高效率,允许事务在其他事务修改后的结果上进行更新,而常见的 snapshot isolation 实现更符合直观感受,尽管在某些场景下可能导致更多的事务回滚。