第 12 章:按证据缩小一次数据库故障
用户说“下单很慢”,不一定是某条 SELECT 扫描太多。请求可能在应用队列、连接池、数据库锁、执行阶段或返回网络上等待。排障先确定阶段,再提出改动。
12.1 保存能够关联的最小信息
应用侧应记录请求标识、数据库操作类别、事务阶段、错误码、总耗时与重试次数。连接池等待最好单独测量。日志中不默认写完整 SQL 参数、密码或个人数据,必要诊断应经过脱敏与访问控制。
数据库侧结合连接与事务信息、实际锁等待、执行计划和受控的慢查询记录。一个应用超时与一个数据库错误码不是同一份证据,应保留两端事实,而不是只记一行“数据库失败”。
12.2 用问题选择工具
| 现象 | 先确认 | 可以补充的证据 |
|---|---|---|
| 获取连接前就耗时 | 连接池容量与占用 | 池等待、活跃连接、事务时长 |
| UPDATE 等待 | 谁阻塞谁 | data_lock_waits、data_locks、事务与连接 |
| 查询扫描过多 | 实际访问路径 | EXPLAIN、统计信息、返回与扫描规模 |
| 收到 1213 | 受害事务与锁顺序 | 最近死锁记录、业务事务完整性 |
| 收到 1205 | 超时配置与前序语句 | rollback_on_timeout、当前事务状态 |
| COMMIT 后连接异常 | 结果是否已提交 | 原业务键、可信数据库查询、恢复记录 |
这些工具应在权限与负载允许的条件下使用。诊断查询本身也可能消耗资源,不能把大范围抓取当成无成本操作。
12.3 每次改动只验证它能证明的事情
加索引后计划变化,应重新检查关键读写与锁行为;缩短事务后等待减少,应验证业务原子性仍成立;增加重试后错误减少,还应检查尝试放大、延迟和重复副作用。
没有真实负载实验时,不给出吞吐提升百分比。没有断电和恢复实验时,不把正常进程重启写成断电持久性证明。本书的 CI 实验证明所列事务与故障窗口,生产容量与高可用需要单独验收。
12.4 复盘保留原始事实与决策
一次好的复盘能区分症状、证据、根因、处置和长期改造。例如“终止长事务后等待消失”是处置效果;根因可能是应用在事务里等待外部接口。长期改造应缩短边界并增加相应并发回归,而不只是继续调大连接池。
练习。 一次故障通过重启应用暂时消失。请写出至少三种仍未被证明的根因假设,以及下一次发生时应提前采集的信息,避免再次仅依靠重启确认问题。