广告位联系
返回顶部
分享到

PostgreSQL HOT与PHOT有哪些区别

PostgreSQL 来源:互联网 作者:佚名 发布时间:2022-09-22 09:01:15 人浏览
摘要

1、HOT概述 PostgreSQL中,由于其多版本的特性,当我们进行数据更新时,实际上并不是直接修改元数据,而是通过新插入一行数据来进行间接的更新。而当表上存在索引时,由于新插入了

1、HOT概述

PostgreSQL中,由于其多版本的特性,当我们进行数据更新时,实际上并不是直接修改元数据,而是通过新插入一行数据来进行间接的更新。而当表上存在索引时,由于新插入了数据,那么索引必然也需要同步进行更新,这在索引较多的情况下,对于更新的性能影响必然很大。

为了解决这一问题,pg从8.3版本开始就引入了HOT(Heap Only Tuple)机制。其原理大致为,当更新的不是索引字段时,我们通过将旧元组指向新元组,而原先的索引不变,仍然指向旧元组,但是我们可以通过旧元组作为间接去访问到新的元组,这样就不用再去更新索引了。

2、HOT实现技术细节

要使用HOT进行更新,需要满足两个前提:

  • 新的元组和旧元组必须在同一个page中;
  • 索引字段不能进行更新。

当我们进行HOT更新时,首先是分别设置旧元组的t_informask2标志位为HEAP_HOT_UPDATED和新元组为HEAP_ONLY_TUPLE。

更新如下图所示:

我们更新tuple1为tuple2,分别设置这两行元组的t_informask2标志位,然后tuple1的ctid指向tuple2,而tuple2指向自己。

但是这样存在一个明显的问题,我们都知道pg会定期进行vacuum清理那些死元组,那么我们这里如果通过tuple1去访问tuple2的话,tuple1这个死元组被清理了又该怎么办呢?

所以pg会在合适的时机进行行指针的重定向,将旧元组的行指针指向新元组的行指针,这一过程称为修剪。于是在修剪之后,我们通过HOT机制访问数据便成了这样:

1、通过索引元组找到旧元组的行指针1;

2、通过重定向的行指针1找到行指针2;

3、通过行指针2找到新元组tuple2。

这样即使旧元组tuple1被清理掉也没有影响了。

HOT对应的wal日志实现:

对于HOT的update操作,其wal日志中记录的信息主要是由xl_heap_update结构存储。

如果新的元组存储在 block_id 为 0 的块上,如果不是 XLOG_HEAP_HOT_UPDATE,那么旧的元组将会存储在 block_id 为 1 的块上。反之如果block_id 为 1 的块没有被使用,那么则认为是 XLOG_HEAP_HOT_UPDATE。

3、何时进行修剪

前面我们提到了,旧行的行指针会重定向到新行的行指针,这一过程称之为修剪。那么什么时候会发生修剪呢?

一般来说,当我们执行select、update、insert、delete这些命令时均有可能触发修剪,其触发机制大致有两种情况:

  • 上一次进行update时无法在本page找到足够的空间;
  • 当前page上剩余空间小于fill-factor的值,最多小于10%

除此之外,当进行修剪时,还会选择合适的时机进行死元组的清理,这一操作称为碎片整理。碎片整理发生在当我们对元组进行检索时发现空闲空间少于10%时,和修剪不同的是,碎片整理不会发生在insert时,因为该操作并不会检索行。

相较于普通的vacuum操作,碎片清理并不涉及索引元组的清理,开销相对于常规的清理要小很多,是通过PageRepairFragmentation函数来实现的。

这也是为什么HOT要求新旧元组需要在同一个page中,虽然从理论上来说我们可以将行指针的链表指向不同page,但是这样我们便不能使用page-local的操作来进行碎片清理了。

4、HOT的不足

前面我们提到了HOT的前提条件之一就是:更新的列不能是索引列。需要注意,当更新的列是索引列时并不仅仅是会修改该列上的索引,整张表上所有的索引均会被修改。

例子:

创建测试表:

1

2

3

4

5

6

7

8

9

10

bill=# create table a(id int, c1 int, c2 int, c3 int);

CREATE TABLE

bill=# insert into a select generate_series(1,10), random()*100, random()*100, random()*100;

INSERT 0 10

