Skip to content
常见陷阱与最佳实践

常见陷阱与最佳实践

这一章是全手册的收束:把散落在 00~12、14 章的坑汇总成一张速查表,把该守的规则压成清单,最后给一张"出事了先查什么"的调试表。写法上不再展开原理(原理都在对应章节),只回答三个问题:坑是什么、后果长什么样、正确做法一句话。

版本基线与全手册一致:MySQL 8.4 LTS(兼顾 8.0 存量与 9.7 LTS);标注"实测"的结论出自本地 8.0.28 实例验证。


1. 陷阱汇总表

1.1 建表与数据类型

陷阱 后果 正确做法 详见
用 utf8 建库建表 utf8 只是 utf8mb3 的别名,存 𠮷、emoji 直接报 ERROR 1366 (HY000): Incorrect string value: '\xF0\xA0\xAE\xB7'(实测:客户端字符集正确时同样拒收,不是客户端的锅) 一律显式 utf8mb4 + utf8mb4_0900_ai_ci,库/表/列三层都定 03
同一条 SQL 里混用不同 collation 的列做比较/拼接 ERROR 1267 (HY000): Illegal mix of collations,迁移合并两套历史库时高发 新表统一 collation;临时用 COLLATE 显式转换,别依赖连接默认 03
金额用 FLOAT/DOUBLE 二进制浮点存十进制小数天然有误差,对账对不平 DECIMAL(M,D),钱的问题用精确小数 03
TIMESTAMP 存长期业务时间 2038 年上界、且随 time_zone 变量变化显示值 业务时间用 DATETIME(3);TIMESTAMP 只用于"记录此刻"的审计字段 03
主键用宽字符串(UUID 原文、业务编码) 聚簇行变宽、每棵二级索引叶子都跟着变宽,扇出腰斩、树提前长高 代理主键 BIGINT UNSIGNED AUTO_INCREMENT,业务编号另建唯一索引 04
VARCHAR 定义宽度按"占空间"来省着给 省不了多少(按实际长度存),但宽度参与索引成本估算和 key_len 计算 给到业务上限即可,别抠 20 和 32 的区别;真正要抠的是跨不跨 255 字节分界(utf8mb4 下 63→64 会让长度前缀从 1 字节变 2 字节,触发列改重建) 03、09
INT(11)、ZEROFILL 当"宽度限制"写进新代码 显示宽度 8.0 起已弃用,纯噪音 写 INT 就好,宽度交给类型本身 03
无主键表 复制(ROW+where 全表定位)、RDS 工具链、在线 DDL 全部受影响 每张 InnoDB 表必须有主键 00、10

1.2 索引

陷阱 后果 正确做法 详见
对索引列做函数/运算(WHERE DATE(t)=...) 索引失效,全表扫 改写成范围:t >= '2026-10-20' AND t < '2026-10-21';或 8.0 函数索引 08
WHERE varchar_col = 123(数字比对字符串) 隐式类型转换按 DOUBLE 比较,该列索引整体失效 类型对齐是最便宜的优化;驱动层参数别偷懒 08
联合索引把范围列放前面((a,b) 但 WHERE a>? AND b=?) b 用不进索引,key_len 只到 a 等值列在前、范围列在后;排序列放在范围列之后还能省 filesort 04、08
EXPLAIN 的 key 显示 idx_a 就以为"走了索引" 走了不等于用得值:命中 30% 以上行数时回表成本可能高于顺序扫 对比 rows 估算与实际,必要时 FORCE INDEX 验证两种计划的真实耗时 08
迷信 key_len 资料里"varchar ≤255 字节 +1 前缀" 算出来的 key_len 永远对不上线上值 EXPLAIN 口径一律 +2(长度前缀)+ 可空列每个 +1;1/2 字节分界是 InnoDB 磁盘记录格式的规则,两套口径别混(实测:utf8mb4 VARCHAR(20) 计 82、VARCHAR(63) 计 254,全部 +2) 08
给低基数枚举列(status、性别)单独建索引 优化器永远不选它,白白摊薄 buffer pool、拖慢写入 要建就建联合(status+时间),或干脆不建靠过滤 04
冗余索引:(a) 与 (a,b) 并存、或 KEY 与同列 UNIQUE 并存 纯浪费写放大 SHOW INDEX 定期体检,删被前缀覆盖的 04
删索引前直接 DROP 万一是某条低频大 SQL 唯一的救命稻草,上线即事故 先 ALTER TABLE ... ALTER INDEX ... INVISIBLE 灰度几天,sys.schema_unused_indexes 无新增再 DROP 04、08

