Linux 上谈「大页」时,实际涉及 两套互不相同的机制:
- 标准大页(Explicit Huge Pages / Hugetlbfs):管理员预留、应用显式申请。
- 透明大页(Transparent Huge Pages,THP):内核在后台自动把 4 KB 页合并为 2 MB 页。
PostgreSQL 与 MySQL 的文档和最佳实践都围绕这两套机制展开,但适用场景不同。下文按 标准大页 → 透明大页 → 两者对比 → MySQL 适用 的顺序说明。
一、PostgreSQL 标准大页(Hugetlbfs)
1.1 为什么 PostgreSQL 特别需要标准大页
Linux 默认内存页大小为 4 KB。PostgreSQL 把 shared_buffers 以及 WAL、锁表等组件放在 共享内存段 里;每个 backend 进程启动时,都要把这段共享内存 映射进自己的虚拟地址空间。
映射越大,内核要维护的 页表(Page Table) 越多。粗算(64 位、4 KB 页,教材常用 每页约 8 字节 页表开销):
- 单进程映射 24 GB
shared_buffers:
(24G / 4K) × 8B ≈ 48 MB页表/进程 - 500 个 backend(一连接一进程):
500 × 48 MB ≈ 24 GB页表(量级上可与 shared_buffers 相当)
页表不是连接瞬间一次性占满,而是访问共享页时 逐步分配;跑久了仍可能涨到很大,表现为 MemAvailable 莫名下降、甚至 OOM。
标准大页把单页从 4 KB 提升到 2 MB 或 1 GB,同样 24 GB 映射所需页表项数量大约按页大小成比例下降(相对 4 KB 常差 两个数量级以上)。这是 PostgreSQL 在 Linux 上 优先推荐标准大页 的根本原因:多进程 × 大共享段 会把页表压力放大到连接数级别。
1.2 标准大页是什么
| 项 | 说明 |
|---|---|
| 内核机制 | Hugetlbfs,通过 vm.nr_hugepages 等在启动前 预留 固定大小的「大页池」 |
| 常见页大小 | 2 MB(Hugepagesize: 2048 kB);部分机器支持 1 GB |
| 分配特点 | 先预留、后使用;池子大小固定,预留不足时 PostgreSQL 可能 启动失败(取决于 huge_pages 参数) |
| PostgreSQL 侧 | 共享内存段尽量从 Hugetlb 池分配;由参数 huge_pages 控制 |
标准大页 不是 内核在运行时自动合并出来的,而是 OS 管理员 + 数据库实例启动时 共同完成的显式分配。
1.3 PostgreSQL 参数
postgresql.conf 中的 huge_pages:
| 值 | 行为 |
|---|---|
try(常见默认) |
能用大页就用,不够则退回 4 KB |
on |
必须用足大页,否则 拒绝启动 |
off |
不使用大页 |
huge_page_size(可选):指定 2MB 或 1GB,需与 OS 预留的大页尺寸一致。
1.4 容量计算与 OS 预留
PostgreSQL 官方文档 18.4.5 Linux Huge Pages 要求:在实例启动 之前 算清需要多少大页。
1)查 PostgreSQL 需要的大页个数
|
|
示例:shared_memory_size_in_huge_pages = 3170,Hugepagesize = 2048 kB → 需要 3170 个 2 MB 大页(约 6.2 GB 大页池)。
注意:这个数字覆盖 整段 PostgreSQL 共享内存,不只 shared_buffers,还包括 WAL buffers、锁空间等。
2)在 OS 上预留 2 MB 大页
|
|
写入 /etc/sysctl.conf 可持久化。若因内存碎片一次分配失败,可 重试或在维护窗口重启后再设(重启后普通页释放,更容易凑出连续大页)。
3)1 GB 大页
设置 huge_page_size = 1GB 时,改用 hugepages-1048576kB 路径预留,并按 1 GB 重算页数。
4)其他常见配置
vm.hugetlb_shm_group:允许指定组使用 Hugetlb 共享内存(按环境需要)。ulimit -l(locked memory):部分环境需放宽,避免 mmap 大页失败。
1.5 验证是否生效
|
|
实例启动后对比 HugePages_Free 是否下降,并结合 shared_memory_size_in_huge_pages 与 PostgreSQL 日志确认共享段是否走大页。
二、透明大页(Transparent Huge Pages,THP)
2.1 透明大页是什么
透明大页(THP) 是 Linux 内核提供的 另一套 大页机制,目标对象是 普通应用程序:不改代码、不预留池子,也能在运行时获得 2 MB 大页带来的 TLB 收益。
与标准大页的关键不同:THP 对应用是「透明」的——进程仍按 4 KB 申请虚拟内存;内核在后台(主要是 khugepaged 等)扫描物理内存,把 连续、合适 的 4 KB 页 合并 成 2 MB 大页。内存紧张时,内核也可能 把大页拆回 4 KB。
2.2 THP 的工作方式(简化)
|
|
THP 的 合并时机、拆分时机 由内核策略决定,应用 无法精确控制。
2.3 查看与配置 THP
|
|
enabled 常见取值:
| 值 | 含义 |
|---|---|
[always] |
积极为匿名/文件映射创建 THP |
[madvise] |
仅对 MADV_HUGEPAGE 标记的区域尝试 THP |
[never] |
不创建 THP |
defrag 控制合并/整理策略(如 always、defer、madvise、never),同样影响 何时、是否 在后台做页合并。
许多发行版默认 开启或半开启 THP,数据库主机上需要 主动确认,不能假设已是 never。
2.4 生产环境为何常关闭 THP
THP 的设计目标是 通用 workload 的自动化优化,不是为 大块预分配、长期驻留、低延迟 的数据库共享内存模型量身定制。常见问题包括:
- 后台合并/拆分与业务争用 CPU,可能造成 延迟尖刺(p99 变差)。
- 合并时机不可预测,与 Buffer Pool /
shared_buffers这类 启动时一次性分配、长期不变 的内存模式不匹配。 - 内存压力下拆大页,可能触发 额外 stall,在高压下表现为偶发慢查询或 checkpoint 抖动。
因此:不是「大页本身不好」,而是 「自动、不可控的 THP」 不适合作为数据库的默认大页方案。这一点在 MySQL 官方文档中表述尤其明确;PostgreSQL 侧也通常 在启用标准大页的同时关闭 THP,避免两套机制互相干扰。
2.5 关闭透明大页(Linux)
|
|
重启后仍生效需写入 udev / systemd / grub 等启动脚本,具体方式因发行版而异。数据库专用主机建议偏向 never,而不是依赖 madvise「碰运气」。
三、标准大页与透明大页:区别与不同
两者名字都带「大页」,但 内核路径、控制方式、数据库适配度 完全不同。对照如下:
| 维度 | 标准大页(Hugetlb / 显式) | 透明大页(THP) |
|---|---|---|
| 本质 | 独立的大页池(Hugetlbfs) | 在普通 4 KB 页之上的 自动合并 |
| 谁负责 | 管理员 预留 nr_hugepages;应用 显式请求 |
内核 khugepaged 等后台线程 |
| 应用是否感知 | 需要(PostgreSQL 设 huge_pages) |
无感,默认可能已开 |
| 页大小 | 2 MB / 1 GB,固定 | 多为 2 MB 合并页 |
| 容量规划 | 可事先用 shared_memory_size_in_huge_pages 算清 |
不可精确规划,随运行时合并变化 |
| 不足时行为 | huge_pages=on → 启动失败;try → 退回 4 KB |
静默保持 4 KB 或 延迟合并 |
| 运行时行为 | 无 khugepaged 式后台合并/拆分 | 可能 异步合并、压力拆页 → 延迟抖动 |
| 可预测性 | 高(池子固定、行为可复现) | 低(合并时机依赖负载与内核策略) |
| 与数据库共享内存 | PostgreSQL 官方推荐在 Linux 尝试/启用 | PostgreSQL / MySQL 生产实践 多建议关闭 |
一句话区分:
- 标准大页 = 「先圈地,再入住」——管理员划好固定池子,PostgreSQL 启动时明确入住。
- 透明大页 = 「先住 4 KB 单间,保洁员半夜帮你并成套房」——合并时机你说了不算。
能否互相替代?
不能。在 PostgreSQL 上 不能 用「开着 THP、不设 Hugetlb」来替代 huge_pages + nr_hugepages:THP 解决不了 500 个进程各自维护超大页表 的核心问题,还可能引入延迟抖动。正确做法是:PG 侧用标准大页解决页表与 TLB;同时关 THP 避免干扰。
四、MySQL 中的体现与适用
MySQL(InnoDB)与 PostgreSQL 的进程模型不同,因此 同一套大页机制,收益与优先级也不同。
4.1 架构差异:页表压力从哪来
| PostgreSQL | MySQL(InnoDB) | |
|---|---|---|
| 架构 | 一连接一进程,各映射 整段 shared_buffers | 单 mysqld 多线程,一份 地址空间映射 Buffer Pool |
| 页表压力 | 连接数 × 每进程页表(上文 500×48 MB 场景) | 主要是 单进程 映射超大 Buffer Pool,不随连接数倍增 |
| 大页首要矛盾 | 页表 + TLB(多进程重复映射) | THP 带来的延迟抖动(其次才是单进程大池的 TLB) |
因此:标准大页对 PostgreSQL 往往是「值得规划的容量项」;对 MySQL 则 「先关 THP」几乎是标配,「上标准大页」是进阶可选项。
4.2 透明大页在 MySQL 中的体现
MySQL 8.0 InnoDB Troubleshooting 明确建议:在 Linux 上 禁用透明大页,由 InnoDB 自己管理 Buffer Pool 中的页,而不是让内核异步合并。
原因归纳:
- InnoDB Buffer Pool 在启动时 预分配大块内存 并长期驻留,与 THP 延迟合并 模型冲突。
khugepaged的合并/拆分会与 刷脏、checkpoint、IO 线程 竞争 CPU,表现为 吞吐或延迟不稳定。- 部分版本启动时会对 THP 配置 打印警告,提示可能影响性能。
运维检查:
|
|
MySQL 对 THP 的适用结论: 生产环境 关闭(never);不要指望 THP 替代任何 Buffer Pool 调优。
4.3 标准大页在 MySQL 中的体现
MySQL 可以 在 Linux 上配合 Hugetlb 使用标准大页,让 InnoDB Buffer Pool 尽量从 2 MB / 1 GB 大页池 分配,以降低 单进程 映射超大池时的 TLB 压力。但:
- 社区与官方 默认叙事 仍是:关 THP + 调好
innodb_buffer_pool_size与实例内存。 - 启用标准大页需要 OS 预留足够大页(按
innodb_buffer_pool_size及其实际分配路径估算,步骤类似 PostgreSQL,但无shared_memory_size_in_huge_pages这样的一站式 GUC)。 - 是否与你的 MySQL 版本、分配方式(
mmap/shm等)完全匹配,需 查对应版本说明并压测验证。
MySQL 对标准大页的适用结论:
| 场景 | 建议 |
|---|---|
| 通用生产 | 关 THP;Buffer Pool 设为物理内存合理比例 |
| 超大 Buffer Pool(如数百 GB)且 TLB miss 明显 | 可评估 Hugetlb 标准大页,需 OS 预留 + 压测 |
| 仅开 THP、不做标准大页 | 不推荐 |
不建议的做法: 在 MySQL 上 保持 THP 为 always,却指望它替代标准大页或 Buffer Pool 调优——这是两套机制里 最不可控 的一种组合。
4.4 PostgreSQL 与 MySQL 对照小结
| 主题 | PostgreSQL | MySQL |
|---|---|---|
| 页表放大来源 | 多进程 × 大 shared_buffers | 单进程大 Buffer Pool |
| 标准大页 | 推荐(huge_pages=try/on + nr_hugepages) |
可选(超大池、有压测数据时) |
| 透明大页 | 与标准大页 并行存在但应关闭 | 明确不建议 |
| 运维优先级 | 算清大页池 → 启 PG 标准大页 → 关 THP | 先关 THP → 调 Buffer Pool → 再考虑标准大页 |
参考
- PostgreSQL:18.4.5 Linux Huge Pages
- Linux:
Documentation/admin-guide/mm/hugetlbpage.rst - MySQL 8.0:InnoDB Troubleshooting(Transparent huge pages 相关说明)