MySQL 入门教程 · 第八课

29次阅读
没有评论

共计 7695 个字符,预计需要花费 20 分钟才能阅读完成。

目录

  1. 观测性全景:SLI/SLO/SLA 与数据库四黄金信号
  2. 指标采集:performance_schema / sys / mysqld_exporter
  3. 慢查询“画像”自动化:Digest 快照与趋势对比
  4. 执行计划回归检测:EXPLAIN JSON → 计划哈希
  5. 性能回归保护:基线、金丝雀、直方图与不可见索引
  6. 告警设计:门槛、延迟与抖动;Prometheus 规则示例
  7. 仪表盘建议:核心面板与读写分离可视化
  8. 压测与容量回归(sysbench + 基线入库)
  9. 一键演练:Docker Compose(MySQL + Exporter + Prometheus + Grafana)
  10. 实战练习(含参考答案)

版本: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_connectedmysql_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_readsmysql_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 快照与计划哈希,抓住“谁最慢、为何慢、什么时候开始慢”。
  • 上线有基线与金丝雀,下线有回退与审计;稳定性是一种工程习惯

正文完
 0
一诺
版权声明:本站原创文章,由 一诺 于2025-10-08发表,共计7695字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(没有评论)
验证码