这是我参与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不会插入相应的索引元组,从而减少更新写入所消耗的资源。