共计 7695 个字符,预计需要花费 20 分钟才能阅读完成。
目录
- 观测性全景:SLI/SLO/SLA 与数据库四黄金信号
- 指标采集:performance_schema / sys / mysqld_exporter
- 慢查询“画像”自动化:Digest 快照与趋势对比
- 执行计划回归检测:EXPLAIN JSON → 计划哈希
- 性能回归保护:基线、金丝雀、直方图与不可见索引
- 告警设计:门槛、延迟与抖动;Prometheus 规则示例
- 仪表盘建议:核心面板与读写分离可视化
- 压测与容量回归(sysbench + 基线入库)
- 一键演练:Docker Compose(MySQL + Exporter + Prometheus + Grafana)
- 实战练习(含参考答案)
版本:MySQL 8.0。延续前几课的
shop库与实践风格。
1) 观测性全景:SLI/SLO/SLA 与数据库四黄金信号
- SLI(服务等级指标):可度量的用户体验信号,如 p99 查询延迟、复制延迟、错误率。
- SLO(服务等级目标):对 SLI 的目标阈值,例如 p99 < 200ms,错误率 < 0.1%。
- SLA(服务等级协议):对外承诺,通常包含赔付与例外条款。
四黄金信号(数据库映射)
- 延迟:查询 p95/p99、事务提交延迟、锁等待时间。
- 流量:QPS/TPS、分库分表各分片 QPS。
- 错误:失败率(语法 / 约束 / 死锁回滚)、复制错误数。
- 饱和度:
Threads_running、Buffer Pool 命中率、Redo 写入、磁盘队列、连接占用率。
原则:用户体验优先 (延迟 / 错误), 系统健康其次 (饱和度),并配合 可追溯证据(慢日志、Digest)。
2) 指标采集:performance_schema / sys / mysqld_exporter
2.1 打开必要开关(my.cnf 摘要)
[mysqld]
performance_schema = ON
# 慢日志
slow_query_log = ON
long_query_time = 0.5
log_queries_not_using_indexes = ON
# 二进制日志与行格式(若后续 CDC 或审计需要)log_bin = mysql-bin
binlog_format = ROW
2.2 sys 库快速画像
-- 语句维度热点(平均 / 总延迟、扫描行、返回行等)SELECT * FROM sys.statement_analysis ORDER BY avg_latency DESC LIMIT 20;
-- 表与索引使用热点
SELECT * FROM sys.schema_table_statistics ORDER BY rows_read DESC LIMIT 20;
SELECT * FROM sys.schema_index_statistics ORDER BY rows_read DESC LIMIT 20;
2.3 mysqld_exporter(Prometheus)指标采集
**docker-compose 片段:** 见 §9;核心是给 Exporter 只读账号:
CREATE USER 'exporter'@'%' IDENTIFIED BY 'Expo@123';
GRANT PROCESS, REPLICATION CLIENT, SELECT ON *.* TO 'exporter'@'%';
FLUSH PRIVILEGES;
常见指标名速查(示例):
- 连接与线程:
mysql_global_status_threads_connected、mysql_global_status_threads_running - QPS:
rate(mysql_global_status_queries[5m]) - 慢查询:
increase(mysql_global_status_slow_queries[5m]) - InnoDB:
mysql_global_status_innodb_buffer_pool_reads、mysql_global_status_innodb_row_lock_time - 复制延迟:
mysql_slave_status_seconds_behind_master(或同义指标)
注意:具体指标名可能因 Exporter 版本略有差异,以上用于示意与告警构造。
3) 慢查询“画像”自动化:Digest 快照与趋势对比
目标 :每天抓取一次“语句指纹(DIGEST)”的聚合统计,观察 排名变化、延迟上升、扫描行异常。
3.1 建表(快照仓)
CREATE TABLE IF NOT EXISTS ps_digest_snapshots (
snapshot_ts DATETIME NOT NULL,
schema_name VARCHAR(64),
digest VARCHAR(64),
digest_text TEXT,
count_star BIGINT,
avg_latency_ms DOUBLE,
sum_rows_examined BIGINT,
sum_rows_sent BIGINT,
first_seen DATETIME,
last_seen DATETIME,
PRIMARY KEY (snapshot_ts, digest)
);
3.2 抽取语句摘要
performance_schema 计时单位是 皮秒(1e-12s),需换算。
INSERT INTO ps_digest_snapshots
SELECT NOW() AS snapshot_ts,
schema_name,
digest,
digest_text,
count_star,
ROUND(sum_timer_wait/1e9 / NULLIF(count_star,0), 3) AS avg_latency_ms,
sum_rows_examined,
sum_rows_sent,
FIRST_SEEN,
LAST_SEEN
FROM performance_schema.events_statements_summary_by_digest
WHERE schema_name IS NOT NULL
ORDER BY count_star DESC
LIMIT 500; -- 记录 Top 500 指纹
3.3 趋势 / 对比查询
-- 最近两次快照对比平均延迟的变化
WITH last2 AS (
SELECT * FROM ps_digest_snapshots
ORDER BY snapshot_ts DESC LIMIT 1000
)
SELECT a.digest, LEFT(a.digest_text,120) AS sample,
a.avg_latency_ms AS now_ms,
b.avg_latency_ms AS prev_ms,
(a.avg_latency_ms - b.avg_latency_ms) AS delta_ms
FROM (SELECT * FROM last2 WHERE snapshot_ts=(SELECT MAX(snapshot_ts) FROM ps_digest_snapshots)
) a
JOIN (SELECT * FROM last2 WHERE snapshot_ts=(SELECT MAX(snapshot_ts) FROM ps_digest_snapshots WHERE snapshot_ts < (SELECT MAX(snapshot_ts) FROM ps_digest_snapshots))
) b USING(digest)
ORDER BY delta_ms DESC
LIMIT 50;
3.4 任务化(事件调度器)
SET GLOBAL event_scheduler = ON;
DELIMITER $$
CREATE EVENT IF NOT EXISTS ev_snapshot_digest
ON SCHEDULE EVERY 1 DAY STARTS CURRENT_DATE + INTERVAL 1 DAY
DO BEGIN
INSERT INTO ps_digest_snapshots
SELECT NOW(), schema_name, digest, digest_text, count_star,
ROUND(sum_timer_wait/1e9 / NULLIF(count_star,0), 3),
sum_rows_examined, sum_rows_sent, FIRST_SEEN, LAST_SEEN
FROM performance_schema.events_statements_summary_by_digest
WHERE schema_name IS NOT NULL
ORDER BY count_star DESC LIMIT 500;
END$$
DELIMITER ;
4) 执行计划回归检测:EXPLAIN JSON → 计划哈希
思路:对关键 SQL 维护“基线计划哈希”,每日(或每次发版)对比当前计划与rows_examined 增幅,异常则告警。
4.1 基线表
CREATE TABLE IF NOT EXISTS plan_baseline (qid VARCHAR(64) PRIMARY KEY, -- 语句标识(自定义)sql_text TEXT NOT NULL,
plan_hash CHAR(32) NOT NULL,
rows_examined BIGINT,
captured_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
4.2 生成计划哈希(示例)
-- 用 EXPLAIN FORMAT=JSON,提取 query_block 并做 MD5
SET @sql = 'SELECT * FROM orders WHERE user_id=? AND created_at>=? ORDER BY created_at DESC LIMIT 50';
SET @json = (SELECT JSON_EXTRACT(EXPLAIN FORMAT=JSON SELECT * FROM orders WHERE user_id=1 AND created_at>=NOW()-INTERVAL 30 DAY ORDER BY created_at DESC LIMIT 50, '$.query_block'));
SELECT MD5(@json) AS plan_hash; -- 保存到 plan_baseline
4.3 比对与告警(写入本地 alerts 表)
CREATE TABLE IF NOT EXISTS plan_alerts (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
qid VARCHAR(64),
old_hash CHAR(32), new_hash CHAR(32),
old_rows BIGINT, new_rows BIGINT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- 伪代码:周期任务中执行(可用事件调度器或外部作业)-- 1) 取基线 sql_text → 拼接绑定变量 → EXPLAIN JSON → 现 hash 与估算 rows
-- 2) 如 hash 改变且 new_rows > old_rows*3,则插入 plan_alerts
进阶:把“计划差异 JSON”也存下;必要时启用 不可见索引 回滚或添加 Hint 暂时兜底。
5) 性能回归保护:基线、金丝雀、直方图与不可见索引
- 基线:发布前跑一组关键 SQL,记录 p95 延迟、
rows_examined、结果行数、计划哈希;发布后再跑对比。 - 金丝雀:对 1% 流量使用新版本 / 新索引;比较 SLI(延迟 / 错误 / 扫描行),无回退风险再放量。
- 直方图:
ANALYZE TABLE ... UPDATE HISTOGRAM提升选择性估计,降低计划抖动。 - 不可见索引 → 可见:先以
INVISIBLE上线,观测计划;稳定后VISIBLE。 - Freeze & Unfreeze:大促 / 峰值前后冻结统计 / 变更窗口,避免自动分析引起计划跳变。
6) 告警设计:门槛、延迟与抖动;Prometheus 规则示例
门槛规则(YAML 片段,示意)
groups:
- name: mysql.rules
rules:
- alert: MySQLHighLatencyP99
expr: histogram_quantile(0.99, sum(rate(mysql_statement_latency_bucket[5m])) by (le)) > 0.2
for: 10m
labels: {severity: page}
annotations:
summary: "p99 查询延迟过高"
- alert: MySQLReplicaLag
expr: mysql_slave_status_seconds_behind_master > 2
for: 2m
labels: {severity: page}
annotations: {summary: "复制延迟超过 2s"}
- alert: MySQLConnectionsHigh
expr: mysql_global_status_threads_connected / mysql_global_variables_max_connections > 0.85
for: 5m
labels: {severity: warn}
annotations: {summary: "连接占用率 >85%"}
- alert: MySQLSlowQueries
expr: increase(mysql_global_status_slow_queries[5m]) > 0
for: 10m
labels: {severity: warn}
annotations: {summary: "出现慢查询(>long_query_time)"}
经验:规则要 有去抖 (
for:)、有 修复指引 ,并且与 SLO 对齐;将 错误预算 用在变更节流上。
7) 仪表盘建议:核心面板与读写分离可视化
- 总览:QPS/TPS、p95/p99 延迟、错误率、慢查询速率、连接 / 线程、Buffer Pool 命中率。
- InnoDB:
rows_read/written、行锁时间、redo/flush、脏页比例、Checkpoint 进度。 - 复制:
Seconds_Behind_Source、中继日志应用速率、延迟分布。 - 读写分离:Writer 与 Readers 分面显示延迟 / 负载;延迟 > 阈值自动摘除的计数与原因。
- 分片:各分片 QPS/ 延迟热力图,热键 TopN。
8) 压测与容量回归(sysbench + 基线入库)
# 准备(示例)sysbench oltp_read_write --mysql-host=127.0.0.1 --mysql-user=root --mysql-password=*** \
--mysql-db=shop --tables=8 --table-size=100000 prepare
# 压测(并发 64,5 分钟)sysbench oltp_read_write --mysql-host=127.0.0.1 --mysql-user=root --mysql-password=*** \
--mysql-db=shop --tables=8 --table-size=100000 --threads=64 --time=300 run
# 清理
aaa=sysbench oltp_read_write --mysql-host=127.0.0.1 --mysql-user=root --mysql-password=*** \
--mysql-db=shop --tables=8 --table-size=100000 cleanup
把关键结果入库(简表):
CREATE TABLE IF NOT EXISTS perf_baseline (run_id VARCHAR(32) PRIMARY KEY,
date_ts DATETIME NOT NULL,
qps DOUBLE, p95_ms DOUBLE, errors INT,
notes VARCHAR(200)
);
-- 将 sysbench 输出解析后写入此表,用于与后续版本对比。
9) 一键演练:Docker Compose(MySQL + Exporter + Prometheus + Grafana)
docker-compose.yml(示例)
version: '3.8'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: Passw0rd!
ports: ["3306:3306"]
volumes:
- ./mysql/my.cnf:/etc/mysql/conf.d/my.cnf:ro
- ./mysql/data:/var/lib/mysql
exporter:
image: prom/mysqld-exporter:latest
environment:
DATA_SOURCE_NAME: exporter:Expo@123@(mysql:3306)/
ports: ["9104:9104"]
depends_on: [mysql]
prometheus:
image: prom/prometheus:latest
volumes:
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
ports: ["9090:9090"]
depends_on: [exporter]
grafana:
image: grafana/grafana:latest
ports: ["3000:3000"]
depends_on: [prometheus]
prometheus.yml(示例)
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'mysqld'
static_configs:
- targets: ['exporter:9104']
启动后导入社区 MySQL 仪表盘或自建面板(见 §7)。首次登录 Grafana 默认用户 / 密码
admin/admin(请及时修改)。
10) 实战练习(含参考答案)
练习 1(Digest 画像):创建 ps_digest_snapshots,跑一次快照,并找出 平均延迟上升 Top 10 的指纹。
-- 建表与插入见 §3.1/§3.2
-- 对比示例见 §3.3;将 ORDER BY delta_ms DESC LIMIT 10
练习 2(计划回归):为“近 30 天按用户查订单”的查询建立基线计划,发版后检测 rows_examined 是否 >3 倍并写入 plan_alerts。
-- 建表见 §4.1;EXPLAIN JSON 见 §4.2;告警写入见 §4.3
练习 3(告警规则):写出三条 Prometheus 规则:p99 延迟、复制延迟、连接占比,并设置 for: 去抖时间。
-- 示例见 §6,可按你的 SLO 调整阈值与 for 时长
练习 4(金丝雀观测):描述如何对 1% 用户启用新索引并比较延迟 / 错误,给出回滚策略。
答案要点:不可见索引→可见 + 流量分割合并对比 → 指标恶化回滚索引可见性或撤销变更。
练习 5(容量回归):用 sysbench 跑 64 并发 5 分钟,把 QPS 与 p95 写入 perf_baseline,下次发版后自动对比并出“提升 / 回退”结论。
答案要点:基线表 + 定时任务;差异 > ±10% 触发通知。
小结
- 把体验化为 SLI,把目标写成 SLO,把规则落在 Prometheus/Grafana。
- 用 Digest 快照与计划哈希,抓住“谁最慢、为何慢、什么时候开始慢”。
- 上线有基线与金丝雀,下线有回退与审计;稳定性是一种工程习惯。

