MySQL 的“黑匣子“:Performance Schema 把数据库内部变成一张可查的表

排查 MySQL 性能问题,很多人的第一反应是 SHOW PROCESSLIST 看一眼线程状态,再去翻慢查询日志。这两样工具我用了很多年,它们能解决一部分问题,但都有一个共同的短板:回答的是"发生了什么",回答不了"时间到底花在哪"。

一条 SQL 执行了 5 秒,是在等锁,还是在等磁盘 I/O,还是卡在排序阶段?慢日志只能告诉你它慢,SHOW PROCESSLIST 只能给你 Sorting result 这样一个粗粒度的状态快照。想再往下挖一层,传统手段就到头了。

Performance Schema(下文简称 P_S)的思路完全不同。它把数据库内部的运行情况变成了一个可查询的数据源:事件采集下来,存进内存表,你用 SQL 去查。用 SQL 查性能数据这件事本身,就是思路的一次升级。

原理:插桩点 + 内存表 + SQL 查询

P_S 的工作流可以拆成三段。

第一段是采集。MySQL 源码里埋了大量插桩点(instrument),任何"耗时"的服务器操作,比如等一把锁、读一个文件、执行一个语句,经过插桩点时都会记下一个事件。

第二段是存储。事件写进 P_S 自己的内存表,由一个专用存储引擎支撑,不落盘。

第三段是查询。所有数据用普通 SQL 就能取出来。默认开启,开销很小,生产环境放心用。

想用好 P_S,先得读懂 instrument 的名字。命名是分层的,像文件路径,用 / 分隔,从左到右由粗到细。前缀表示事件类型:

  • wait

    :等待事件,比如等 I/O、等锁

  • stage

    :语句执行阶段,和 SHOW PROCESSLIST 里的状态对应

  • statement

    :语句事件

  • transaction

    :事务事件

  • memory

    / idle:内存事件、空闲事件

后缀表示代码区域。同一个组件在不同上下文里含义不同,比如:

复制代码
wait/io/file/innodb/innodb_log_file     -- 等 InnoDB 日志文件的 I/Owait/synch/mutex/sql/LOCK_delete        -- 等一个源码里的互斥锁stage/sql/Sorting result                -- 排序阶段

wait/io/file/innodb/... 里的 InnoDB 是文件 I/O 的属主,wait/synch/cond/innodb/... 里的 InnoDB 是同步机制的一部分。读懂名字,才读得懂事件。

配置:按需采集,控制开销

P_S 的配置集中在几张 setup_* 表里,各管一件事:

  • setup_instruments

    :开哪些采集点。ENABLED 管采集与否,TIMED 管是否计时

  • setup_consumers

    :哪些事件表真正落数据

  • setup_objects

    :按库、表对象过滤

  • setup_actors

    :按用户、主机过滤前台线程

consumer 是有层级的。最顶上 global_instrumentation 是总开关,关掉它,下面所有配置都不生效。往下是 thread_instrumentation,再往下才是 events_waits_current、events_statements_history 这类具体的事件表。statements_digest 比较特殊,管的是 SQL 归一化聚合,慢查询分析靠它。

核心原则一句话:只采你需要的。默认配置对日常诊断已经够用,事件采得越多开销越大。需要深挖等待细节时,再按需打开 wait 和 stage 的 history:​​​​​​​

复制代码
UPDATE setup_instruments SET ENABLED='YES', TIMED='YES'WHERE NAME LIKE 'wait/%'; UPDATE setup_consumers SET ENABLED='YES'WHERE NAME LIKE 'events_waits_%';

查问题定位完,记得关回去。想看 P_S 自己占了多少内存:

复制代码
SHOW ENGINE performance_schema STATUS;

它的内存模型是自动扩缩的,运行期只回收不释放,关闭服务才彻底归还。一般不用操心,心里有数就行。

查询:下钻靠事件层级,聚合靠 digest

P_S 的表分几类,记规律不用死记表名:

  • events_xxx_current

    :每个线程最近一条事件

  • events_xxx_history

    :每个线程最近 10 条

  • events_xxx_history_long

    :全局最近一万条

  • xxx_instances

    :被监控对象的实例,mutex、rwlock、file、socket、cond 各一张

  • events_xxx_summary_*

    :按 digest、用户、主机、线程等维度的聚合表

事件本身是有嵌套层级的:会话套事务,事务套语句,语句套 stage,stage 套 wait。每一层事件通过 NESTING_EVENT_ID 指向上层,这就是下钻的路径。从一条慢语句出发,沿着嵌套关系往下走,能一路定位到具体卡在哪把锁、哪个文件 I/O 上。

日常分析慢查询,主力是 events_statements_summary_by_digest。它把 SQL 归一化(字面量替换成 ?)后按 digest 聚合,一行就是一类语句的完整画像:​​​​​​​