1.3 SQL 写法

陷阱 后果 正确做法 详见
SELECT * 上生产 覆盖索引退化、网络浪费、表结构一变应用就炸 列清单显式写 02
深分页 LIMIT 1000000,20 扫 100 万条再丢掉,越翻越慢 游标式改写:WHERE id > 上次末尾id ORDER BY id LIMIT 20;或延迟关联(子查询分页拿主键再回表) 08
COUNT(*) 恐慌 其实 InnoDB 就是扫最小的树数行,百万级毫秒~秒;真正慢的是 COUNT(*) 无 where 大表 + 高频调用 要"大概"用统计信息 information_schema.tables.rows 或 Redis 计数,要精确就接受扫描 02、08
GROUP_CONCAT 结果莫名被截断 默认 group_concat_max_len=1024(实测),拼接长列表悄悄丢尾 会话级调大,或改应用层聚合 02
递归 CTE 不设边界 cte_max_recursion_depth 默认 1000,超了报 ERROR 3636(recursive query aborted after 1001 iterations,实测) 层级不可控的树在 WHERE 里带深度计数 02
依赖 8.0 的 hash join 当"免费午餐" join buffer 占内存(join_buffer_size 按线程分配),超大表 join 照样慢 先保证等值列有索引,hash join 是兜底不是常规 08
上线 ONLY_FULL_GROUP_BY 报错的旧 SQL 直接改全局 sql_mode 关掉它 掩盖了真实歧义:非聚合列在组内本来就不确定 修 SQL(进 GROUP BY 或套聚合函数),8.0 默认 sql_mode 别动(实测默认含 ONLY_FULL_GROUP_BY、STRICT_TRANS_TABLES) 00、02

1.4 事务与锁

陷阱 后果 正确做法 详见
代码里 BEGIN 后异常路径没 COMMIT/ROLLBACK 连接归还连接池后长悬挂:undo 膨胀、purge 停滞、复制延迟、DDL 被 MDL 卡死 事务边界包住整个逻辑;框架层兜底 rollback;information_schema.innodb_trx 上告警(>60s 就报警) 05
UPDATE/DELETE 的 WHERE 没有可用索引 RR 下全表记录逐行加 X 锁,等同锁表;别的会话全部 1205 无索引的批量 UPDATE 是高危变更,上线前 EXPLAIN 必须看到 key 非 NULL 05
把 1205 当死锁处理 两者语义不同:ERROR 1205 Lock wait timeout exceeded(实测)只回滚当前语句、事务还活着,直接重试原语句会拿半套状态重来 1205 → 查谁持锁(data_lock_waits/sys.innodb_lock_waits);ERROR 1213 Deadlock found(实测)才回滚整个事务,幂等重试 05
死锁慌了删索引"避锁" 锁冲突的本质是交叉更新顺序,删索引只会把它变成全表锁 统一加锁顺序、拆小事务、innodb_print_all_deadlocks=ON 留现场 05
RR 快照读之后紧跟一条 UPDATE UPDATE 是 current read,改的可能正是"你的快照看不到的"别家已提交行,随后同事务再读它就"变"了 需要"读到什么就锁住什么"用 SELECT ... FOR UPDATE(且必须在 UPDATE 之前);业务对一致性敏感就直说用 RC + 应用层判 06
队列表 SELECT FOR UPDATE + 高并发取任务 gap/next-key 锁把"取一条"放大成"锁一段",插入也被挡 8.0 用 FOR UPDATE SKIP LOCKED(实测跳过被锁行返回下一批)或 NOWAIT(ERROR 3572 ... NOWAIT is set,实测) 05
大事务一次改几十万行 行锁升级为长持锁 + binlog/undo 双膨胀 + 从库回放尖峰 分批(每批几千行、批间提交);定时任务错峰 05、07