bill=# create index idx_a_1 on a (id);

CREATE INDEX

bill=# create index idx_a_2 on a (c1);

CREATE INDEX

bill=# create index idx_a_3 on a (c2);

CREATE INDEX

观察索引页内容:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

bill=# SELECT * FROM bt_page_items('idx_a_1',1);

 itemoffset |  ctid  | itemlen | nulls | vars |          data           | dead |  htid  | tids

------------+--------+---------+-------+------+-------------------------+------+--------+------

          1 | (0,1)  |      16 | f     | f    | 01 00 00 00 00 00 00 00 | f    | (0,1)  |

          2 | (0,2)  |      16 | f     | f    | 02 00 00 00 00 00 00 00 | f    | (0,2)  |

          3 | (0,3)  |      16 | f     | f    | 03 00 00 00 00 00 00 00 | f    | (0,3)  |

          4 | (0,4)  |      16 | f     | f    | 04 00 00 00 00 00 00 00 | f    | (0,4)  |

          5 | (0,5)  |      16 | f     | f    | 05 00 00 00 00 00 00 00 | f    | (0,5)  |

          6 | (0,6)  |      16 | f     | f    | 06 00 00 00 00 00 00 00 | f    | (0,6)  |

          7 | (0,7)  |      16 | f     | f    | 07 00 00 00 00 00 00 00 | f    | (0,7)  |

          8 | (0,8)  |      16 | f     | f    | 08 00 00 00 00 00 00 00 | f    | (0,8)  |

          9 | (0,9)  |      16 | f     | f    | 09 00 00 00 00 00 00 00 | f    | (0,9)  |

         10 | (0,10) |      16 | f     | f    | 0a 00 00 00 00 00 00 00 | f    | (0,10) |

(10 rows)

bill=# SELECT * FROM bt_page_items('idx_a_2',1);

 itemoffset |   ctid    | itemlen | nulls | vars |          data           | dead |  htid  |       tids

------------+-----------+---------+-------+------+-------------------------+------+--------+-------------------

          1 | (0,5)     |      16 | f     | f    | 00 00 00 00 00 00 00 00 | f    | (0,5)  |

          2 | (0,4)     |      16 | f     | f    | 05 00 00 00 00 00 00 00 | f    | (0,4)  |

          3 | (0,8)     |      16 | f     | f    | 0e 00 00 00 00 00 00 00 | f    | (0,8)  |

          4 | (0,9)     |      16 | f     | f    | 25 00 00 00 00 00 00 00 | f    | (0,9)  |

          5 | (16,8194) |      32 | f     | f    | 30 00 00 00 00 00 00 00 | f    | (0,1)  | {"(0,1)","(0,3)"}

          6 | (0,10)    |      16 | f     | f    | 58 00 00 00 00 00 00 00 | f    | (0,10) |

          7 | (0,6)     |      16 | f     | f    | 60 00 00 00 00 00 00 00 | f    | (0,6)  |

          8 | (0,7)     |      16 | f     | f    | 62 00 00 00 00 00 00 00 | f    | (0,7)  |

          9 | (0,2)     |      16 | f     | f    | 63 00 00 00 00 00 00 00 | f    | (0,2)  |

(9 rows)

bill=# SELECT * FROM bt_page_items('idx_a_3',1);

 itemoffset |  ctid  | itemlen | nulls | vars |          data           | dead |  htid  | tids

------------+--------+---------+-------+------+-------------------------+------+--------+------

          1 | (0,6)  |      16 | f     | f    | 03 00 00 00 00 00 00 00 | f    | (0,6)  |

          2 | (0,3)  |      16 | f     | f    | 12 00 00 00 00 00 00 00 | f    | (0,3)  |

          3 | (0,5)  |      16 | f     | f    | 15 00 00 00 00 00 00 00 | f    | (0,5)  |

          4 | (0,9)  |      16 | f     | f    | 1a 00 00 00 00 00 00 00 | f    | (0,9)  |

          5 | (0,4)  |      16 | f     | f    | 33 00 00 00 00 00 00 00 | f    | (0,4)  |

          6 | (0,7)  |      16 | f     | f    | 3d 00 00 00 00 00 00 00 | f    | (0,7)  |

          7 | (0,10) |      16 | f     | f    | 4e 00 00 00 00 00 00 00 | f    | (0,10) |

          8 | (0,1)  |      16 | f     | f    | 4f 00 00 00 00 00 00 00 | f    | (0,1)  |

          9 | (0,2)  |      16 | f     | f    | 5c 00 00 00 00 00 00 00 | f    | (0,2)  |

         10 | (0,8)  |      16 | f     | f    | 5d 00 00 00 00 00 00 00 | f    | (0,8)  |

