衡量和解决表和索引膨胀问题

当清理操作无法跟上针对表的数据操纵语言 (DML) 操作的速率时,就会出现表和索引膨胀。有些表和索引膨胀是不可避免的,并且通常可以接受,因为事务可以重复使用该空间。严重膨胀会导致性能问题,例如查询时间变长、I/O 增加、共享缓冲区和缓存使用效率降低,以及存储费用增加。

估算内存膨胀的最佳方法是使用 pgstattuple 模块。不过,当扩展程序必须读取关系的所有页面时,使用 pgstattuple 的效率会降低。如果您无法使用 pgstattuple,可以使用其他脚本来估算膨胀,不过这种方法的准确性较低。

读取 pgstattuple 输出

当您使用 pgstattuple 报告表或索引的物理存储特征时,输出采用以下格式:

> SELECT * FROM pgstattuple('large_table');
-[ RECORD 1 ]------+-----------
table_len          | 7431888896
tuple_count        | 66666666
tuple_len          | 2796296271
tuple_percent      | 37.63
dead_tuple_count   | 0
dead_tuple_len     | 0
dead_tuple_percent | 0
free_space         | 3873153652
free_percent       | 52.12

以下内容介绍了输出:

table_len (7431888896)
表的大小总计(以字节为单位)。它包括所有活动元组、死元组和可用空间。在本例中,large_table 大约占用 7,088 GB。
tuple_count (66666666)
表示表中的有效元组(即可见且活跃的行)数量。该表包含大约 6,600 万个有效行。
tuple_len (2796296271)
所有实时元组的总长度(以字节为单位),约为 2.6 GB。
tuple_percent (37.63)

活跃元组占 table_len 的百分比。计算方法如下:

(tuple_len/table_len) ∗ 100 = (2796296271/7431888896) ∗ 100 ≈ 37.63%

此输出表明,活跃数据使用的磁盘空间不到表的 40%。

dead_tuple_count (0)

无效元组的数量 - 在更新或删除后,清理操作尚未移除的行。值为 0 表示相应表是干净的,没有等待回收的无效元组。这通常表明 VACUUM(即自动清理)在表上高效运行。

dead_tuple_len (0)

所有无效元组的总长度(以字节为单位)。与 dead_tuple_count 一致,值为 0 表示失效行不占用任何空间。

dead_tuple_percent (0)

不活跃元组所占的 table_len 百分比。计算方法如下:

(dead_tuple_len/table_len) ∗ 100 = (0/7431888896) ∗ 100 = 0%

free_space (3873153652)

此值表示表数据页中的可用空间量。您可以将此空间重新用于新的插入或更新,而无需扩展表。这表示有大量可用空间,约为 3.7 GB。

free_percent (52.12)

此值表示 table_len 的可用空间百分比。 计算方式如下:

(free_space/table_len) ∗ 100 = (3873153652/7431888896) ∗ 100 ≈ 52.12%

使用查询估算膨胀

您可以通过针对系统目录表运行查询来估算膨胀,不过此方法不如使用 pgstattuple 等扩展程序准确。如果您使用 PostgreSQL 表膨胀检查等脚本,则表统计信息必须是最新的,否则估计值不准确。

将查询的输出与 pgstattuple 模块进行比较时,估计值具有可比性,但并非膨胀的确切测量值。该模块仍然是衡量膨胀程度的更准确指标,因为 SQL 查询依赖于 PostgreSQL 的累积统计信息引擎,而该模块读取的是实际数据页。

以下示例比较了两种不同的 PostgreSQL 表膨胀分析方法针对名为 large_table 的表的输出。

> SELECT * FROM pgstattuple('large_table');
-[ RECORD 1 ]------+-----------
table_len          | 2475892736
tuple_count        | 22000000
tuple_len          | 922925931
tuple_percent      | 37.28
dead_tuple_count   | 0
dead_tuple_len     | 0
dead_tuple_percent | 0
free_space         | 1293696900
free_percent       | 52.25

Vs.

-[ RECORD 1 ]+------------
databasename | postgres
schemaname   | public
tablename    | large_table
can_estimate | t
est_rows     | 21965400
pct_bloat    | 54
mb_bloat     | 1269.24
table_mb     | 2361.195

地址膨胀

您可以通过以下方式管理膨胀,每种方式都有其优点和缺点,需要仔细规划:

  • 执行全面吸尘。此操作需要在清理期间锁定整个表,并且需要停机。
  • 将数据移至新表。若要使用此方法,您必须使表更改静止,以确保更改不会转移到新关系。
  • 使用 pg_squeeze。此扩展程序可缓解在线膨胀问题。该扩展程序会创建一个影子表,插入所有数据,然后使用复制槽维护表更改,直到最后一步,即用新创建的未膨胀的表替换膨胀的表。然后,它会请求一个简短的表锁定,以移动非膨胀表来替换膨胀表。此方法在操作期间需要两倍于对象的空间,但对正常操作的干扰最小。