PostgreSQL block_size:那个你改不了的参数,却决定了存储的一切

2026-05-18 38 预计阅读时间:1 分钟
来源:postgr.es AI 摘要 原文链接

免责声明:本文为 AI 摘要整理,建议结合原文阅读。摘要可能省略上下文、版本差异或边界条件,不作为官方说明。

预计阅读时间:8 分钟

在 PostgreSQL 的配置参数海洋里,有一类参数安静地躺在文档的 "Preset Options" 区域——它们从集群初始化那一刻起就写定了,运行时看得到,改不了。block_size 就是其中最值得深挖的一个。它告诉你 PostgreSQL 的一个数据页(page)有多大,默认 8192 字节,也就是 8KB。这个数字看似平淡,但它从磁盘布局到缓冲池管理、从索引分裂到行溢出策略,几乎牵动着存储引擎的每一条神经。

8KB 页面:PostgreSQL 存储的原子单位

PostgreSQL 把表文件拆成一个个 page,每个 page 的大小就是 block_size。一个 page 既是磁盘 I/O 的最小传输单位,也是 shared_buffers 里一个 buffer slot 的容量单位。这意味着:

  • 读取一行数据,至少要把包含这行的整个 page 从磁盘拉进缓冲池。
  • 一个 page 里能塞多少行,取决于行宽和 page 内的元数据开销(Line Pointer、PageHeader 等)。
  • 当行太大塞不进一个 page 时,PostgreSQL 会走 TOAST 机制,把大字段的外部存储切成同样大小的 chunk。

这 8KB 不是随便选的。它是一个在 I/O 效率和空间利用率之间反复权衡的结果——太小则每页能装的行少、索引层级深;太大则一次 I/O 拖回太多无关数据,缓冲池浪费严重。

block_size 的"亲戚们":Preset Options 全家福

block_size 并不是孤例。在同一个 Preset Options 分组里,还有几个同样只读的参数,它们共同描绘了集群初始化时定下的硬件级约定:

参数 含义 典型值
block_size 数据页大小 8192
wal_block_size WAL 页大小 8192
data_checksums 是否开启数据页校验 on/off
server_version 服务器版本号 17.x 等

你可以随时查询它们,但 ALTER SYSTEM 对它们无效。这些值要么在 initdb 时编译进去,要么由编译选项(如 --with-blocksize)决定,一旦集群创建完成,就是铁律。

下面这条 SQL 可以一次性列出所有 preset 参数:

-- 查看所有 Preset Options 类参数
SELECT name, setting, category, short_desc
  FROM pg_settings
 WHERE category = 'Preset Options'
 ORDER BY name;

运行结果示例(PostgreSQL 17):

       name        | setting |        category        |           short_desc
-------------------+---------+------------------------+----------------------------------
 block_size        | 8192    | Preset Options         | Shows the size of a disk block.
 data_checksums    | on      | Preset Options         | Shows whether data checksums are enabled.
 integer_datetimes | on      | Preset Options         | Datetimes are stored as 64-bit integers.
 max_function_args | 100     | Preset Options         | Shows the maximum number of function arguments.
 max_identifier_length | 63  | Preset Options         | Shows the maximum identifier length.
 server_version    | 170000  | Preset Options         | Shows the server version.
 server_version_num | 170000 | Preset Options         | Shows the server version as an integer.
 wal_block_size    | 8192    | Preset Options         | Shows the size of a WAL disk block.

8KB 页面如何影响你的日常操作

理解 block_size 不只是"知道一个数字",它直接影响你能观察到的若干现象。

行溢出与 TOAST 阈值

一个 page 扣除 PageHeader(24 字节)和 Line Pointer 数组后,留给行数据的可用空间大约是 8192 − 24 − (4 × 行数)。PostgreSQL 的规则是:如果一行数据的原始大小超过约 2KB(约 page 的 1/4),就会触发 TOAST——先尝试压缩,压缩后仍超限则把大字段值存到单独的 TOAST 表里,主表只留一个指针。

这意味着你设计宽表时,如果一行有多个 TEXT/VARCHAR 列且经常同时写入大值,TOAST 的额外 I/O 会成为性能隐患。

索引分裂频率

B-tree 索引的每个节点也是一个 page。8KB 的 page 决定了每个索引节点能装多少个 key+tuple pointer。key 越宽,每个节点能容纳的条目越少,索引分裂越频繁,树越深。对于 UUID 索引(16 字节 key)vs 整数索引(4 字节 key),前者的扇出明显更低,这是 block_size 在背后起作用。

实际观察:一个 page 里能塞多少行

你可以用 pg_relation_size 结合行数估算来验证:

-- 创建一个窄行测试表
CREATE TABLE narrow_rows (id int, val text);

-- 插入 10000 行短数据
INSERT INTO narrow_rows
  SELECT g, md5(g::text)
    FROM generate_series(1, 10000) AS g;

-- 查看表占用的 page 数
SELECT pg_relation_size('narrow_rows') / current_setting('block_size')::int AS pages,
       count(*) AS rows,
       round(count(*)::numeric /
             (pg_relation_size('narrow_rows') / current_setting('block_size')::int), 1)
             AS rows_per_page
  FROM narrow_rows;

典型结果:8KB page 下,这种窄行大约每页能装 80+ 行。如果你把 val 换成更长的文本,rows_per_page 会迅速下降——这就是 block_size 在"收税"。

能改 block_size 吗?理论上可以,实际别碰

PostgreSQL 编译时提供了 --with-blocksize 选项,允许你把 page 大小设为 4KB、16KB 甚至 32KB。但:

  • 改了之后,所有工具(pg_dump、pg_upgrade、流复制)都必须匹配同一个 block_size,生态兼容性立刻断裂。
  • 大部分扩展和第三方备份工具假设 block_size = 8192,硬编码在二进制里。
  • 性能收益并不确定:16KB page 在顺序扫描时可能更好,但随机读写和缓冲池利用率可能变差。
  • 官方文档明确标注此参数为 "Preset",不是运行时调优参数。

结论:除非你在做嵌入式/特殊硬件的深度定制,并且愿意承担完整的生态隔离成本,否则不要动它。8KB 是 PostgreSQL 社区的事实标准。

实用检查清单

在日常运维和架构设计中,把 block_size 当作一个"已知常数"来用,而不是一个需要调优的变量:

  1. 估算表页数pg_relation_size(relid) / 8192,快速判断表是否大到需要分区。
  2. 评估索引深度:key 越宽,8KB page 里条目越少,三层 B-tree 能覆盖的行数越少。宽 key 场景考虑 hash 索引或 BRIN。
  3. TOAST 意识:行宽超过 ~2KB 就会触发外部存储,查询时多一次随机 I/O。宽表设计时优先拆字段或用 JSONB 压缩。
  4. vacuum 碎片理解:一个 page 里死行占比达到 vacuum_threshold 才触发自动 vacuum,8KB page 下几条死行可能不够触发——小表反而容易膨胀。
  5. 迁移前确认pg_config --configure 的输出里如果出现非默认 --with-blocksize,目标集群必须完全一致,否则 pg_upgrade 直接报错。
# 快速确认当前集群的 block_size 和编译选项
psql -c "SHOW block_size;"
pg_config --configure

8KB 是 PostgreSQL 给你画好的格子线。你不需要移动它,但你需要知道每条线在哪里,才能把数据摆得又密又快。


相关推荐