InfoQ Uber为什么放弃Postgres选择迁移到MySQL?( 二 )
对于后两种情况 , 二级索引中的ctid字段不是按照字典顺序递增的 , 这与自动递增主键的情况不同 。
假设我们需要更新该表中的一条记录 , 比如我们要更新al-Khwārizmī的出生年份 。 如前所述 , 行的元组是不可变的 。 因此 , 为了更新记录 , 我们向表中添加了一个新的元组 。 这个新的元组有一个新的ctid , 我们将其称为I 。 Postgres需要区分新元组I与旧元组D 。 在内部 , Postgres在每个元组中保存了一个版本字段和一个指向先前元组的指针(如果有的话) 。 因此 , 表的最新结构如下所示:
文章图片
只要存在al-Khwārizmī行的两个版本 , 索引中就必须同时包含两个行的条目 。 为简便起见 , 我们省略了主键索引 , 只显示了二级索引 , 如下所示:
文章图片
文章图片
我们用红色表示旧数据行 , 用绿色表示新数据行 。 Postgres使用另一个版本字段来确定哪个元组是最新的 。 数据库根据这个字段确定哪个元组对不允许查看新版本数据的事务可见 。
文章图片
在Postgres中 , 主索引和二级索引都直接指向磁盘上的元组偏移量 。 当元组位置发生变化时 , 必须更新所有索引 。
复制
当我们在表中插入新行时 , 如果启用了流式复制 , Postgres需要对其进行复制 。 为了能够在发生崩溃后恢复 , 数据库维护了预写日志(WAL) , 并用它来实现两阶段提交 。 即使未启用流式复制 , 数据库也必须维护WAL , 因为WAL可以保证ACID中的原子性和持久性 。
为了更好地理解WAL , 我们可以想象一下如果数据库意外发生崩溃(例如突然断电)会发生什么 。 WAL代表了一系列数据库计划对表和索引在磁盘上内容做出的更改 。 Postgres守护进程在启动时会将WAL的数据与磁盘上的实际数据进行对比 。 如果WAL中包含未反映到磁盘上的数据 , 数据库就会更正元组或索引数据 , 并回滚出现在WAL中但在事务中没有被提交的数据 。
Postgres通过将主数据库上的WAL发送给副本来实现流式复制 。 每个副本数据库就像是在进行崩溃恢复 , 不断地应用WAL更新 。 流式复制和实际发生崩溃恢复之间的唯一区别是 , 处于“热备用”模式的副本在应用WAL时可以提供查询服务 , 但真正处于崩溃恢复模式的Postgres数据库通常会拒绝提供查询服务 , 直到数据库实例完成崩溃恢复过程 。
因为WAL实际上是为实现崩溃恢复而设计的 , 所以它包含了底层的磁盘更新信息 。 WAL包含了元组及其磁盘偏移量(即行ctid)在磁盘上的表示 。 如果副本完全与主数据库同步 , 此时暂停Postgres的主数据库和副本 , 那么副本的磁盘内容与主数据库的磁盘内容将完全一致 。 因此 , 如果副本与主数据库不同步 , 可以用rsync之类的工具来修复 。
2
Postgres的设计所带来的后果
Postgres的设计导致Uber的数据效率低下 , 还让我们遇到了很多麻烦 。
写入放大
Postgres的第一个问题是写入放大 。 通常 , 写入放大是指将数据写入SSD磁盘时遇到的问题:小的逻辑更新(例如 , 写入几个字节)在转换到物理层时会放大 , 成本会变高 。 在之前的示例中 , 如果我们对al-Khwārizmī的出生年份进行小的逻辑更新 , 必须进行至少四个物理更新:
将新的行元组写入表空间
更新主键索引
更新(first,last)索引
【InfoQ Uber为什么放弃Postgres选择迁移到MySQL?】更新birth_year索引
实际上 , 这四个更新也只反映了对主表空间的写操作 。 除此之外 , 这些写操作也需要反映在WAL中 , 因此磁盘上的写操作总数会变得更多 。
推荐阅读
- 前有iPhone 12,后有Mate 40,为什么我却攥着中兴天机Axon 20 5G不放
- 机核网|为什么我觉得《刺客信条》越来越差了:游戏性篇
- 捷途|买新车时,为什么4S店都要送贴膜?原来里面这么多猫腻,太坑了
- 做你一辈子的蜡笔|西方为什么不假思索的污蔑中国?因为我们的这个错误!
- 暖日科技盈如|这到底是为什么?,俄罗斯华人给出劝告:最好别跟俄罗斯女孩结婚
- 什么值得买|为什么要精养信用卡?因为信用卡背后有你想象不到的财富!
- 汽车之家|我想说因为了解因此喜欢,别人总问我为什么喜欢风光ix5
- 电动大咖|3的为什么是宏光MINI EV,打败特斯拉Model
- 给你说个车|都不会考虑四驱,这是为什么呢?,多数人在买SUV时
- |为什么每一年的高考试卷,要安排在监狱里面印刷?原因你可能不信