(10 rows)

更新索引列c2:

1

2

3

4

5

6

7

bill=# update a set c2=c2+1 where id=1 returning ctid,*;

  ctid  | id | c1 | c2 | c3

--------+----+----+----+----

 (0,11) |  1 | 19 | 66 | 86

(1 row)

 

UPDATE 1

再观察索引内容:

可以看到3个索引的索引页均发生了变化。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

bill=# SELECT * FROM bt_page_items('idx_a_1',1);

 itemoffset |  ctid  | itemlen | nulls | vars |          data           | dead |  htid  | tids

------------+--------+---------+-------+------+-------------------------+------+--------+------

          1 | (0,1)  |      16 | f     | f    | 01 00 00 00 00 00 00 00 | f    | (0,1)  |

          2 | (0,11) |      16 | f     | f    | 01 00 00 00 00 00 00 00 | f    | (0,11) |

          3 | (0,2)  |      16 | f     | f    | 02 00 00 00 00 00 00 00 | f    | (0,2)  |

          4 | (0,3)  |      16 | f     | f    | 03 00 00 00 00 00 00 00 | f    | (0,3)  |

          5 | (0,4)  |      16 | f     | f    | 04 00 00 00 00 00 00 00 | f    | (0,4)  |

          6 | (0,5)  |      16 | f     | f    | 05 00 00 00 00 00 00 00 | f    | (0,5)  |

          7 | (0,6)  |      16 | f     | f    | 06 00 00 00 00 00 00 00 | f    | (0,6)  |

          8 | (0,7)  |      16 | f     | f    | 07 00 00 00 00 00 00 00 | f    | (0,7)  |

          9 | (0,8)  |      16 | f     | f    | 08 00 00 00 00 00 00 00 | f    | (0,8)  |

         10 | (0,9)  |      16 | f     | f    | 09 00 00 00 00 00 00 00 | f    | (0,9)  |

         11 | (0,10) |      16 | f     | f    | 0a 00 00 00 00 00 00 00 | f    | (0,10) |

(11 rows)

bill=# SELECT * FROM bt_page_items('idx_a_2',1);

 itemoffset |  ctid  | itemlen | nulls | vars |          data           | dead |  htid  | tids

------------+--------+---------+-------+------+-------------------------+------+--------+------

          1 | (0,6)  |      16 | f     | f    | 04 00 00 00 00 00 00 00 | f    | (0,6)  |

          2 | (0,9)  |      16 | f     | f    | 0b 00 00 00 00 00 00 00 | f    | (0,9)  |

          3 | (0,1)  |      16 | f     | f    | 13 00 00 00 00 00 00 00 | f    | (0,1)  |

          4 | (0,11) |      16 | f     | f    | 13 00 00 00 00 00 00 00 | f    | (0,11) |

          5 | (0,2)  |      16 | f     | f    | 19 00 00 00 00 00 00 00 | f    | (0,2)  |

          6 | (0,5)  |      16 | f     | f    | 1d 00 00 00 00 00 00 00 | f    | (0,5)  |

          7 | (0,8)  |      16 | f     | f    | 1e 00 00 00 00 00 00 00 | f    | (0,8)  |

          8 | (0,4)  |      16 | f     | f    | 21 00 00 00 00 00 00 00 | f    | (0,4)  |

          9 | (0,3)  |      16 | f     | f    | 28 00 00 00 00 00 00 00 | f    | (0,3)  |

         10 | (0,10) |      16 | f     | f    | 3a 00 00 00 00 00 00 00 | f    | (0,10) |

         11 | (0,7)  |      16 | f     | f    | 4d 00 00 00 00 00 00 00 | f    | (0,7)  |

(11 rows)

bill=# SELECT * FROM bt_page_items('idx_a_3',1);

 itemoffset |  ctid  | itemlen | nulls | vars |          data           | dead |  htid  | tids

