Postgresql的HOT技术

636 阅读2分钟

这是我参与8月更文挑战的第31天,活动详情查看:8月更文挑战

不使用HOT带来的问题

PG实现了MVCC,但是没有引入undo表空间,为实现多版本读,所有的update操作都插入一个新版本行(delete-marked-old + insert-new),数据行的各个版本从老到新形成一个单向链表,即update chain。

pre-8.3,索引块也是采用同样的策略,即便update没有更新任何索引列,举例说明:

表t有两个列(id , name),其中name上有索引 ind_name,现在表有2个数据行,分别称为tuple1和tuple2

(1, 'a')

(0, 'b')

执行update t set id =id+ 1 where name = 'a';

现在表上有3个数据行

(1, 'a') ---> (2, 'a') 这个是update chain, (1, 'a')此时被标记为deleted,且其指向(2, 'a')

(0, 'b')

而索引ind_name同样会新增一个entry,尽管没有更新索引列name

('a', tuple1_pos) --> ('a', tuple1_pos),第2个entry没有存在的必要

('b', tuple2_pos)

这会导致索引占用更多的空间,提升访问代价。

解决方法 HOT技术

PG会在修剪过程中在适当的时候清理dead tuple,这种处理称为碎片整理(defragmentation)。

碎片整理的成本低于正常VACUUM处理的成本,因为碎片整理不涉及删除索引元组。

HOT不可用情况

当更新的元组存储在另一个不存储旧元组的页中时,指向元组的索引元组也被插入到索引页中。

当索引元组的key值被更新时,新的索引元组被插入到索引页中。

从8.3开始,当update语句同时满足下述两个条件时,索引不会新增entry:

1 update没有更新索引列;

2 表块有足够空间容纳update产生的新版本行,即同一update chain上的所有版本必须位于同一个数据块中。

同样的例子,执行update t set id =id+ 1 where name = 'a'后

现在表上有3个数据行

(1, 'a') ---> (2, 'a')

(0, 'b')

而索引ind_name没有改变

('a', tuple1_pos)

('b', tuple2_pos)

尽管数据行从(1, 'a')变成了(2, 'a'),但是ind_name的索引项仍然指向(1, 'a'),回表时通过遍历update chain找到(2, 'a')

这种改进被称为HOT(heap only tuple)。

HOT功能小结

HOT在8.3版本中实现;当更新的行与旧行将存储在同一个表页中时,HOT能有效使用索引页和表页;HOT降低了VACUUM处理的必要性;

当用HOT更新行时,如果更新的行与旧行将存储在同一个表页中,则PostgreSQL不会插入相应的索引元组,从而减少更新写入所消耗的资源。