MySQL 8.0 Performance Schema默认开启但采集器关闭,需显式启用stage/%=ON和statement/%=ON才能查SQL阶段耗时;误开query cache或binlog会引入额外锁争用与CPU开销;thread_id需从performance_schema.threads中筛选FOREGROUND用户线程,timer_wait单位为皮秒,须除以1000000000换算成秒。

Performance Schema默认开启但instrument没开,查不到阶段耗时还白占CPU
MySQL 8.0 默认 performance_schema = ON,但这是个“空壳”——所有 stage、statement、wait 类采集器默认是关闭的。不手动打开 stage/%=ON 和 statement/%=ON,events_stages_summary_by_thread_by_event_name 表永远为空,你却还在为“为什么看不到 SQL 阶段 CPU 分布”困惑。
更关键的是:未启用的 instrument 不耗资源,但一旦你加了 performance_schema_instrument = 'stage/%=ON' 却漏掉 statement/%=ON,会导致 stages 表有数据而无法关联到具体 SQL(因为 events_statements_current 没采集),排查断层;若全开了又没控制粒度,反而让 background thread 持续轮询、小幅度推高 CPU。
- 必须显式配置
performance_schema_instrument = 'statement/%=ON,stage/%=ON',不能只开 stage - 避免用
%=ON全开,尤其wait/%=ON在高并发下会显著增加 mutex 争用和 CPU 开销 - 修改后必须重启 MySQL,运行时
SET GLOBAL对performance_schema_instrument无效
误开 query cache 或 binlog 导致 stage/sql/Waiting for query cache lock 高频出现
在 MySQL 8.0 中,query_cache_type 默认是 OFF,但如果你从 5.7 升级或手动开启了它,就会触发 stage/sql/Waiting for query cache lock ——这个 stage 不代表“正在缓存”,而是多个线程在抢同一把 query cache 全局锁,CPU 就这么空转耗掉了。
同样,log_bin = ON 虽然不影响 stages 表本身,但它会让每个事务多一次 binlog cache 写入 + fsync,间接拉高 stage/sql/Executing 时间(尤其配合大事务或高 QPS 时),还会让 performance_schema 多采集一层 binlog 相关 wait 事件,进一步加重负担。
- 执行
SHOW VARIABLES LIKE 'query_cache_type';,非 0 就立刻设为0并重启 - 执行
SHOW VARIABLES LIKE 'log_bin';,返回ON且非主从环境,加skip-log-bin到 my.cnf 并删旧binlog文件再重启 -
stage/sql/Waiting for query cache lock出现在events_stages_summary_by_thread_by_event_name查询结果里,就是最直接的信号
threads 表里 thread_id 对不上,查到的 stage 数据全是 background 线程的
events_stages_summary_by_thread_by_event_name 的 thread_id 来自 performance_schema.threads,但这个 ID 和 SHOW PROCESSLIST 的 ID 完全无关。新手常犯的错是:看到 top 里 mysqld 占 CPU 高,就随便挑一个 PROCESSLIST 里的 ID 去查 stages 表,结果查到的全是 thread/innodb/io_ibuf_thread 或 thread/sql/compress_gtid_table 这类后台线程的统计,跟业务 SQL 毫无关系。
正确做法是先锁定「当前活跃的用户线程」:
- 查
SELECT THREAD_ID, TYPE, PROCESSLIST_USER, PROCESSLIST_INFO FROM performance_schema.threads WHERE TYPE = 'FOREGROUND' AND PROCESSLIST_USER IS NOT NULL; - 再用这些
THREAD_ID去关联events_statements_current和events_stages_summary_by_thread_by_event_name - 如果
PROCESSLIST_INFO是NULL,说明该线程当前没在执行语句,stage 统计也为空,别白查
timer_wait 单位是皮秒,不换算直接看数字会误判“某 stage 耗时 1e12”
events_stages_summary_by_thread_by_event_name.timer_wait 字段单位是 picoseconds(皮秒),不是毫秒也不是微秒。直接看原始值比如 1234567890123,会以为“花了 1.2 秒”,其实除以 1000000000 才是秒数(≈1.235 秒);若除以错了(比如少一个 0),结论就全偏了。
更隐蔽的问题是:SUM(timer_wait) 可能溢出 bigint,尤其在高负载长周期汇总时。别用裸 SUM,加 TRUNCATE(SUM(timer_wait)/1000000000, 6) 是底线操作。
- 必须除以
1000000000得秒数,不是1000000(那是微秒) - 排序时用
ORDER BY SUM(timer_wait) DESC没问题,但展示给同事看务必换算 - 如果发现某 stage 的
timer_wait总是0,大概率是对应 instrument 没开,或者该 stage 本身不被 timing(如部分初始化阶段)


