1.5 DDL 与大表变更

陷阱 后果 正确做法 详见
ALTER 不写 ALGORITHM= 就上线 你以为 INSTANT,实际悄悄降级 COPY/INPLACE 重建,大表直接拖垮 IO 变更单显式声明 ALGORITHM=INSTANT, LOCK=NONE,不支持就直接报错(ERROR 1845 ... Try ALGORITHM=COPY/INPLACE.,实测)反而安全 09
无脑反复 INSTANT ADD/DROP 列 行版本累积有上限,超限后连普通 DDL 都报 1799 类错误,被迫停机重建 定期把高频变更表排进"重建归零"维护窗口 09
大表加列同时加索引 两件事各自都在线,叠一起放大 row log 与 MDL 风险 拆两次变更:加列走 INSTANT,索引单独 CREATE INDEX ... ALGORITHM=INPLACE,各自有失败重试边界 09
gh-ost/pt-osc 当万能 都要唯一键、都要磁盘余量、都吃从库/主库复制流量;pt-osc 依赖 trigger,和别的 trigger 冲突 变更 10 万行以下 INSTANT/INPLACE 直做,亿级才上工具,跑前在预发按同量级演练 09
DDL 执行完卡在最后的 MDL 申请 “卡住不报错"时全表读写排它后面排队,看起来像数据库挂了 先 kill 持锁长查询再重试 DDL;sys.schema_table_lock_waits 定位 05、09

1.6 复制与高可用