------------+--------+---------+-------+------+-------------------------+------+--------+------

          1 | (0,2)  |      16 | f     | f    | 17 00 00 00 00 00 00 00 | f    | (0,2)  |

          2 | (0,7)  |      16 | f     | f    | 18 00 00 00 00 00 00 00 | f    | (0,7)  |

          3 | (0,5)  |      16 | f     | f    | 33 00 00 00 00 00 00 00 | f    | (0,5)  |

          4 | (0,6)  |      16 | f     | f    | 37 00 00 00 00 00 00 00 | f    | (0,6)  |

          5 | (0,4)  |      16 | f     | f    | 38 00 00 00 00 00 00 00 | f    | (0,4)  |

          6 | (0,1)  |      16 | f     | f    | 41 00 00 00 00 00 00 00 | f    | (0,1)  |

          7 | (0,11) |      16 | f     | f    | 42 00 00 00 00 00 00 00 | f    | (0,11) |

          8 | (0,9)  |      16 | f     | f    | 4d 00 00 00 00 00 00 00 | f    | (0,9)  |

          9 | (0,8)  |      16 | f     | f    | 58 00 00 00 00 00 00 00 | f    | (0,8)  |

         10 | (0,3)  |      16 | f     | f    | 62 00 00 00 00 00 00 00 | f    | (0,3)  |

         11 | (0,10) |      16 | f     | f    | 63 00 00 00 00 00 00 00 | f    | (0,10) |

(11 rows)

这也意味着当我们无法使用HOT更新时,每更新一条数据都会更新所有的索引,那么这个对性能的影响可想而知。而我们简单想想其实并没有这个必要,因为对于没有被更新的索引列而言,只是ctid发生了变化,其索引列指向的值并没有变化。那么有没有办法让我们更新索引列时只修改该索引列的索引呢?PHOT应运而生。

5、PHOT概述

PHOT(Partial Heap Only Tuples),正如我们前面所说,PHOT是为了解决HOT更新索引列时会修改索引列的弊端。目前,PG中还不支持该功能,不过社区中已经有相关的讨论了,预计可能会在PG15中和大家见面。

PHOT的设计思路主要是:通过在t_infomask2标志位新加入两种类型HEAP_HOT_UPDATED 和HEAP_ONLY_TUPLE用来表示PHOT更新,类似于HOT。当我们对索引列进行修改时,通过一个PHOT的bitmap位图来记录哪些索引列被更新了,然后,当我们对索引列修改时,只需要修改这个bitmap位图中的列即可。

1

2

#define HEAP_HOT_UPDATED        0x4000  /* tuple was HOT-updated */

#define HEAP_ONLY_TUPLE         0x8000  /* this is heap-only tuple */

6、PHOT实例

我们通过下面的例子来看看PHOT和HOT的不同之处。

构建环境:

1

2

3

4

5

6

7

8

9

CREATE TABLE test (a INT, b INT, c INT);

CREATE INDEX ON test (a);

CREATE INDEX ON test (b);

CREATE INDEX ON test (c);

INSERT INTO test VALUES (0, 0, 0);

UPDATE test SET a = 1, b = 1;

UPDATE test SET b = 2, c = 2;

UPDATE test SET a = 2;

UPDATE test SET a = 3, c = 3;

不使用PHOT:

可以看到,不使用PHOT的情况下,每次更新都会产生新的索引元组。

在这些更新之后,有 15 个索引元组、5 个行指针和 5 个堆元组。

1

2

3

4

5

test_a_idx 0       1       1       2       3

test_b_idx 0       1       2       2       2

test_c_idx 0       0       2       2       3

lp         1       2       3       4       5

heap       (0,0,0) (1,1,0) (1,2,2) (2,2,2) (3,2,3)

PHOT:

在使用PHOT后,通过增加了一个bitmap位图来记录被更新的列。当执行完上述更新后,有 10 个索引元组、5 个行指针、5 个堆元组和 4 个位图。

1

2

3

4

5

6

test_a_idx 0       1               2       3

test_b_idx 0       1       2

test_c_idx 0               2               3

lp         1       2       3       4       5

heap       (0,0,0) (1,1,0) (1,2,2) (2,2,2) (3,2,3)

bitmap             xx-     -xx     x--     x-x

