回归PostgreSQL隔离级别
多年来,我参加过几次系统设计面试。有些顺利,有些则不然。有一件事一直很稳定:当我设计一个在线系统事务隔离几乎总是在讨论中被提及。
当需求要求时CP系统我通常选择兼容PostgreSQL的NewSQL数据库,比如Aurora PostgreSQL、DSQL或CockroachDB。Andy Pavlo 和 Matthew Aslett 定义了 NewSQL作为关系型数据库,旨在实现类似NoSQL的扩展,同时保留SQL和ACID事务。
部分原因是我喜欢用Postgres思考。还有常见的面试手法,系统从一个小规模开始,面试官为了好玩把它放大100倍。使用无共享的Postgres,我可能突然需要手动分片或换个数据库。
无论哪种选择,都可能改变交易边界,迫使进行重大重新设计。NewSQL 让我可以保留关系模型和分布式事务,覆盖更广泛的规模。
这仍然留下孤立性的问题。我用Postgres作为参考模型。如果 MongoDB、DynamoDB、Cassandra 或其他数据库更适合这个问题,我会把它的保证和 Postgres 的已阅读 承诺,可重复读取, 和可序列化隔离层。
然后我可以描述最接近的Postgres对应词,并解释它们的区别。
我在面试之外也采用同样的方法。当我遇到不熟悉的 OLTP 或 SQL OLAP 数据库时,我会将其快照语义和冲突处理与 Postgres 进行比较,然后才信任隔离级别的名称。
这对于提供快照读取但不支持多语单事务的分析数据库尤其有用。语句级快照更接近Postgres的已阅读 承诺行为比其可重复读取隔离,即使两者都用MVCC底层。
想想ClickHouse。
Postgres给了我一个非常接近SQL标准的具体参考模型。
- 已阅读 承诺为每个语句拍摄新的快照,因此同一事务中的两次读取可能会看到不同的提交状态。
- 可重复读取事务使用一个快照,防止不可重复和幻影读取,但可能出现写偏差等序列化异常。
- 可序列化通过增加依赖追踪功能可序列化快照隔离(SSI)并且中止无法串行排序的交易。
以下是我如何将几个常见数据库与该模型叠加的方法。
| 数据库 | 事务与隔离模型 | 最近的Postgres参考 | 亮点 |
|---|---|---|---|
| PostgreSQL | 跨行和表格的交互式交易。已阅读 承诺是默认值,可重复读取提供快照隔离,可序列化使用SSI。 | 基线 | 可重复读取可以允许写偏差。可序列化可能会回归40001,要求应用程序重新尝试整个交易。 |
| Aurora DSQL | 使用交互式分布式事务强快照隔离以及乐观并发控制。 | 可重复读取 | DSQL 检测写写冲突,但不验证普通的读写依赖。写作倾斜是有可能发生的。冲突回归40001, 和SELECT FOR UPDATE可以让相关的读依赖参与冲突检测。 |
| MongoDB | 单文档操作是原子操作。多文档事务可以跨越集合、数据库和分片。snapshot多数写入关注的读取关注提供跨分片的同步快照。 | 可重复读取 | MongoDB没有通用可序列化水平。阅读问题控制可见性,写入担忧控制持久性。写偏差可能发生在快照事务中,写冲突需要重试。 |
| DynamoDB | TransactWriteItems以及TransactGetItems提供最多100个已知物品的一次性交易。事务操作包括可序列化相对于其他交易和单个项目操作。 | 可序列化在有界项集上 | 保证涵盖了请求中提到的物品。Query,Scan, 和BatchGetItem是已阅读 承诺作为综合运营。没有交互式的读-算-写事务,全局表也无法在区域间保持事务原子性。 |
Cassandra 是一个有用的边缘案例,因为它没有与 Postgres 匹配的隔离层。普通的CQL读写可以独立完成。你不能打开已阅读 承诺或可重复读取事务并对一个快照运行多个语句。
批次组写入,但隔离在分区边界处停止。轻量级交易使用Paxos进行线性比较与设置,这是最接近的匹配可序列化.它只涵盖一个分区内的条件变更。
Postgres可以在可序列化交易。用 Cassandra 时,我必须把这个不变量放在一个分区里,或者在应用里协调。
我不需要把每个数据库都记住成一个独立的隔离模型。我从Postgres开始,找到最近的层级,然后用剩下的讨论时间讲区别。到目前为止,这对我来说效果不错。
快照隔离是我在任何提供快照隔离的数据库中最喜欢的隔离级别。在Postgres中,这是可重复读取.它检测写入-写入冲突。可序列化同样如此,但SSI还跟踪行并预测每个事务的读取,以便检测读写依赖关系。
这使得SSI能够防止写偏移。
这种簿记会随着读集的增加而增加,而宽广的读写可能会重叠更多写入,导致更多的序列化失败。当不变量映射到现有行时,SELECT FOR UPDATE可以强制竞争事务在这些行上冲突,防止写偏移。
如果不变量依赖于缺失的行或任意谓词,可能没有什么可锁定的,所以你需要使用可序列化.但我在实际操作中倾向于避免这种设计。
马克·布鲁克有一个精彩的帖子关于为什么快照隔离对高性能系统有意义。