背景与问题
MySQL 8.0 的 performance_schema 提供了细粒度的等待事件监控,但默认无法直接拆分单条查询的耗时到 Buffer Pool IO、锁等待和 CPU 时间三个维度。本文通过一个线上 P99 抖动案例,详细说明如何通过 performance_schema + sys schema 联动分析实现三维度拆解。
核心原理
-
等待事件分类:
wait/io/*:Buffer Pool 页面读取等待(如 page not found)wait/lock/*:行锁/间隙锁等待(需配合 sys.schema_lock_waits 分析)wait/synch/*:软中断(softirq)抢占和上下文切换
-
关联逻辑:
- 通过
performance_schema.events_statements_history_by_thread关联查询语句 - 使用
thread_id拉取对应等待事件
- 通过
架构与实现细节
1. 查询与等待事件关联
SELECT
s.digest,
SUM(CASE WHEN event_name LIKE 'wait/io/%' THEN timer_wait END) AS io_wait,
SUM(CASE WHEN event_name LIKE 'wait/lock/%' THEN timer_wait END) AS lock_wait,
SUM(CASE WHEN event_name LIKE 'wait/synch/%' THEN timer_wait END) AS sync_wait,
SUM(timer_wait) AS total_wait
FROM
performance_schema.events_waits_history_by_thread t
JOIN
performance_schema.events_statements_history_by_thread s
ON t.thread_id = s.thread_id
WHERE
s.digest = 'your_query_digest'
GROUP BY
s.digest;
2. 锁等待溯源
SELECT
r.trx_id AS waiting_trx_id,
r.trx_started AS waiting_start,
b.trx_id AS blocking_trx_id,
b.trx_started AS blocking_start,
r.trx_rows_locked,
r.trx_rows_modified
FROM
sys.schema_lock_waits r
JOIN
sys.schema_lock_waits b
ON r.blocking_trx_id = b.trx_id
WHERE
r.trx_mysql_thread_id = (SELECT thread_id FROM performance_schema.threads WHERE processlist_id = 123);
3. CPU 时间验证
- 使用
perf record -p <pid> -g抓取进程级 CPU 调用栈 - 分析
softirq和context switch频率(需 root 权限) - 对比
sync_wait与系统级 CPU 使用率
踩坑与解决
- 数据关联失效:初期未正确关联
thread_id导致分析偏差 - 锁等待误判:需配合
sys.schema_lock_waits精准定位阻塞事务 - CPU 调用栈采集:需确保进程有足够权限并避免采样干扰
性能与验证数据
| 指标 | 基线值 | 抖动期间 | 优化后 | |------|--------|----------|--------| | P99 延迟 | 1ms | 20ms | 1.2ms | | buffer pool 命中率 | 98% | 97.8% | 98.5% | | context switch | 800/s | 4200/s | 900/s |
对比与选型
- 纯 performance_schema:无法精准拆分三维度
- sys.schema 联动:实现锁等待溯源但需额外配置
- perf 工具:提供 CPU 调用栈但侵入性强
总结与展望
通过 performance_schema + sys.schema 联动分析,可有效拆解单条查询耗时至三维度。未来可结合 eBPF 技术实现更精细化的 CPU 跟踪,同时探索 AI 驱动的自动根因分析。
高并发下的资源争用分析
在高并发场景下,单条查询的耗时拆解需要更精细的资源争用分析。当系统面临大量并发请求时,Buffer Pool IO、锁等待和CPU时间的争用会呈现出复杂的交互关系,需要通过特定的查询和监控手段识别瓶颈。以下将从三个维度展开分析:高并发下的锁争用链路追踪、多线程IO调度的热点识别和CPU资源的并发竞争分析。
1. 高并发下的锁争用链路追踪
在并发场景中,锁等待可能形成复杂的链式阻塞,例如事务A等待事务B的锁,事务B又等待事务C的锁,最终形成死锁。此时,传统的锁等待溯源方法可能无法完整呈现争用链路。需要通过以下步骤进行深度分析:
1.1 事务级锁等待拓扑分析
SELECT
r.trx_id AS waiting_trx_id,
r.trx_started AS waiting_start,
b.trx_id AS blocking_trx_id,
b.trx_started AS blocking_start,
r.trx_rows_locked,
r.trx_rows_modified,
r.trx_mysql_thread_id AS waiting_thread_id,
b.trx_mysql_thread_id AS blocking_thread_id,
r.trx_query AS waiting_query,
b.trx_query AS blocking_query
FROM
sys.schema_lock_waits r
JOIN
sys.schema_lock_waits b
ON r.blocking_trx_id = b.trx_id
WHERE
r.trx_mysql_thread_id IN (
SELECT thread_id
FROM performance_schema.threads
WHERE processlist_id IN (123, 456, 789)
)
ORDER BY
waiting_start DESC;
该查询通过sys.schema_lock_waits表的blocking_trx_id和trx_mysql_thread_id字段,构建事务间的阻塞关系图。在高并发场景中,可以通过trx_started字段识别阻塞链的形成时间,结合trx_query字段定位具体SQL语句,从而分析锁争用的业务逻辑关联。
1.2 间隙锁与行锁的区分
对于InnoDB引擎,wait/lock/table/sql/row_lock和wait/lock/table/sql/row_lock/consistent_read事件分别对应行锁和间隙锁。通过分析sys.schema_lock_waits中的object_schema、object_name和lock_type字段,可以区分锁的粒度:
SELECT
trx_mysql_thread_id,
object_schema,
object_name,
lock_type,
COUNT(*) AS lock_count
FROM
sys.schema_lock_waits
WHERE
lock_type IN ('RECORD', 'GAP')
GROUP BY
trx_mysql_thread_id, object_schema, object_name, lock_type
ORDER BY
lock_count DESC;
该查询帮助识别高并发场景下是否存在大量间隙锁争用,例如在范围查询或索引失效的场景中,间隙锁可能占用大量资源。
2. 多线程IO调度的热点识别
在高并发写入场景中,Buffer Pool IO争用可能成为性能瓶颈。通过分析performance_schema.events_waits_history_by_thread中的wait/io/*事件,结合线程状态信息,可以定位IO热点:
2.1 Buffer Pool IO事件分类
SELECT
thread_id,
SUM(CASE WHEN event_name LIKE 'wait/io/buffer_pool/%' THEN timer_wait END) AS buffer_pool_wait,
SUM(CASE WHEN event_name LIKE 'wait/io/file/%' THEN timer_wait END) AS file_io_wait
FROM
performance_schema.events_waits_history_by_thread
WHERE
thread_id IN (
SELECT thread_id
FROM performance_schema.threads
WHERE processlist_id IN (123, 456, 789)
)
GROUP BY
thread_id
ORDER BY
buffer_pool_wait DESC;
该查询区分了Buffer Pool内部读取等待(如wait/io/buffer_pool/page_not_found)和磁盘文件IO等待(如wait/io/file/innodb_data_file)。在高并发写入时,file_io_wait的占比可能显著上升,需结合innodb_buffer_pool_wait_free指标判断是否需要扩容Buffer Pool。
2.2 IO请求的线程级分布
通过performance_schema.threads表的thread_id和processlist_id字段,可以关联线程状态与IO等待:
SELECT
t.thread_id,
t.processlist_id,
t.processlist_user,
t.processlist_host,
t.processlist_db,
SUM(CASE WHEN event_name LIKE 'wait/io/%' THEN timer_wait END) AS total_io_wait
FROM
performance_schema.events_waits_history_by_thread w
JOIN
performance_schema.threads t
ON w.thread_id = t.thread_id
WHERE
t.processlist_id IN (123, 456, 789)
GROUP BY
t.thread_id, t.processlist_id
ORDER BY
total_io_wait DESC;
此查询可识别高IO消耗的线程,进一步分析其SQL语句是否涉及全表扫描或未使用索引的场景。
3. CPU资源的并发竞争分析
在高并发场景下,CPU争用可能由软中断(softirq)和上下文切换(context switch)导致。通过perf工具和performance_schema的联合分析,可定位CPU瓶颈:
3.1 软中断与上下文切换监控
perf record -p <mysql_pid> -g --cpu 0-3
perf report --no-children
通过perf工具抓取CPU调用栈,可识别softirq和context switch的热点函数。例如,频繁的poll或wait_for_completion调用可能表明内核锁争用或IO事件处理延迟。
3.2 CPU使用率与等待事件的关联
结合sys.schema_processlist和performance_schema.events_waits_history_by_thread,可分析CPU使用率与等待事件的关联:
SELECT
p.processlist_id,
p.processlist_user,
p.processlist_db,
p.processlist_time,
p.state,
SUM(CASE WHEN event_name LIKE 'wait/synch/%' THEN timer_wait END) AS sync_wait,
SUM(CASE WHEN event_name LIKE 'wait/io/%' THEN timer_wait END) AS io_wait,
SUM(CASE WHEN event_name LIKE 'wait/lock/%' THEN timer_wait END) AS lock_wait
FROM
performance_schema.events_waits_history_by_thread w
JOIN
performance_schema.threads t
ON w.thread_id = t.thread_id
JOIN
sys.schema_processlist p
ON t.processlist_id = p.processlist_id
WHERE
p.processlist_time > 100
GROUP BY
p.processlist_id
ORDER BY
sync_wait + io_wait + lock_wait DESC;
该查询通过processlist_time筛选长时间运行的查询,结合等待事件的总和,可识别CPU争用与等待事件的耦合关系。例如,若sync_wait占比高,可能表明内核锁争用;若io_wait占比高,则需优化IO性能。
高并发场景的综合分析建议
- 优先级排序:根据等待事件占比,优先优化占比最高的瓶颈(如锁等待或IO等待)。
- 资源隔离:通过
innodb_thread_concurrency参数限制并发线程数,避免资源争用。 - 锁粒度优化:减少行锁和间隙锁的持有时间,例如通过调整事务隔离级别或优化索引设计。
- CPU调优:若软中断频繁,可尝试调整内核参数(如
net.core.somaxconn)或升级硬件。