复制代码
SELECT DIGEST_TEXT, COUNT_STAR,       SUM_TIMER_WAIT, AVG_TIMER_WAIT,       SUM_ROWS_EXAMINED, SUM_NO_INDEX_USED,       SUM_LOCK_TIME, FIRST_SEEN, LAST_SEENFROM events_statements_summary_by_digestORDER BY SUM_TIMER_WAIT DESCLIMIT 10;

COUNT_STAR 高、SUM_TIMER_WAIT 大、SUM_NO_INDEX_USED 不为零,又慢又频繁还不走索引的语句,一眼现形。这比翻慢日志高效得多。

一个必须记住的坑:原始事件表里的计时单位是皮秒(10⁻¹² 秒)。看到 TIMER_WAIT 是一串天文数字别慌,先确认单位:

复制代码
SELECT * FROM setup_timers;

stage 事件用的是纳秒,wait 事件默认用 CYCLE 计时器。算不准的时候,用 sys 的 format_time() 转一下。

再给一个随手可用的例子,找出最近没走索引的 SELECT:​​​​​​​

复制代码
SELECT SQL_TEXT FROM events_statements_historyWHERE NO_INDEX_USED != 0  AND EVENT_NAME = 'statement/sql/select';

sys schema:把门槛踏平

P_S 原始表有几十张,列名也不友好,直接查的学习成本不低。sys schema 就是干这个的:一层封装好的视图加存储过程,视图直接给结论,不用自己拼复杂查询。

视图大多成对出现:statement_analysis 是人类可读版,时间格式化成 16.75 s 这种;x$statement_analysis 保留原始数值,给工具和脚本做二次加工用。

几张我日常用得最多的:​​​​​​​

复制代码
-- 按语句聚合的完整画像:总/平均/最大延迟、扫描行数、临时表、全表扫描标记SELECT * FROM sys.statement_analysis LIMIT 5; -- 延迟排进前 5% 的语句SELECT * FROM sys.statements_with_runtimes_in_95th_percentile; -- 做全表扫描的语句SELECT * FROM sys.statements_with_full_table_scans; -- 建了之后从没被用过的索引SELECT * FROM sys.schema_unused_indexes; -- InnoDB 锁等待的完整链条:谁等谁、等了多久、各自的 SQLSELECT * FROM sys.innodb_lock_waits;

sys.processlist 也值得换掉 SHOW PROCESSLIST。它走 P_S 的 threads 表,非阻塞,而且附带 last_wait、trx_latency、current_memory 这些原生 processlist 没有的信息。

配置方面 sys 也给了快捷方式。不用去 UPDATE setup 表,一键开关采集:​​​​​​​

复制代码
CALL sys.ps_setup_enable_instrument('wait');CALL sys.ps_setup_enable_instrument('stage');CALL sys.ps_setup_enable_consumer('history_long');

排查完想恢复出厂状态,CALL sys.ps_setup_reset_to_default(TRUE); 一条搞定。皮秒换算交给 sys.format_time(342342342342345),返回 00:05:42,救眼神。

喜欢用图形界面的话,MySQL Workbench 里也有 P_S 的配置页和现成报表,数据同样来自这套体系,顺手一提。

收尾:P_S 的正确姿势

我的用法是三层节奏。P_S 默认常开,这部分开销可以忽略,不用纠结。日常巡检靠 sys 视图,statement_analysis 和 waits_global_by_latency 扫一遍,异常基本有数。真出了问题再按需开 wait、stage 的深采集,定位完关回去。

最后一点要记牢:P_S 的数据全在内存里,实例重启即清空。它是诊断工具,不是历史库。想看长期趋势,得定期把关键聚合表落盘,或者交给监控系统常态化采集。把它当"案发时的现场记录仪"用,别指望它记得上个月的事。

今天话题就聊到这,欢迎留言交流。觉得内容有用,别忘了点赞转发给有需要的朋友,回见!

相关推荐
这个DBA有点耶2 小时前
同城双活落地的三座山:网络延迟、脑裂预防、反向同步
数据库·架构·dba
青春之我_XP2 小时前
MySQL 常用日期函数 实战指南
数据库·sql·mysql·数据分析·数据库开发·日期函数
Nturmoils2 小时前
sys_dump 备了库,角色和权限别漏在外面
数据库
接着奏乐接着舞。2 小时前
【2026】73道Redis 常见面试题与参考答案
数据库·redis·后端·缓存
其实防守也摸鱼4 小时前
权限提升与横向移动:从内网渗透到域控的完整技术图谱
运维·服务器·数据库·安全·github·copilot·渗透
人生百态,人生如梦4 小时前
每日论文解读(9.1)——ReToolSQL:面向鲁棒Text-to-SQL的Agentic强化学习与工具增强两阶段训练框架
数据库·sql
DevOpenClub4 小时前
网页采集如何稳定输出结构化数据:JSON、链接与快照三阶段流水线
数据库·json·api
其实防守也摸鱼4 小时前
免杀与持久化入门:从载荷免杀到隐蔽通道的完整指南
运维·服务器·数据库·安全·github·copilot·渗透