【背景】
MySQL 8.0.32 / InnoDB,单主 16C64G,innodb_buffer_pool_size=32G,核心表 t_fulfillment 约 3800 万行,PK=id,二级索引 idx_uid_status_created (uid, status, created_at)。线上是一条典型的 OLTP 点查链路(按 uid+status 取最近 N 条),常态 P50≈1.8ms、P99≈3ms。
【现象】
过去三周,P99 在每天上午 10-11 点出现 2~4 次毛刺,P99 打到 80~120ms,P999 偶发 300ms+;P50 全程稳定在 1.8~2.2ms,说明是尾部问题而不是整体劣化。关键矛盾点:
- 毛刺窗口内 SHOW GLOBAL STATUS 算出的 buffer pool 命中率仍是 99.9%(Innodb_buffer_pool_read_requests 口径),基本没有物理读;
- EXPLAIN 与 performance_schema 的 stage 分布对比正常窗口,执行计划完全一致,rows_examined 没有变化;
- 已排除锁等待:毛刺窗口内 Innodb_row_lock_time_avg 无抬升,performance_schema.events_waits_summary 里 lock 相关事件占比 <0.5%;
- 应用侧 GC、连接池排队(HikariCP pending)在毛刺窗口内均无异常。
【已尝试的排查】
- 拉了毛刺窗口 ±5 分钟的 performance_schema.events_statements_history_long,按 query 聚合看 latency 分布:同一 SQL 的耗时直方图右尾拉长,但单条语句的 examined rows 与正常窗口一致;
- 看了 SHOW ENGINE INNODB STATUS 的 history list 长度,毛刺期间最长 12 条,不算深;
- 怀疑过 LRU 冲刷,但毛刺前 1 小时内没有跑过全表扫描或大范围 range 查询,也抓不到后台批处理的证据;
- 还没做:把单条慢语句的耗时拆成 stage 级(stage SQL/execute 各阶段耗时)逐条对拍,以及用 sys 库的 io 事件按 thread 粒度看毛刺窗口内是否有隐藏的顺序读。
【期望得到的答案形态】
- 一个可复现的排查顺序:在「命中率虚高 + 计划不变 + 无锁等待」这个组合下,P99 的成本还能藏在哪几个地方(如 change buffer merge、purge 线程 IO 竞争、buffer pool 内部 page cleaner 刷脏、redo 刷盘对齐、OS page cache 抖动等),每个候选用什么指标/表可以定量证伪;
- 具体到 performance_schema / sys 库要开哪些 instrumentation、查哪几张表、按什么口径算占比,给我一个可以直接抄的 SQL 清单;
- 如果你们线上遇到过「命中率 99.9% 但 P99 抖动」且最终根因不是锁、不是冷读的案例,很想看你们的最终证据链长什么样。
参考了两篇站内文章:《buffer pool 命中率 99.7% 但 P99 仍 800ms》讲过冷读冲刷 LRU 的机制,但我的场景毛刺前没有大查询、命中率也没掉,属于该文章没覆盖的「命中率看起来完全健康」的分支;《MySQL 8.0 慢查询 P99 偶发抖动 20 倍》那个提问方向接近,但我想问的是「排除 IO 和锁之后」剩下的候选与证伪方法。