PostgreSQL 中系统表 pg_attribute 膨胀现象

本文探讨了PostgreSQL系统表pg_attribute在存储表列信息时可能出现的膨胀现象。通过创建自定义函数展示表信息,创建并删除表的示例,揭示了删除表或schema操作如何导致pg_attribute表的膨胀。执行VACUUM FULL可以解决这个问题,将膨胀的空间返还给操作系统。在生产环境中忽视这个问题可能导致数据库大小与实际数据不符。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

说明
   PostgreSQL 一览表 pg_attribute存储关于表列的信息,数据库中每张表中的行都会对应在该系统表 pg_attribute 中。既然存储的是数据库中表字段相关的信息,那么对表所做的修改都会通过该表记录。如创建表指定的列,修改表,修改表的数据类型等等。
创建自定义函数查看某张表的信息


postgres=# CREATE OR REPLACE FUNCTION f_get_table_column_info(varchar,varchar)
postgres-# RETURNS TABLE
postgres-# (
postgres(# 模式名称             varchar,
postgres(# 表名称               varchar,
postgres(# 表所属表空间          varchar,
postgres(# 表对应列名称          varchar,
postgres(# 表对应列数据类型oid   oid,
postgres(# 表对应列顺序编号      integer,
postgres(# 标识列               text
postgres(# )
postgres-# AS
postgres-# $FUNCTION$
postgres$# SELECT a.schemaname,
postgres$#        a.tablename,
postgres$#        a.tablespace,
postgres$#        d.attname,
postgres$#        d.atttypid,
postgres$#        d.attnum,
postgres$#        d.attidentity
postgres$# FROM pg_tables a,
postgres$#      pg_class b,
postgres$#      pg_attribute d
postgres$# WHERE b.oid = d.attrelid
postgres$#   AND a.tablename = b.relname
postgres$#   AND d.attrelid = $1::regclass
postgres$#   AND a.schemaname = $2
postgres$#   AND a.schemaname !~ 'pg_catalog|information_schema'
postgres$#   AND d.attname !~ 'xmin|xmax|cmax|cmin|ctid|tableoid';
postgres$# $FUNCTION$
postgres
<think>嗯,我现在要介绍一下PostgreSQL的TOAST技术以及相关的源码详解。首先,我需要回忆一下TOAST是什么。记得TOAST是The Oversized-Attribute Storage Technique的缩写,用来处理大字段数据存储的问题。当某个字段的数据太大,超过页面大小(通常是8KB)时,PostgreSQL会使用TOAST来压缩或者线外存储这些数据,以避免主的数据行过大导致性能问题。 那TOAST的具体机制是怎样的呢?可能包括压缩、线外存储等策略。比如,当插入或更新数据时,如果某个字段的数据超过了TOAST_TUPLE_THRESHOLD(通常是2KB),PostgreSQL会尝试压缩它。如果压缩后仍然太大,就会存储到TOAST中,原来的地方只保留一个指针。这样主的行就不会因为某个大字段而占用过多空间,保持高效访问。 接下来要考虑的是TOAST的四种策略:PLAIN、EXTENDED、EXTERNAL、MAIN。这些策略决定了如何处理大字段数据。例如,EXTENDED允许压缩和线外存储,而EXTERNAL只允许线外存储但不压缩,MAIN会先尝试压缩,如果还是太大再线外存储。PLAIN则不允许压缩或线外存储,所以只能用于那些不能使用TOAST的数据类型。 然后要深入源码部分。可能需要查看PostgreSQL源码中与TOAST相关的部分,比如src/backend/access/common/toast_internals.c,或者处理存储的相关模块。TOAST的结构是怎样的呢?每个TOAST都有chunk_id、chunk_seq和chunk_data字段,用于存储被分割的数据块。主中对应的TOAST指针会指向这些chunk。 在处理数据插入或更新时,源码中的函数如heap_toast_insert_or_update会被调用,检查数据是否需要TOAST处理。如果需要,会调用toast_save_datum等函数进行压缩或存储到TOAST。压缩可能使用LZ压缩算法,例如在toast_compress_datum函数中处理。 另外,查询时需要从TOAST中读取数据,这个过程对用户是透明的。当读取数据时,PostgreSQL会自动根据TOAST指针获取对应的数据块,并重新组装成原始数据。源码中可能会有toast_fetch_datum这样的函数来处理这个过程。 需要注意的是,TOAST技术虽然优化了存储,但也会带来一些开销,例如在存取时需要额外的处理步骤。此外,线外存储可能导致查询时需要访问多个TOAST的数据块,可能影响性能,尤其是在涉及大量大字段数据的查询时。 用户可能需要了解如何查看TOAST的信息。在PostgreSQL中,每个对应的TOAST名可以通过pg_class查看,关联的oid存在pg_class.reltoastrelid字段中。例如,查询SELECT relname, reltoastrelid FROM pg_class WHERE relname = '名'; 就可以找到对应的TOAST。 总结一下,TOAST技术通过压缩和线外存储有效管理大字段数据,源码实现涉及数据存储策略、压缩处理、TOAST的维护以及数据的透明存取。了解这些细节有助于优化数据库设计,合理选择数据类型和TOAST策略,以平衡存储效率和查询性能。</think>PostgreSQL的TOAST(The Oversized-Attribute Storage Technique)技术是用于高效管理大字段数据的核心机制。以下从技术原理和源码实现两个层面逐步解析: --- ### **一、TOAST技术原理** #### **1. 背景问题** - PostgreSQL默认数据页大小为8KB,若单行数据(含所有字段)超过此限制,会导致存储效率低下甚至失败。 - 大字段(如`TEXT`/`JSONB`)易超出限制,需特殊处理。 #### **2. TOAST核心机制** - **压缩**:优先尝试压缩数据(LZ算法)。 - **线外存储(Out-of-line)**:若压缩后仍过大,将数据拆分存入独立的TOAST,主仅保留指针。 #### **3. 四种存储策略** | 策略 | 压缩 | 线外存储 | 适用场景 | |-----------|------|----------|------------------------------| | `PLAIN` | 否 | 否 | 小字段(如`INT`) | | `EXTENDED`| 是 | 是 | 默认策略,平衡存储与性能 | | `EXTERNAL`| 否 | 是 | 需快速读取的大字段(如`BLOB`)| | `MAIN` | 是 | 否 | 优先压缩,仅无法压缩时线外存储 | --- ### **二、源码实现解析** #### **1. 关键代码路径** - **TOAST结构**:每个对应一个TOAST(`pg_toast_<主OID>`),结构如下: ```c // src/include/catalog/pg_toast.h typedef struct ToastTableData { Oid toastrelid; // TOASTOID Oid toastindexid; // 索引OID (chunk_id + chunk_seq) } ToastTableData; ``` - **TOAST条目存储**:数据被分块存储为`chunk_id`(唯一标识)、`chunk_seq`(顺序)、`chunk_data`(实际数据)。 #### **2. 核心函数** - **数据插入/更新处理**: ```c // src/backend/access/common/heaptuple.c void heap_toast_insert_or_update(Relation rel, HeapTuple newtup, ...) { // 检查是否需要TOAST处理 if (HeapTupleHasExternal(&newtup->t_data->t_choice.t_datum)) { toast_save_datum(rel, newtup, ...); // 触发线外存储 } } ``` - **压缩逻辑**: ```c // src/backend/access/common/toast_internals.c static void toast_compress_datum(Datum value, ...) { // 使用LZ压缩算法 compressed_data = pglz_compress(DatumGetPointer(value), ...); if (compressed_size < original_size) { // 替换为压缩后数据 } } ``` #### **3. TOAST指针结构** 主中存储的指针包含TOAST信息: ```c // src/include/access/heapam.h struct varatt_external { int32 va_rawsize; // 原始数据大小 uint32 va_toastrelid; // TOASTOID uint32 va_toastpos; // 位置信息(chunk_id + chunk_seq) }; ``` --- ### **三、性能影响与优化** 1. **优势**: - 减少主膨胀,提升扫描效率。 - 透明存取,对应用无感知。 2. **代价**: - 线外存储增加IO次数(需读取多块`chunk_data`)。 - 更新大字段可能触发TOAST重组,产生写放大。 --- ### **四、实践建议** - **监控TOAST**: ```sql SELECT relname, reltoastrelid FROM pg_class WHERE relname = 'your_table'; ``` - **策略选择**:对大字段显式指定策略: ```sql ALTER TABLE your_table ALTER COLUMN big_col SET STORAGE EXTERNAL; ``` --- ### **总结** TOAST通过压缩和线外存储解决大字段存储难题,其源码实现围绕数据分块、压缩和指针管理展开。合理选择存储策略可显著优化数据库性能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值