Skip to content

存储模型

堆表、页面与元组、TOAST 机制、与 MySQL 聚簇索引存储的对比。

Updated View as Markdown
For humans

存储模型

PostgreSQL 的存储是“堆表 + 索引 + TOAST”的组合,和 MySQL InnoDB 的聚簇索引组织有根本差异。理解堆表,才能理解为什么 PG 的索引结构长这样、为什么 UPDATE 会产生死元组。

堆表:行的无序集合

PG 的表是堆(heap):行按插入顺序追加存放,不按主键有序。每张表物理上对应三个文件:

文件 作用
主文件 实际的行数据
_fsm 空闲空间映射,记录每个页的空闲空间,插入时快速找页
_vm 可见性映射,标记哪些页全部可见,加速 VACUUM 和仅索引扫描

基本单位是 8KB 的页(page)。行在页内的位置用 CTID(页号 + 行指针) 标识,类似物理地址。

与 MySQL 的关键差异

维度 PostgreSQL MySQL InnoDB
表组织 堆表,数据按插入顺序 聚簇索引,数据按主键有序
二级索引叶子 存 CTID(物理指针) 存主键值
主键变更 不影响数据位置,只改索引 移动数据行,代价高
更新方式 插入新版本元组(旧版本留待清理) 聚簇索引原地更新,undo log 记旧值

PG 二级索引存 CTID,回表是按 CTID 一次定位堆页;只查索引列时靠 Index-Only Scan(依赖可见性映射)完全免回表。MySQL 二级索引存主键,回表是二次 B+ 树查找。但 PG 的 CTID 会随 UPDATE 变化,索引需要维护。

元组与 MVCC

每行(元组)头部带隐藏字段:xmin(插入该版本的事务 ID)、xmax(删除或锁定该版本的事务 ID)、ctid(版本链指针)。UPDATE 不是原地修改,而是插入新版本并更新旧版本的 xmax 和 ctid,旧版本留在页里成为死元组,直到 VACUUM 清理。

TOAST:处理超大字段

PG 的元组不能跨页存储(8KB 页的限制)。当一行超过约 2KB 时,TOAST(The Oversized-Attribute Storage Technique)介入:

  1. 先压缩(默认 EXTENDED 策略,LZ 压缩)
  2. 压缩后仍超阈值,切成块存入专门的 TOAST 表,主表元组里只留指针

收益:避免跨页存储;查询只选非 TOAST 列时完全跳过 TOAST 表,不用读大字段;压缩对应用透明。存储策略可用 SET STORAGE 调整:PLAIN 禁止 TOAST、EXTENDED 压缩加外存(默认)、EXTERNAL 只外存不压缩、MAIN 优先压缩。

面试追问

  1. 堆表和聚簇索引的区别? 堆表数据按插入顺序存放不按主键有序,二级索引存 CTID;聚簇索引数据按主键组织,二级索引存主键值
  2. CTID 是什么? 行的物理位置(页号加行指针)。UPDATE 会改变,不能当持久业务标识
  3. TOAST 什么时候触发? 行超过约 2KB(页 8KB 的四分之一)。先压缩再外存,主表只留指针
  4. 为什么 PG 的 UPDATE 会产生死元组? MVCC 机制:更新插入新版本,旧版本留在页里等 VACUUM。这是 PG 与 MySQL undo log 方案的根本差异
  5. FSM 和 VM 干什么? FSM 记录页空闲空间加速插入找页,VM 标记全可见页加速 VACUUM 和仅索引扫描
Navigation

Type to search…

↑↓ navigate↵ selectEsc close