陷阱 后果 正确做法 详见
默认异步复制 + 主库宕机硬切换 未及传输的 binlog 随主机一起丢 核心业务半同步(rpl_semi_sync_*,8.0.29+ 用 AFTER_SYNC 即"lossless”)+ GTID 10
应用直接读从库 复制延迟下"写完立刻读不到"(写后读不一致) 下单/支付类强制读主;或事务内粘主;或 GTID 等待(rds_async/WAIT_FOR_EXECUTED_GTID_SET) 10
从库调并行复制只调 worker 数 replica_parallel_workers=8 但回放仍是串行 8.0.26+ 默认 WRITESET 依赖追踪即可并行;8.4 内部恒为 WRITESET,只留 replica_preserve_commit_order=ON(别关) 10
binlog 当备份 ROW 格式大事务/无主键表 UPDATE 会把从库与备份一起打爆 备份用 xtrabackup/mysqldump,binlog 只做增量链与恢复点 07、11
binlog_row_image=minimal 想省空间 闪回/下游 CDC(Canal 类)拿不到 before image,兼容性碎一地 保持默认 FULL;省空间去调保留策略而不是行镜像 07、10

1.7 备份与恢复

陷阱 后果 正确做法 详见
mysqldump 不加 --single-transaction 要么锁全表停写,要么备份不一致 InnoDB 统一 --single-transaction --source-data=2(8.0.26+ 新名;旧 --master-data 仍可用) 11
--single-transaction 当万能 库里混着 MyISAM/MEMORY 表照样不一致 迁平 InnoDB,或接受逻辑备份加读锁窗口 11
只备份、不备份 binlog 恢复到"上次备份"为止,备份间隔内的数据全丢 全量 + 连续 binlog 归档 + 定期真实恢复演练;没演练过的备份等于没有备份 11
Percona XtraBackup 版本乱配 8.0 用 xb8.0+、8.4 用 xb8.4/9.x,拿 5.7 的老工具直接失败 版本矩阵查官方,升级前先验证备份工具跟得上 11
恢复演练从不碰权限/账号 数据回来了应用连不上(账号表没跟着走) 演练覆盖 mysql.user 等 sys schema 之外的库;克隆实例全链路验证 11

1.8 配置与资源

陷阱 后果 正确做法 详见
innodb_buffer_pool_size 默认值直接用 128MB 默认跑生产库,页永远在换进换出 dedicated 机器给物理内存的 50%~70%;容器里注意被 OOMKill 的余量 00、11
调 sort_buffer_size/join_buffer_size 设"全局加大" 他们是每连接/每操作分配,几百连接同时大排序直接把内存打爆 全局保持默认,会话级按需调 11
redo 容量"越大越稳" 崩溃恢复要重放的量与容量正相关;5.7 时代改大小还是停机事故(8.0.30+ 用 innodb_redo_log_capacity 在线改,旧参数已弃用未移除) 用 checkpoint age 曲线定容量,别拍脑袋 07
关慢日志省 IO / long_query_time 设 10 秒 出事时没有现场证据 slow_query_log=ON,生产常配 long_query_time=0.1~1 + log_queries_not_using_indexes(加 min_examined_row_limit 防刷屏) 08、11
磁盘满不监控 error log、binlog、临时表全写不进,实例直接不可用 数据盘/日志盘/binlog 盘分开规划 + 80% 告警;innodb_online_alter_log_max_size 打满同样是 DDL 失败现场 09、11

1.9 分库分表

陷阱 后果 正确做法 详见
分片键选错(按 user_id 分,却大量按 order_id 查) 广播全分片查询,比不分还慢 分片键=最高频等值查询的维度;另一维度用基因法/异构索引表补 12
分片数拍脑袋定当前量 扩容 = 全网数据搬迁 按五年峰值定,宁大勿小;2 的幂便于翻倍迁移 12
跨分片 ORDER BY ... LIMIT 深分页 每片各取 offset+limit 条回内存归并,N 片 × 深页 = 内存爆炸 游标翻页或汇聚层(异构到 ES) 12
雪花 ID 时钟回拨没处理 重号或抛异常,主键冲突在生产是 P0 回拨等待/借位报警,或数据库号段模式 12
跨库事务当真分布式事务做 2PC 性能与可用性双输 尽量路由单分片本地事务;确需跨片用最终一致(本地消息表/MQ) 12

1.10 程序性对象与视图

陷阱 后果 正确做法 详见
备份不带 --routines --events mysqldump 默认不导出存储过程/函数/事件,恢复出来的库缺一块逻辑且要到调用时才炸 逻辑备份固定带这两个开关(触发器默认有,过程和事件没有) 14、11
删账号前不盘 DEFINER 视图/过程/函数/触发器/事件默认按创建者权限执行,definer 账号一删全变 ERROR 1449(实测) 下线账号前 information_schema 盘 definer;共享对象用专门服务账号做 definer 14
把视图 WITH CHECK OPTION 当约束 校验发生在 BEFORE 触发器改写之后——实测 amt=1 经触发器变 202,视图闸门照样放行 约束落在列定义/检查与应用层;视图闸门只做"直写不越界"的兜底 14
binlog 开启就裸建函数 ERROR 1418(实测原文:函数须声明 DETERMINISTIC/NO SQL/READS SQL DATA 之一);有人图省事开 log_bin_trust_function_creators 绕过 如实声明特性(写进元数据,SHOW CREATE FUNCTION 可见);trust 开关是"信任创建者"不是"关掉问题" 14
AFTER 触发器里改 NEW 建触发器直接失败 ERROR 1362(实测,创建期就拦);改值请用 BEFORE NEW 可写性规则背下来:BEFORE 写 NEW,AFTER 只读 14
触发器回写触发它的基表 ERROR 1442(实测):“本语句已占用的表不能在触发器里再动”;“插入后回写汇总"这类需求在 MySQL 触发器里无解 汇总写别的表(审计/聚合表)或放应用层同一事务 14
以为多个同类触发器有优先级语法 MySQL 没有 Oracle 的 FOLLOWS/PRECEDES,同一时机+事件按创建顺序执行(实测),换序只能 DROP 重建 别让触发器互相依赖;链条超过两个就该合并成一个过程 14
SET GLOBAL event_scheduler=ON 当持久 默认 OFF 且 GLOBAL 不落盘,重启回到 OFF,“事件莫名其妙不再跑” SET PERSIST event_scheduler=ON(8.0);且注意事件上一轮未跑完时错过的调度跳过不补,逻辑须幂等 14
万行游标循环当优化 逐行循环基本必慢,且过程里的 sql_mode 冻结在创建时,全局模式改了行为却不跟着改 集合 SQL 能替代的一律改写;过程行为诡异先查 information_schema.routines.SQL_MODE 14
XA prepared 悬挂没人裁决 分支占行锁和 undo 独立存活,协调者缺位就是事故;XA RECOVER 不巡检等于不管 用 XA 必须有超时兜底与裁决流程;没有就退回本地消息表最终一致(12 章) 14、07

2. 六个最容易在生产炸掉的坑,展开讲

上面的表格适合事后检索,这六条适合在评审会上当面讲。它们的共同点:单看没错,量级一上来就错。

2.1 无索引的 UPDATE —— “我就改 5000 行”

UPDATE orders SET status='CLOSED' WHERE shop_id=42 AND created_at < '2026-01-01';

shop_id 没索引,RR 下 InnoDB 沿聚簇索引逐行扫描并给每行加 X 锁(锁的是扫过的全部行,不是命中的 5000 行)。表 3000 万行,这条语句就锁了 3000 万行,还外加间隙锁——其他所有写入排队,监控上看到"锁等待风暴”,值班第一反应 kill 慢 SQL,kill 又要先回滚几十万 undo,等分钟级。正确姿势:写之前 EXPLAIN 确认 key 命中;确实没索引,先加 (shop_id, created_at) 再改,或分批按主键改。

2.2 长事务 + MDL —— “DDL 卡住了,全库都卡了”

事务 A 开着不动(BEGIN 后没提交),DDL 来申请排他 MDL 被挡,MDL 队列里 DDL 之后的所有新查询继续排在 DDL 后面——表象是"数据库整体变慢"。处置顺序固定:sys.schema_table_lock_waits 找到持锁事务 → kill → DDL 自己跑过去 → 队列疏通。预防靠 DDL 前置检查(活跃长事务监控、lock_wait_timeout 给 DDL 会话设小一点让它自己先放弃)。

2.3 key_len 两套口径 —— 把磁盘格式规则套到 EXPLAIN 上

流传极广的口诀"varchar 长度 ≤255 字节时长度前缀按 1 算"只对 InnoDB 磁盘记录格式成立;EXPLAIN 的 key_len 一律 +2。实测 utf8mb4 下 VARCHAR(20)=82、VARCHAR(63)=254(255 字节内),VARCHAR(64)=258,全部 +2。官方文档自己的例子(utf8 VARCHAR(20) NULL 计 63)也是 +2。口径混用的直接后果是"索引到底用到了第几列"判错,优化决策跟着错。

2.4 从库读"自己的写" —— 复制延迟是业务问题不是运维问题

延迟常态 0.5s 时看起来无害,直到有人把"提交订单 → 立刻跳转订单详情"的读指到从库。用户视角就是"我下了单呢?"。解法按成本排序:强制主读(写后关键读)→ 会话粘主 → WAIT_FOR_EXECUTED_GTID_SET 等位点 → 半同步 + 延迟告警。评审时先问一句"哪些读路径跨了读写分离",比事后加缓存便宜。

2.5 INSTANT 的账 —— 免费的也要还

每次 INSTANT 变更让行格式里多叠一个版本记录,单表累积有上限(量级几十次,具体随版本演进)。超限后 ALTER 报错、且表已经"脏"到只能重建——那时候重建就是又一场计划外停机。把"高频变更表每年重建归零"写进例行维护,而不是等报错。

2.6 备份没演练过 —— 薛定谔的备份

mysqldump 退出码 0 不代表能恢复:表结构不兼容、binlog 位点错位、账号缺失,都只在恢复那天爆。最低标准的演练:每季度在克隆机/独立实例上做一次全量 + binlog 回放到指定时间点,验证业务 SQL 能跑。顺带验证恢复时长是否满足 RTO。


3. 最佳实践清单

建表与数据类型

  • 库/表/列三层显式 utf8mb4;所有表有 BIGINT UNSIGNED 自增主键 + created_at/updated_at DATETIME(3)。
  • 金额 DECIMAL、时间 DATETIME(3)、状态 TINYINT + 注释枚举值;禁用显示宽度与 ZEROFILL。
  • 单行目标 < 1KB:大字段垂直拆表,让 16KB 页装尽可能多的行。
  • 生产表全部 InnoDB + 有主键,为复制、备份、在线 DDL 三件事同时铺路。

索引

  • 联合索引口诀:等值在前、范围在后、排序列收尾;一个查询尽量被一个索引吃下(前缀 + 排序 + 覆盖)。
  • 每个索引都要回答"哪条 SQL 用它";答不上来的季度体检删掉(先 INVISIBLE 灰度)。
  • 二级索引叶子带主键:主键越窄,所有索引都跟着减肥。
  • 上线索引用 EXPLAIN 验收四件事:key 非空、type 不劣于 range、rows 与预期同量级、Extra 没有 Using filesort; Using temporary 成对出现。

SQL

  • 列清单显式;WHERE 列侧零函数零转换;分页用游标;大查询必带 LIMIT。
  • 上线 SQL 在预发用真实量级数据跑 EXPLAIN ANALYZE,估算 rows 与实际 rows 差 10 倍以上先 ANALYZE TABLE 再谈优化。
  • 复杂报表 SQL 和联机交易 SQL 物理隔离(不同连接池/不同账号配额),慢查询不能拖死核心链路。

事务

  • 事务里禁止人机交互(RPC 外部服务、发请求);默认 RR 的团队考虑统一改 RC + binlog ROW,换来更少的 gap 锁。
  • 幂等重试是业务层标配:1213 回滚重跑、1205 先诊断再动。
  • autocommit=1 保持默认,业务框架统一管边界;>60s 活跃事务必须告警。

DDL

  • 变更单三件套:预估行数、期望 ALGORITHM(显式写死)、回滚方案。ALGORITHM=INSTANT 报错就是信息,不是故障。
  • 大表(千万行以上)加索引/改类型用 gh-ost 或 pt-osc,窗口期 + 复制延迟监控 + 磁盘余量检查一次不少。
  • OPTIMIZE/重建排进业务低峰,且知道它会占 MDL。

复制与 HA

  • 开 GTID、ROW binlog、enforce_gtid_consistency=ON;从库 super_read_only=ON。
  • 半同步 + AFTER_SYNC 覆盖核心链路;切换工具(MHA/Orchestrator/云托管)要定期演练真切。
  • 读从库的每条路径先过"能不能容忍延迟"这一关;不容忍就回主。
  • 并行复制:8.4 什么都不用配(内部恒 WRITESET),只调 replica_parallel_workers,replica_preserve_commit_order 不关。

备份

  • 全量(xtrabackup 或 mysqldump --single-transaction)+ binlog 连续归档 + 异机存放,三者缺一不算备份。
  • 每季度真实恢复演练一次,记录恢复时长对照 RTO/RPO。
  • 备份账号权限最小化(RELOAD/PROCESS/REPLICATION CLIENT + 库级 SELECT),密码进密钥管理不进脚本。

配置与资源

  • innodb_buffer_pool_size = 物理内存的 50%~70%(dedicated 机器),上线前算过账而不是抄默认。
  • redo 用 innodb_redo_log_capacity 管理并按 checkpoint age 定容;innodb_flush_log_at_trx_commit=1 + sync_binlog=1 是金融默认档,降档要写明丢了什么。
  • 慢日志常开(long_query_time ≤1s)、innodb_print_all_deadlocks=ON;error log 磁盘水位 80% 告警。
  • 连接数按"应用总连接 < max_connections × 70%“配,thread_cache 让建连不再成为毛刺。

4. 生产上线检查清单

新实例/新业务上线前逐条打钩:

  • 字符集三层 utf8mb4,sql_mode 保持 8.0/8.4 默认(STRICT + ONLY_FULL_GROUP_BY 在线)
  • 所有表 InnoDB、有主键、无 MyISAM/MEMORY 混入
  • 核心 SQL 全部有 EXPLAIN 凭证(改前/改后两份计划归档)
  • 慢日志、error log、死锁日志开启且落盘位置有监控
  • 备份任务 + binlog 归档已跑通一次真实恢复
  • 复制:GTID、ROW、半同步(若要求)、super_read_only、从库延迟告警
  • MDL:DDL 前置检查脚本/流程就位,长事务告警阈值已设
  • 磁盘:数据/日志/binlog 分盘或分目录,水位告警 80%
  • 账号:应用账号无 SUPER/FILE;备份账号最小权限;无 mysql_native_password 遗留(8.4 默认已禁用,存量账号迁移 caching_sha2_password)
  • 参数:buffer pool、redo 容量、连接数三项有容量计算依据,不是"和测试环境一样”
  • 大表变更预案:预估行数、ALGORITHM 声明、工具选型、回滚路径
  • 分库分表(若用):分片键覆盖 95% 查询路由,全局 ID 时钟回拨策略验证过

5. 调试速查表

症状 第一步查什么 命令/入口
整体变慢、连接堆积 有没有长事务/锁等待风暴 SELECT * FROM information_schema.innodb_trx ORDER BY trx_started;;sys.innodb_lock_waits
一条 SQL 突然变慢 计划变没变、统计旧不旧 EXPLAIN FORMAT=TREE 对比历史;EXPLAIN ANALYZE 对 actual/estimated rows;ANALYZE TABLE
偶发 1205/1213 现场锁信息或历史死锁 performance_schema.data_locks/data_lock_waits;error log(innodb_print_all_deadlocks=ON 后 LATEST DETECTED DEADLOCK)
DDL 卡住不动 MDL 排队 sys.schema_table_lock_waits;SHOW PROCESSLIST 看 “Waiting for table metadata lock”
从库延迟尖峰 大事务 / 无主键表 UPDATE / 单线程回放 error log 与 SHOW REPLICA STATUS\G(Seconds_Behind_Source、Last_Error);主库审计当时的大 DML
写入周期性卡顿 redo 逼近写满、刷脏跟不上 SHOW ENGINE INNODB STATUS\G 的 checkpoint age;Innodb_buffer_pool_pages_flushed 突刺
内存告急 buffer pool 外的大块会话缓冲 排序/临时表统计:Created_tmp_disk_tables、Sort_merge_passes
备份失败 版本矩阵与磁盘 xb 日志;df -h;检查备份窗口是否与 purge/长事务冲突
表膨胀、查询变慢但行数没涨 大量 UPDATE/DELETE 未回收空洞 information_schema.tables 的 data_free;低峰 OPTIMIZE/重建
连不上/认证失败 插件与账号 8.4:mysql_native_password 默认禁用,老客户端需 --mysql-native-password=ON 或升级驱动用 caching_sha2
视图/存储过程"昨天还好好的"今天报 1449 definer 账号是不是被删了、sql_mode 冻结值 information_schema.{views,routines,triggers,events} 盘 DEFINER/SQL_MODE(见 14 章第 6 节)
定时事件不再执行 event_scheduler 掉回 OFF(重启)或上轮跳过 SELECT @@event_scheduler;information_schema.events 看 STATUS/execute_at;改 SET PERSIST

实战建议:这一章的正确用法不是通读,而是三件事——① 上线评审时把第 4 节清单过一遍;② 出事时跳到第 5 节按症状入口;③ 每季度把第 1 节表格里"我们踩过"的列做标记,踩过的坑比没踩的更值得复训。全手册各章(00~12、14)负责"为什么",这一章负责"记住"。