ag旗舰厅
官网
Portraits
Journal
Contact
B 树被用在 id 字段上,B 树中的每个节点上保存了 CTID 值。注意,在这种情况下,在 B 树的字段的顺序,刚好与表中顺序相同,这是由于使用自动递增的 id 的缘故,但这并不一定需要是这种情况。
二级索引看起来相似;主要的区别是字段存储顺序不同,因为 B 树,必须按字典顺序组织。姓名索引
(first,last)按
字母表的顺序排列:
同样,birth_year 聚簇索引按升序排列,就像这样:
正如你所看到的,在这两种情况下,在各自的二级索引 CTID 字段本身并不是有序的,不象第一个自动递增的主键的情况。
假设我们需要更新此表中的记录。举例来说,假设要更新 al-Khwārizmī’ 的出生年份到 770 CE。正如前面提到的,行的 tuple 是不可变的。因此,要更新记录,需要添加一个新的 tuple。这种新的 tuple 有一个新的不透明 CTID,我们称之为 I。Postgres 需要能够从旧的 tuple D 处找到新的 I。在内部,Postgres 存储每个
tuple
中的版本字段,以及指向前一
tuple 的 ctid 指针
(如果有)。因此,该表的新结构如下:
只要 al-Khwārizmī 的两个版本存在,索引则必须维护两行的记录。为简单起见,我们省略了主键索引并显示只有在这里的二级索引,它是这样的:
我们将旧版本标识成红色,将新版标识成绿色。在此之下,Postgres 使用另一个字段来保存该行版本,以确定哪一个 tuple 是最新的。这个新增的字段允许数据库确定事务看到的是那一个行的 tuple。
在 Postgres,主索引和二级索引都指向磁盘上的
tuple
偏移。当一个
tuple
的位置变化,各项索引都必须更新。
复制
当我们插入数据到表中,如果启用了流复制机制,Postgres 将会对数据进行复制,处于崩溃恢复的目的,数据库启用了预写日志 (WAL)并使用它来实现两阶段提交(2PC)。
即使不启用复制的情况下,
数据库也必须保留 WAL ,因为 WAL 提供了 ACID 的原子性(Atomicity)及持久性(Durability)能力。
我们可以通过如下场景来更好的理解 WAL,如果数据库遇到突然断电时意外崩溃,WAL 就提供了磁盘上表与索引更新变化的一个账本。当 Postgres 的守护程序再次启动后,就会对比账本上的记录与磁盘上的实际数据是否一致。如果帐本包含未在磁盘上的体现的数据,则可以利用 WAL 的记录来修正磁盘上的数据。
另外一方面,Postgres 也利用 WAL 将其在主从之间发送来实现流复制功能。每个从库复制数据与上述崩溃恢复的过程类似。流复制与实际崩溃恢复之间的唯一区别是,在恢复数据过程中是否能对外提供数据访问服务。
由于 WAL 实际上是为崩溃恢复目的而设计,它包含在物理磁盘的低级别更新的信息。WAL 记录的内容是在行
tuple
和它们的磁盘偏移量(即一行 ctids) 的实际磁盘上的代表级别。如果暂停一个 Postgres 主库,从库数据完全赶上后,在从库的实际磁盘上的内容完全匹配主库。因此,像工具 rsync 都可以恢复一个同步失败的从库。
Postgres 上述设计的大坑
Postgres 的上述设计给 Uber 在 PG 的使用上,导致了效率低下和其他很多问题。
1、写放大(Write Amplification)
在 Postgres 设计的第一个问题是已知的写入放大 。
通常的写入放大是指一种问题数据写入,比如在 SSD 盘上,一个小逻辑更新(例如,写几个字节)转换到物理层后,成为一个更大的更昂贵的更新。
同样的问题也出现在 Postgres,在上面的例子,当我们做出的小逻辑更新,比如修改
al-Khwārizmī 的
出生年份时,我们不得不执行至少四个物理的更新:
1、在表空间中写入新行的
tuple;
2、
为新的 tuple 更新主键索引;
3、
为新的 tuple 更新姓名
索引
(first, last)
;
4、
更新 birth_year 索引,为新的
tuple
添加一条记录;
官网
Portraits
Journal
Contact