Oracle 在 MySQL 9.7.0 中首次发布了 PGO(Profile-Guided Optimization)编译版本。Percona 随即用 Sysbench OLTP Read/Write 对 PGO 和非 PGO 构建做了系统对比,覆盖 2GB、12GB、32GB 三档 InnoDB buffer pool,线程数从 1 到 512。结论很直接:PGO 版本在大部分场景下更快,最大提升 14.3%,平均 6.5%,而且性能增益的来源不是缓存或锁优化,而是 CPU 指令层面的效率提升。
三档 Buffer Pool 下的具体表现
测试分三个 Tier,每个 Tier 用不同大小的 InnoDB buffer pool 模拟不同内存压力:
| Tier | Buffer Pool | 平均提升 | 最佳提升 | 回退情况 |
|---|---|---|---|---|
| 2G | 2 GB | 3.0% | 5.5%(4 线程) | 无 |
| 12G | 12 GB | 4.1% | 8.6%(4 线程) | 128 线程时 -3.1% |
| 32G | 32 GB | 12.2% | 14.3%(1 线程) | 无 |
几个值得注意的规律:
- 大内存场景收益最稳定。32GB Tier 在所有线程数下都保持 10.3%–14.3% 的提升,没有回退。这说明当内存充足、I/O 不构成瓶颈时,CPU 指令优化能充分释放。
- 低并发区间增益最大。1–4 线程时三个 Tier 都达到各自峰值,因为此时单线程的指令路径效率直接决定了 QPS。
- 小内存 + 高并发可能出现回退。12GB Tier 在 128 线程时出现了 -3.1% 的回退,512 线程时持平。PGO 优化的是"热路径"的指令布局,当并发压力改变代码执行频率分布,之前的优化可能不再对齐实际热点,甚至引入额外开销。
增益不是来自缓存或锁——是 CPU 指令层优化
Percona 对 InnoDB 内部指标做了深入对比,发现 PGO 和非 PGO 版本之间:
- Buffer pool hit ratio 几乎完全一致
- 锁竞争指标没有差异
- I/O 量随吞吐量等比例增长,没有因为 PGO 而减少读写次数
换句话说,PGO 并没有让 MySQL "少干活",而是让同样的工作量在 CPU 上执行得更高效。具体机制:
- 更好的分支预测:PGO 根据运行时 profile 标注分支的倾向性,编译器把大概率执行的分支放在顺序执行路径上,减少 CPU 分支预测失败。
- 更紧凑的指令缓存利用:热路径函数被重新排列,让它们落在同一个 cache line,减少 icache miss。
- 更精准的内联决策:编译器知道哪些函数调用频率高、体积小,优先内联它们,消除调用开销。
- 指令调度优化:根据实际执行频率重新排列指令顺序,减少流水线气泡。
这些优化叠加起来,就是 6.5% 的平均吞吐提升——同样的硬件、同样的数据、同样的 SQL,只是二进制文件的指令布局不同。
如何自己构建 PGO 版本的 MySQL
MySQL 9.7.0 的 BUILD.md 已经描述了 PGO 构建流程。核心思路是两阶段编译:先插桩编译 → 用典型负载跑 profile → 再用 profile 数据重新编译。下面是一个可以在 Linux 上直接执行的构建脚本框架:
#!/usr/bash
# MySQL 9.7.0 PGO 构建示例(基于 BUILD.md 流程)
# 前置条件:已安装 cmake, gcc/clang, make, 以及 MySQL 构建依赖
MYSQL_SRC=/path/to/mysql-9.7.0
BUILD_DIR=/tmp/mysql-pgo-build
PROFILE_DIR=/tmp/mysql-pgo-profiles
# ===== 第一阶段:插桩编译 =====
mkdir -p "$BUILD_DIR/stage1" && cd "$BUILD_DIR/stage1"
cmake "$MYSQL_SRC" \
-DCMAKE_BUILD_TYPE=Release \
-DWITH_PGO=GENERATE \
-DWITH_INNODB_BUFFER_POOL_SIZE=1G \
-DCMAKE_INSTALL_PREFIX=/usr/local/mysql-pgo
make -j$(nproc)
make install
# ===== 第二阶段:收集 Profile =====
# 启动插桩版本,用典型工作负载跑一段时间
/usr/local/mysql-pgo/bin/mysqld --defaults-file=my-pgo.cnf &
# 用 sysbench 模拟 OLTP 负载(与 Percona 基准测试方法对齐)
sysbench oltp_read_write \
--tables=20 \
--table-size=5000000 \
--threads=32 \
--time=600 \
--mysql-host=127.0.0.1 \
--mysql-port=3306 \
--mysql-user=root \
run
# 停止 MySQL,profile 数据会写入 $PROFILE_DIR
mysqladmin -u root shutdown
# 插桩二进制运行时会在指定目录生成 .profdata 文件
# ===== 第三阶段:用 Profile 重新编译 =====
mkdir -p "$BUILD_DIR/stage2" && cd "$BUILD_DIR/stage2"
cmake "$MYSQL_SRC" \
-DCMAKE_BUILD_TYPE=Release \
-DWITH_PGO=USE \
-DPROFILE_DIR="$PROFILE_DIR" \
-DCMAKE_INSTALL_PREFIX=/usr/local/mysql-pgo-final
make -j$(nproc)
make install
echo "PGO 构建完成:/usr/local/mysql-pgo-final"
关键参数说明:
-DWITH_PGO=GENERATE:第一阶段,告诉编译器插入 profiling 指令。-DWITH_PGO=USE:第二阶段,把收集到的 profile 数据喂给编译器做优化。-DPROFILE_DIR:指定 profile 数据目录,具体路径取决于编译器和插桩运行时的输出配置,请参考 MySQL 源码中的 BUILD.md 确认。- 收集 profile 时的工作负载应尽量贴近你生产环境的真实查询模式。Percona 用的是 Sysbench OLTP Read/Write,如果你的业务以读为主或写密集,应调整 sysbench 参数或直接用真实流量。
用 Sysbench 复现基准测试
如果你想在自己的硬件上验证 PGO 效果,可以复现 Percona 的测试方法:
# 准备数据(20 张表,每张 500 万行)
sysbench oltp_read_write \
--tables=20 \
--table-size=5000000 \
--threads=32 \
--mysql-host=127.0.0.1 \
--mysql-port=3306 \
--mysql-user=root \
prepare
# 逐线程数跑基准测试
for t in 1 4 16 32 64 128 256 512; do
echo "=== Running with $t threads ==="
# 先预热
sysbench oltp_read_write \
--tables=20 --table-size=5000000 --threads=$t \
--time=600 --mysql-host=127.0.0.1 --mysql-port=3306 \
--mysql-user=root --warmup-time=180 run > "result_pgo_t${t}.log"
done
# 对 PGO 和非 PGO 版本各跑一轮,对比 tps 数值
建议至少跑两轮取稳定值,并确保测试期间没有其他负载干扰。Percona 每个线程数跑 900 秒(15 分钟),预热 180–600 秒,这个时长足以让 buffer pool 和 OS cache 进入稳态。
采纳建议与注意事项
谁应该用 PGO 版本?
- 内存 ≥ 32GB、以低中并发为主的 OLTP 场景——收益最大且无回退风险。
- CPU 是瓶颈(高 QPS 单实例)的场景——PGO 直接提升每核吞吐。
- 有能力自行构建和验证的用户——Oracle 官方 PGO 版本可以直接用,但自建版本可以用自己的业务负载做 profile,效果可能更好。
谁需要谨慎?
- 内存较小(2–4GB buffer pool)且并发极高(128+ 线程)的场景——收益有限,12GB Tier 在 128 线程甚至出现了 -3.1% 回退。
- 无法在预发布环境做充分验证的生产系统——PGO 的 profile 质量取决于采集负载的代表性,用 sysbench 采集的 profile 对 sysbench 负载最优,对你的业务不一定最优。
自建 PGO 的检查清单:
- Profile 采集负载是否覆盖了生产环境的典型查询模式?
- 采集时长是否足够(建议 ≥ 10 分钟稳态运行)?
- 是否在相同硬件上对比了 PGO 和非 PGO 版本?
- 是否测试了目标并发范围(特别是高线程数场景)?
- 是否监控了 InnoDB hit ratio、锁等待、I/O 延迟等指标,确认 PGO 版本没有引入异常?
PGO 不是银弹,6.5% 的平均提升在"零成本"优化里已经相当可观——不需要改 SQL、不需要调参数、不需要加硬件,只是换一个编译选项。但在高并发小内存场景下,务必先跑一轮自己的基准测试再决定是否上线。