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

悄悄放一张:

PostgreSQL运维技术

我在之前的一篇介绍MVCC的文章中提到,MVCC有事务ID回卷的问题,它的解决方案是VACUUM。但是没有展开讲。这章的话,会详细地介绍下VACUUM。

VACUUM的两个主要任务是删除死亡元组和冻结事务ID。

为了删除死亡元组,VACUUM提供了两种模式,即Concurrent Vacuum和Full Vacuum。

Concurrent Vacuum通常简称为VACUUM。它在删除表文件中的死亡元组时,其他事务仍可以在此进程运行时读取表。而Full VACUUM在运行时其他事务不能访问表。

本章将主要描述以下内容

VM(可见性映射)

Freeze

删除不必要的clog

AutoVacuum

Full VACUUM

注:本文是The Internals of PostgreSQL第六章


6.1. VACUUM的概述

VACUUM对数据库中的表执行以下的任务:

1、删除死亡元组

删除死亡元组和整理每个页面的活元组。

删除指向死元组的索引元组。

2、冻结old txids

如有必要,冻结元组的old txids。

更新冻结的txid相关的系统目录(pg_database和pg_class)。

如果可能,清除clog不必要的部分。

3、其他

更新已处理表的FSM和VM。

更新一些统计信息(pg_stat_all_tables等)。

下面的伪代码描述了VACUUM处理。


