第七章 PostgreSQL之HOT和Index-Only Scans

最近搞了一个公众号PostgreSQL运维技术,欢迎来踩~

悄悄放一张:

PostgreSQL运维技术 

本章描述了与索引扫描相关的两个特性,即HOT 和 Index-Only Scans

注:是The Internals of PostgreSQL第七章

 

7.1. Heap Only Tuple (HOT)

 

PG在8.3版本中实现了HOT, 主要是为了当要更新的行存储在与旧行相同的表页中时,有效地使用索引和表中的页面。Hot也减少了VACUUM处理的必要性。

 

7.1.1. 使用非HOT的方式更新行

假设表' tbl '有两列:'id'和'data', 'id'是'tbl'的主键。

testdb=# \d tbl                Table "public.tbl" Column |  Type   | Collation | Nullable | Default --------+---------+-----------+----------+--------- id     | integer |           | not null |  data   | text    |           |          | Indexes:    "tbl_pkey" PRIMARY KEY, btree (id)

表tbl有1000个元组, id为'1000'的最后一个元组存储在表的第5页,最后一个元组指向对应的索引元组,其键为'1000',tid为'(5,1)'。参见图7.1(a)。

图. 7.1.使用非HOT的方式更新行

图片

我们考虑如何在不使用HOT的情况下更新最后一个元组。

testdb=# UPDATE tbl SET data = 'B' WHERE id = 1000;

在这种情况下,PostgreSQL不仅会在索引页中插入新的表元组,还会插入新的索引元组。见图7.1(b)。

索引元组的插入会消耗索引页空间,而且索引元组的插入和清除成本都很高。HOT降低了这些问题的影响。

 

7.1.2. HOT 是如何工作的?

 

当某一行数据使用HOT的方式被更新时,如果被更新的行与旧行数据存储在同一个页面,那么PG不会插入相应的索引元组。并且将老数据行的t_informask2字段设置为HEAP_HOT_UPDATE位, 新数据行的t_informask2字段设置为HEAP_ONLY_TUPLE位。参见图7.2和图7.3。

图. 7.2. 使用HOT的方式更新行

图片

例如,在本例中,'Tuple_1'和'Tuple_2'分别被设置为HEAP_HOT_UPDATED位和HEAP_ONLY_TUPLE位。

图. 7.3. HEAP_HOT_UPDATED and HEAP_ONLY_TUPLE 位

图片

 

接下来将会介绍PostgreSQL如何在使用HOT更新元组之后使用索引扫描来访问更新后的元组。

图. 7.4. 修剪行指针

图片

 

(1)找到指向目标元组的索引元组。

(2)访问正在获取的索引元组所指向的行指针[1]。

(3)读“Tuple_1”。

(4)通过'Tuple_1'的t_ctid读取' Tuple_2 '

在这种情况下,PostgreSQL读取两个元组,'Tuple_1'和'Tuple_2',并使用第五章中描述的并发控制机制来决定哪个是可见的。

但是,如果表页中的死亡元组被删除,就会出现一个问题。例如,在图7.4(a)中,如果'Tuple_1'被删除,因为它是一个死元组,'Tuple_2'不能从索引访问。

 

为了解决这个问题,PostgreSQL在适当的时候将指向旧元组的行指针重定向到指向新元组的行指针。在PostgreSQL中,这种处理称为剪枝。图7.4(b)描述了PostgreSQL如何访问修剪后更新的元组。

(1)查找索引元组。

(2)访问正在获取的索引元组所指向的行指针[1]。

(3)通过重定向(redirect)的行指针访问指向Tuple_2的行指针[2]。

(4)读取从行指针'[2]'指向的' Tuple_2 '。

如果可能的话,将在执行SELECT、UPDATE、INSERT和DELETE等SQL命令时执行修剪处理。具体的执行时间在本章中没有描述,因为它非常复杂。详细信息在PG源码中HOT的自述文件中有介绍。见:https://github.com/postgres/postgres/blob/master/src/backend/access/heap/README.HOT

 

PostgreSQL会在适当的时候删除死元组,在PostgreSQL的文档中,这种处理称为碎片整理。图7.5描述了使用HOT进行碎片整理的过程。

图. 7.5. 对死亡元祖进行碎片整理

图片

 

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

 

因此,使用HOT可以减少索引和页表的消耗;这也减少了VACUUM处理必须处理的元组的数量。因此,HOT对性能有很好的影响,


另外,我们需要注意下HOT不可用的情况。

当更新后的元组存储在另一个页(该页不存储旧元组)时,指向该元组的索引元组也会插入到索引页中。参见图7.6(a)。

当索引元组的键值被更新时,新的索引元组被插入到索引页中。见图7.6(b)。

图. 7.6. Hot不可用的情况

图片

 

7.2. Index-Only Scans

 

为了减少I/O(输入/输出)成本,当SELECT语句的所有目标条目都包含在索引键中时,仅索引扫描(通常称为仅索引访问)直接使用索引键而不访问相应的表页。几乎所有的商业RDBMS都提供了这种技术,比如DB2和Oracle。PostgreSQL从9.2版开始引入了这个选项。

 

下面,使用一个具体的例子,描述了PostgreSQL中索引扫描是如何执行的。

本例的假设解释如下:

表定义

我们有一个表'tbl',其定义如下所示:

testdb=# \d tbl      Table "public.tbl" Column |  Type   | Modifiers --------+---------+----------- id     | integer |  name   | text    |  data   | text    | Indexes:    "tbl_idx" btree (id, name)

索引

表'tbl'有一个索引'tbl_idx',它由'id'和'name'两列组成。

元组

'tbl'已经插入元组。

'Tuple_18',它的id是'18',名称是' Queen ',存储在第0页。

'Tuple_19',它的id是'19',名称是'BOSTON',存储在第一页。

可见性

第0页中的所有元组总是可见的;第一页中的元组并不总是可见的。注意,每个页面的可见性存储在相应的可见性映射中,可见性映射在6.2节中描述。

让我们看看PostgreSQL在执行下面的SELECT命令时是如何读取元组的。

testdb=# SELECT id, name FROM tbl WHERE id BETWEEN 18 and 19; id |  name   ----+-------- 18 | Queen 19 | Boston(2 rows)

 

该查询从表的两个列获取数据:'id'和'name',索引'tbl_idx'由这些列组成。因此,在使用索引扫描时,乍一看似乎不需要访问表页,因为索引元组包含必要的数据。然而,事实上,PostgreSQL原则上必须检查元组的可见性,索引元组没有任何关于事务的信息,比如堆元组的t_xmin和t_xmax,这在5.2节中有描述。因此,PostgreSQL必须访问表数据来检查索引元组中数据的可见性。

 

为了避免这种困境,PostgreSQL使用目标表的可见性映射。如果存储在一个页面中的所有元组都是可见的,PostgreSQL使用索引元组的键,而不访问索引元组指向的表页来检查其可见性;否则,PostgreSQL从index元组中读取指向的表元组,并检查元组的可见性。

 

在本例中,不需要访问' Tuple_18 ',因为存储' Tuple_18 '的第0页是可见的,也就是说,包括第0页中的Tuple_18在内的所有元组都是可见的。相反,需要访问' Tuple_19 '来处理并发控制,因为第一个页面的可见性不可见。参见图7.7。

图. 7.7.  Index-Only Scans 是如何工作的?

图片

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值