而前面的例子我们使用PHOT更新又是什么结果呢?

可以看到下图中,左边是使用PHOT进行更新的,只是被更新的索引列发生了变化,而右边非PHOT进行更新则是所有的索引列均发生变化。

性能测试:

通过简单的pgbench测试,TPS 提高了约 34%,其中每个表都有 5 个额外的文本列和每列上的索引。有了额外的索引和列,理论上 TPS 的提升应该会更高。对于没有表修改的短期 pgbench 运行,使用 PHOT 观察到 TPS 增加了约 2%,表明常规 pgbench 运行没有受到显着影响。

总结

PostgreSQL使用HOT机制来避免因为其多版本特性导致的每次更新数据均需要修改索引的情况。而当前的HOT机制,对于索引列的更新仍然存在比较明显的性能问题,因为所有的索引均需要发生修改,不过预计在PG15中,将会加入更为强大的PHOT功能,更新索引列再也不会影响其它索引了。


版权声明 : 本文内容来源于互联网或用户自行发布贡献,该文观点仅代表原作者本人。本站仅提供信息存储空间服务和不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权, 违法违规的内容, 请发送邮件至2530232025#qq.cn(#换@)举报,一经查实,本站将立刻删除。
原文链接 : https://foucus.blog.csdn.net/article/details/121336372
相关文章
  • Postgresql删除数据库表中重复数据的几种方法
    一直使用Postgresql数据库,有一张表是这样的: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 DROP TABLE IF EXISTS public.devicedata; CREATE TABLE public.devicedata ( Id varchar(20
  • PostgreSQL HOT与PHOT有哪些区别

    PostgreSQL HOT与PHOT有哪些区别
    1、HOT概述 PostgreSQL中,由于其多版本的特性,当我们进行数据更新时,实际上并不是直接修改元数据,而是通过新插入一行数据来进行间接
  • PostgreSQL索引失效会发生什么?

    PostgreSQL索引失效会发生什么?
    前段时间碰到个奇怪的索引失效的问题,实际情况类似下面这样: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 bill=# begin; BEGIN bill=*# create inde
  • PostgreSQL pg_filenode.map文件介绍

    PostgreSQL pg_filenode.map文件介绍
    今天在网上看到有人问误删pg_filenode.map该如何恢复或者重建,解决这个问题前我们先来了解下pg_filenode.map文件。 对于PostgreSQL中的每张表在磁
  • PostgreSQL limit的神奇作用介绍
    最近碰到这样一个SQL引发的性能问题,SQL内容大致如下: 1 2 3 4 5 6 7 SELECT * FROM t1 WHERE id = 999 AND (case $1 WHEN true THEN info = $2 ELSE info = $3 end) l
  • PostgreSql生产级别数据库安装要注意事项

    PostgreSql生产级别数据库安装要注意事项
    我让公司的小伙伴写一个生产级别的PostgreSQL的安装文档,结果他和我说:不是用一个命令就能安装好么?还用写文档么?。我知道他想说的
  • PostgreSQL12.5中分区表的一些操作介绍
    1、创建一个有DEFAULT的分区表 1、先创建主表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 create table tbl_log ( id serial, create_time timestamp(0) without time zone, remark char
  • Postgres中UPDATE更新语句源码分析
    PG中UPDATE源码分析 本文主要描述SQL中UPDATE语句的源码分析,代码为PG13.3版本。 整体流程分析 以update dtea set id = 1;这条最简单的Update语句进行
  • 在Centos8-stream安装PostgreSQL13的教程

    在Centos8-stream安装PostgreSQL13的教程
    一、安装postgresql13-server 1 2 yum install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm yum install -y postgresql13-
  • PostgreSQL自动更新时间戳实例介绍
    什么是PostgreSQL时间戳数据类型? 在PostgreSQL中,下一个数据类型是 TIMESTAMP ,它可以存储 TIME 和 DATE 值。但它不支持任何 时区数据。这意味
  • 本站所有内容来源于互联网或用户自行发布,本站仅提供信息存储空间服务,不拥有版权,不承担法律责任。如有侵犯您的权益,请您联系站长处理!
  • Copyright © 2017-2022 F11.CN All Rights Reserved. F11站长开发者网 版权所有 | 苏ICP备2022031554号-1 | 51LA统计