(1)  FOR each table
(2)       Acquire ShareUpdateExclusiveLock lock for the target table/* The first block */
(3)       Scan all pages to get all dead tuples, and freeze old tuples if necessary
(4)       Remove the index tuples that point to the respective dead tuples if exists/* The second block */
(5)       FOR each page of the table
(6)            Remove the dead tuples, and Reallocate the live tuples in the page
(7)            Update FSM and VMEND FOR/* The third block */
(8)       Clean up indexes
(9)       Truncate the last page if possible
(10       Update both the statistics and system catalogs of the target tableRelease ShareUpdateExclusiveLock lockEND FOR/* Post-processing */
(11)  Update statistics and system catalogs
(12)  Remove both unnecessary files and pages of the clog if possible

(1)从指定的表中获取每个表。

(2)获取表的ShareUpdateExclusiveLock锁。这个锁允许读取其他事务

(3)扫描所有页面得到所有死元组,如果有必要冻结旧元组。

(4)删除指向各自死亡元组的索引元组(如果存在的话)。

(5)对表格的每一页,执行以下步骤(6)和(7)。

(6)移除死去的元组,在页面中重新分配活着的元组。

(7)更新目标表中的FSM和VM。

(8)使用index_vacuum_cleanup()@indexam.c函数清理索引。

(9)截断最后一页,如果最后一页没有任何元组。

(10)更新目标表中与vacuum处理相关的统计信息和系统目录。

(11)更新与真空处理相关的统计数据和系统目录。

(12)如果可能,删除clog中不必要的文件和页面。

这个伪代码有两个部分:每个表的循环和后续处理。内环可分为三个区块。每个块都有单独的任务。

下面将概述这三个模块和后期处理。

注意:VACUUM命令从版本13开始就支持PARALLEL选项,如果设置了该选项,并且创建了多个索引,那么它将并行地处理vacuum index和cleanup index 阶段。

注意,此特性仅在VACUUM命令中有效,autovacuum不支持此特性。

6.1.1. 第一部分

这一部分执行冻结处理并删除指向死元组的索引元组。

首先,PostgreSQL扫描一个目标表,建立一个死元组列表,如果可能的话,冻结旧元组。列表存储在本地内存的maintenance_work_mem中。

扫描完成后,PostgreSQL通过引用死元组列表来删除索引元组。这个过程在内部被称为“清理阶段”。无须多说,这个过程是昂贵的。在版本10或更早的版本中,清理阶段是一定会被执行的。在版本11或更高版本中,如果目标索引是B-tree,那么是否执行清理阶段由配置参数vacuum_cleanup_index_scale_factor决定。详细信息请参见对该参数的描述。

当maintenance_work_mem已满且扫描未完成时,PostgreSQL继续执行下一个任务,即步骤(4)到(7);然后返回步骤(3),继续进行剩余的扫描。

6.1.2. 第二部分

这一部分执行删除死元组,并逐页更新FSM和VM。图6.1给出了一个例子:

图. 6.1. 删除死亡元组

假设该表包含三个页面。我们关注第0页(即第一页)。这个页面有三个元组。Tuple_2是一个死元组(图6.1(1))。在这种情况下,PostgreSQL移除Tuple_2并重新排序剩下的元组来修复碎片,然后更新FSM和VM(图6.1(2))。PostgreSQL将继续这个过程直到最后一页。

请注意,不必要的行指针不会被删除,它们将在将来被重用。因为,如果删除了行指针,那么关联索引的所有索引元组都必须更新。

6.1.3. 第三部分

第三部分在删除索引后执行清理,并更新与每个目标表vacuum处理相关的统计信息和系统目录。

而且,如果最后一页没有元组,它将从表文件中截断。

6.1.4. 后续处理

当vacuum处理完成后,PostgreSQL会更新一些与vacuum处理相关的统计信息和系统目录,并尽可能删除不必要的clog(章节6.4)。

注:

VACUUM采用第8.5节所述的ring buffer;因此,已处理的页面不会缓存在共享缓冲区(shared buffer)中。


6.2. VM(可见性映射)

VACUUM处理成本很高;因此,在8.4版本中引入VM以减少此成本。

VM的基本概念很简单: 每个表都有一个单独的可见性映射,它保存表文件中每个页面的可见性。页面的可见性决定了每个页面是否有死元组。真空处理可以跳过没有死元组的页面。

VM的使用情况如图6.2所示。假设该表由三个页面组成,第0页和第2页包含死元组,第1页不包含。该表的VM保存有关哪些页包含死元组的信息。此时,vacuum处理跳过第1页,直接参考VM的信息进行处理。

图. 6.2. VM是怎么工作的?

每个VM由一个或多个8 KB的页面组成,该文件以“VM”后缀存储。以relfilenode为18751的表文件为例,其中FSM (18751_fsm)和VM (18751_vm)文件如下所示。​​​​​​​

 $ cd $PGDATA$ ls -la base/16384/18751*-rw------- 1 postgres postgres  8192 Apr 21 10:21 base/16384/18751-rw------- 1 postgres postgres 24576 Apr 21 10:18 base/16384/18751_fsm-rw------- 1 postgres postgres  8192 Apr 21 10:18 base/16384/18751_vm

6.2.1. VM的增强版

在9.6版本中增强了VM,以提高冻结处理的效率。新的VM显示了页面的可见性以及元组是否在每个页面中被冻结的信息(章节6.3.3)。


6.3. Freeze处理

冻结处理有两种模式,根据特定条件在任意一种模式下执行。为了方便起见,这些模式被称为Lazy(惰性)模式和eager(急切)模式。

注:concurrent Vacuum通常被称为“lazy Vacuum”。然而,本文档中定义的lazy模式是冻结处理执行的一种模式。

冻结处理通常在lazy模式下运行;但是,在满足特定条件时运行eager模式。

在lazy模式下,冻结处理只扫描使用目标表的VM各自包含死元组的页面。

相反, eager模式扫描所有页面,而不管每个页面是否包含死元组,它还更新与冻结处理相关的系统目录,并在可能的情况下删除clog中的不必要部分。

第6.3.1节和第6.3.2节分别描述了这两个模式。以eager模式改进冻结流程,请参见6.3.3。

6.3.1. Lazy 模式

当启动freeze处理时,PostgreSQL会计算一个freezeLimit txid值,冻结t_xmin小于这个值的元组。

freezeLimit txid的定义如下:​​​​​​​

freezeLimit_txid=(OldestXmin−vacuum_freeze_min_age)

OldestXmin是当前运行的事务中最古老的txid。举个例子,如果在执行VACUUM命令时运行三个事务(txids 100、101和102),那么OldestXmin是100。

而vacuum_freeze_min_age是一个配置参数(默认为50,000,000)。

图6.3显示了一个特定的示例。这里,Table_1由三个页面组成,每个页面有三个元组。当执行VACUUM命令时,当前txid为50,002,500,并且没有其他事务。在这种情况下,OldestXmin是50002500,因此,freezeLimit txid为2500。冻结处理如下所示。

图. 6.3. Lazy Mode

第0页: 冻结三个元组是因为所有t_xmin值都小于freezeLimit txid。另外,在这个VACUUM过程中,Tuple_1会因为都是死元组而被移除。

第1页: 此页因为VM而略过。

第2页: Tuple_7和Tuple_8冻结;Tuple_7被移除。

在完成VACUUM处理之前,与VACUUM相关的统计信息会被更新,例如:pg_stat_all_tables' n_live_tup, n_dead_tup, last_vacuum, vacuum_count等。

6.3.2. Eager 模式

eager模式弥补了lazy模式的缺陷。它扫描所有页面,以检查表中的所有元组,更新相关的系统目录,并删除不必要的文件和页面阻塞,如果可能的话。

当满足以下条件时,将执行eager模式:​​​​​​​

pg_database.datfrozenxid < (OldestXmin −vacuum_freeze_table_age)

在上面的条件中,pg_database.datfrozenxid表示pg_database系统目录的列,并保存每个数据库最古老的冻结txid。细节在后面描述;因此,我们pg_database.datfrozenxid的值是1821(这是9.5版新数据库集群安装后的初始值)。Vacuum_freeze_table_age是一个配置参数(默认为150,000,000)。

图6.4显示了一个具体的例子。在Table_1中,Tuple_1和Tuple_7都被移除。Tuple_10和Tuple_11已经插入到第二页。当执行VACUUM命令时,当前txid为150,002,000,并且不存在其他事务。因此,OldestXmin为150,002,000,而freezeLimit txid为100,002,000。在这种情况下,上述条件得到满足,因为:

1821<(150002000−150000000)

因此,冻结处理以eager模式执行,如下所示。

(请注意,这是9.5或更早版本的行为;最新的行为将在章节6.3.3中描述。)

图6.4. 以eager模式冻结旧元组(9.5版或更早版本)。

第0页:

即使所有元组已经冻结,Tuple_2和Tuple_3仍会被检查,

第1页:

因为所有的t_xmin值都小于freezeLimit txid,所以该页中的三个元组已经被冻结。注意,该页面在惰性模式下被跳过。

第2页:

Tuple_10被冻结。Tuple_11没有。

在冻结每个表之后,目标表的pg_class.relfrozenxid被更新。pg_class是一个系统目录,每个pg_class.relfrozenxid保存对应表的最新冻结xid。在这个例子中,Table_1的pg_class。relfrozenxid被更新为当前的freezeLimit txid(即100,002,000),这意味着所有在Table_1中t_xmin小于100,002,000的元组都被冻结。

在完成VACUUM之前,pg_database.datafronzenxid也可能会被更新。每个pg_database.datfrozenxid列保存着相应数据库里最小的pg_class.relfrozenxid。

例如,如果只有Table_1在eager模式下被冻结,那么pg_database.datfrozenxid不会被更新。因为这个数据库中其他的表的pg_class.relfrozenxid还没有被更新。如果数据库中的所有表都以eager 模式冻结,那么pg_database.datfrozenxid会被更新。

Freeze命令的可选项

带有冻结选项的VACUUM命令将强制冻结指定表中的所有txids。这是在eager模式下执行的;但是,freezeLimit被设置为OldestXmin(而不是' OldestXmin - vacuum_freeze_min_age ')。例如,当txid 5000执行VACUUM FULL命令时,没有其他正在运行的事务,OldesXmin将设置为5000,小于5000的txid将被冻结。

6.3.3. 改进eager模式下的Freeze处理

9.5版本或更早版本中的eager模式效率不高,因为它总是扫描所有页面。例如,在第6.3.2节的示例中,即使第0页中的所有元组都被冻结,仍然会扫描第0页。

为了解决这个问题,9.6版本改进了VM和冻结进程。如6.2.1节所述,VM有关于是否所有元组在每个页面中都被冻结的信息。当以eager模式执行冻结处理时,可以跳过只包含冻结元组的页面。

图6.6显示了一个示例。冻结该表时,根据VM的具体信息跳过第0页。冻结第一页后,由于该页的所有元组都已冻结,关联的VM信息会更新。

图6.6. 在eager模式下冻结旧元组(9.6或更高版本)。


6.4. 删除不必要的Clog文件

在5.4节中描述的clog存储事务状态。当pg_database.datfrozenxid更新后,PostgreSQL尝试删除不必要的clog文件。请注意,相应的clog页面也被删除。

图6.7显示了一个示例。如果最小的pg_database.datfrozenxid包含在阻塞文件“0002”中,可以删除较旧的文件(“0000”和“0001”),因为存储在这些文件中的所有事务都可以作为整个数据库集群中冻结的txid处理。

图. 6.7. 删除不必要的clog文件


6.5. Autovacuum

VACUUM已经通过autovacuum守护进程实现了自动化;因此,PostgreSQL的操作变得非常简单。

autovacuum守护进程定期调用几个autovacuum_worker进程。默认情况下,它每1分钟唤醒一次(由autovacuum_naptime定义),并调用三个worker(由autovacuum_max_works定义)。

由autovacuum调用的autovacuum工作器对各个表并行地逐步执行VACUUM处理,而对数据库活动的影响最小。


6.6. Full VACUUM

虽然并行VACUUM对操作是必要的,但它是不够的。例如,即使删除了许多无用的元组,它也不能减少表的大小。

图6.8展示了一个极端的例子。假设一个表包含3个页面,每个页面包含6个元组。使用DELETE命令删除元组,使用VACUUM命令删除死元组:

图. 6.8. 一个展示(并行)VACUUM缺点的例子。

死去的元组被移除;然而,表的大小并没有减少。这不仅浪费磁盘空间,而且对数据库性能有负面影响。例如,在上面的示例中,当读取表中的三个元组时,必须从磁盘加载三个页面。

为了处理这种情况,PostgreSQL提供了FULL VACUUM模式。图6.9显示了这种模式的轮廓。

图. 6.9. Full VACUUM 模式的轮廓.

(1)创建新表文件:图6.9(1)

当对表执行VACUUM FULL命令时,PostgreSQL首先获取表的AccessExclusiveLock锁,并创建一个大小为8 KB的新表文件。AccessExclusiveLock锁不允许访问。

(2)复制活元组到新表:图6.9(2)

PostgreSQL只将旧表文件中的活元组复制到新表中。

(3)删除旧文件,重建索引,更新statistics, FSM, VM:图6.9(3)

复制完所有的活元组后,PostgreSQL删除旧文件,重建所有相关的表索引,更新该表的FSM和VM,更新相关的统计信息和系统目录。

FULL VACUUM的伪代码如下图所示:​​​​​​​

(1)  FOR each table(2)       Acquire AccessExclusiveLock lock for the table(3)       Create a new table file(4)       FOR each live tuple in the old table(5)            Copy the live tuple to the new table file(6)            Freeze the tuple IF necessary            END FOR(7)        Remove the old table file(8)        Rebuild all indexes(9)        Update FSM and VM(10)      Update statistics            Release AccessExclusiveLock lock       END FOR(11)  Remove unnecessary clog files and pages if possible

使用VACUUM FULL命令时应该考虑两点。

  • 当执行FULL VACUUM处理时,没有人可以访问(读/写)表(阻塞读写)。

  • 临时使用的磁盘空间最多为表的两倍;因此,在处理大表时,有必要检查剩余磁盘容量。

PostgreSQL中的VACUUM相关推荐

  1. postgresql中COPY的用法

    一.测试创建表: [postgres@cacti ~]$ cat test.sql CREATE TABLE weather ( city varchar(80), temp_lo int, temp ...

  2. PostgreSQL 中的系统字段:tableoid、xmin、xmax、cmin、cmax、ctid

    文章目录 tableoid ctid xmin xmax cmin cmax oid 总结 大家好!我是只谈技术不剪发的 Tony 老师.今天我们来谈谈 PostgreSQL 数据表中几个隐藏的系统字 ...

  3. Postgresql杂谈 16—Postgresql中的锁机制

    今天,我们学习下Postgresql中的锁机制.锁是数据库事务的基础,通过锁才能保证数据库在并发时能够保证数据的安全和一致,才能够达到事务的一致性和隔离性.但是任何事物都有它的两面性,引入锁同样会增加 ...

  4. 论PostgreSQL中的各种“xid”

    论PostgreSQL中的各种"xid" ​ 在分析PostgreSQL 9.6.9(下文称PG)的多版本(MVCC)问题时,经常打交道的就是Snapshot了,而Snapshot ...

  5. PostgreSQL中的表锁

    本文转自PostgreSQL中文社区,文章链接:https://www.modb.pro/db/26462 PostgreSQL的并发控制以**快照隔离(SI)为主,以两阶段锁定(2PL)**机制为辅 ...

  6. PostgreSQL中的MVCC机制

    MVCC,Multi-Version Concurrency Control,多版本并发控制. 一句话讲,MVCC就是用同一份数据临时保留多版本的方式,实现并发控制.它可以避免读写事务之间的互相阻塞, ...

  7. PostgreSQL中的索引—5(GiST)上

    在之前的文章中,我们讨论了PostgreSQL索引引擎.访问方法的接口,以及两种访问方法:哈希索引和B树.在本文中,我们将描述GiST索引. GiST GiST是"广义搜索树"的缩 ...

  8. PostgreSQL中的数据库实例、模式、用户(角色)、表空间

    2019独角兽企业重金招聘Python工程师标准>>> 本文参考:http://blog.csdn.net/kanon_lgt/article/details/5931522 htt ...

  9. 插入,在PostgreSQL中重复更新吗?

    本文翻译自:Insert, on duplicate update in PostgreSQL? Several months ago I learned from an answer on Stac ...

最新文章

  1. weblogic项目java.sql.SQLException: ORA-01861: 文字与格式字符串不匹配 at oracle.jdbc.....错误解决
  2. 关于Laravel中使用response()方法调用json()返回数据unicode编码转换的问题解决
  3. h5首页加载慢_H5网站好不好?
  4. mssql 计划怎每隔n秒_前端:调你一个接口6秒还配资深工程师?后端:有24部分需要处理!...
  5. 【最全干货下载】| DTCC 2020:阿里云数据库9大要点精彩回顾
  6. REVERSE-COMPETITION-GeekChallenge2021
  7. 在线WGCNA分析 (直接出交互式结果报告)
  8. 怎样才能在自动驾驶任务中高效地利用时间轴上的信息进行视频检测?
  9. 【Qt】qt库结构及示例
  10. 淘宝直播连续3年增速150%以上 一年喊了2.27亿句“宝宝”
  11. 如何巧用区块链密码学避免数据“裸奔”?
  12. Repeater控件的
  13. ap漫游测试软件,AC+AP方案选择,TP无缝漫游强过UBNT?胖AP如何实现802.11r?
  14. 一文看懂多模态大型语言模型GPT-4
  15. 狗狗的年龄的python编程_狗狗与人年龄换算表,终于知道狗狗多大了!
  16. LAN-Cruising
  17. 常用颜色16进制、3原色对照
  18. 资本寒冬,毕业不满一年被裁,失业后我们如何自渡?
  19. REDIS-雪崩、击穿、穿透
  20. html5入门 epub,分享《HTML5与CSS3基础教程(第7版) 》(图灵程序设计丛书) pdf epub azw3格式...

热门文章

  1. 【转】一个B2C网站性能测试需求分析
  2. Mac是不是基于Linux系统开发?
  3. 省赛选拔-A 警察抓小偷
  4. oracle pns配置,Oracle
  5. 出海的成本越来越高,奈何
  6. MySQL协议和canal实现
  7. 蓝桥杯题目练习(旅行家的预算)
  8. 浅谈C语言中数组理解
  9. 简单电子病历功能设计
  10. 数字化转型:核心架构、重要价值及实现路径