MySQL 线上问题实战演练:复合故障排查与系统设计

概述

衔接前文

前 10 篇从 MySQL 分层架构到 InnoDB 存储引擎,从事务 MVCC 到行锁与间隙锁,从 SQL 优化器到主从复制与分库分表,再到慢查询诊断、连接管理以及反模式排查宝典,构建了完整的理论体系。然而,真实的生产故障从来不会按章节发生------它们往往是多因素叠加的复合体:一个慢查询可能同时牵扯索引统计信息过时、Buffer Pool 命中率下降和连接池配置不当;一条看似普通的 DDL 可能因一个未提交的长事务而拖垮整个系统。本文作为系列的终极实战篇,通过 12 个真实级别的复合场景,帮助你在压力下综合运用所有知识,建立真正的诊断直觉和架构决策能力。

总结性引言

凌晨三点,PagerDuty 响起。PMM 显示 CPU 飙到 95%,慢查询日志在 10 分钟内暴涨 50 倍,SHOW PROCESSLIST 里堆积了上百个 Sending data 状态------你只有 30 分钟。是索引统计信息过期?是 Buffer Pool 命中率突然下降?还是某个刚上线的 DDL 操作锁住了关键表?真实世界中,这些可能性往往同时成立。更危险的是那些尚未触发告警的隐蔽故障------主从数据已在悄悄漂移,索引统计信息正在缓慢偏离,直到某天业务逻辑全面异常。本文将模拟 10 个这样的复合故障场景和 2 个高难度系统设计挑战,从告警响起(或数据校验发现异常)的第一分钟开始,按时间线逐步推演诊断决策,最终定位根因并给出修复方案。

核心要点

  • 10 个复合故障排查场景:按难度递进(单一领域 → 跨两领域 → 跨三领域),覆盖索引、事务锁、复制、连接池、分库分表、DDL 等领域的交叉故障,含隐蔽故障场景。
  • 2 个系统设计挑战:亿级订单系统数据架构设计与跨机房灾备方案,含具体压测验证策略。
  • 统一场景结构:故障排查采用"初始告警→时间线推演→诊断工具链→根因定位→紧急处理→长期修复→事后复盘",系统设计采用"需求→方案→验证→风险评估"。

文章组织架构图

flowchart TB A["1-3. 单一领域深层故障
场景 1-3"] --> B["4-7. 跨两领域交互故障
场景 4-7"] B --> C["8-10. 跨三领域复合故障
场景 8-10"] C --> D["11-12. 系统设计挑战"] D --> E["13. 从诊断到架构:
完整能力图谱总结"] A --> A1["场景1: 索引+Buffer Pool"] --> A2["场景2: 长事务+连接池"] --> A3["场景3: 复制+分片"] B --> B1["场景4: 半同步+网络"] --> B2["场景5: DDL锁+雪崩"] --> B3["场景6: 慢查询+超时"] --> B4["场景7: 分片JOIN+GC"] C --> C1["场景8: Binlog漂移"] --> C2["场景9: 数据清理+死锁"] --> C3["场景10: 连接池扩容"] classDef level1 fill:#d0e1f9,stroke:#2f4b7c classDef level2 fill:#ffe0b2,stroke:#f57c00 classDef level3 fill:#f9d0d0,stroke:#c62828 class A,A1,A2,A3 level1 class B,B1,B2,B3,B4 level2 class C,C1,C2,C3 level3

架构图说明

图表主旨概括 :本图展示了全文 13 个模块按难度递进的逻辑路径------从单一领域深层故障到跨两领域交互故障,再到跨三领域复合故障,最后以系统设计挑战和能力图谱总结收尾。
逐层分解 :三层故障排查场景用不同颜色区分难度等级,蓝色为单一领域,橙色为跨两领域,红色为跨三领域,箭头表示推理能力的逐级构建。
设计原理映射 :递进结构对应认知负荷理论,先建立单领域深层诊断能力,再训练跨领域关联分析,最后挑战多故障叠加的极限推演,使读者逐步形成从现象到根因的快速诊断通路。
工程联系与关键结论:线上故障排查不是知识的简单调用,而是在时间压力和部分信息缺失下的推演能力。通过 12 个复合场景的刻意练习,建立从现象到根因的快速诊断通路,是区分初级和高级数据库工程师的关键。


故障排查类场景

场景 1:索引统计信息过时 + 优化器选错索引 + Buffer Pool 命中率下降

初始告警

凌晨 02:15,PagerDuty 触发多条告警:PMM 显示 CPU Usage 从基线 30% 飙升至 95%,Slow Queries 从每分钟 5 条暴涨至 250 条,Buffer Pool Hit Ratio 从 99% 骤降至 60%。业务监控显示订单查询接口 getOrderList 的 P99 延迟从 50ms 升至 20s,部分请求返回 504 Gateway Timeout。

时间线推演

T+0min(接警)

  • SHOW GLOBAL STATUS LIKE 'Threads_running' 返回 86,正常值为 8~12。
  • SHOW FULL PROCESSLIST 发现 60% 的线程处于 Sending data 状态,SQL 文本均为 SELECT order_id, user_id, amount FROM orders WHERE user_id = ? AND status = 'PAID' ORDER BY create_time DESC LIMIT 20
  • vmstat 1 显示 wa 列高达 60%,r 列 16 超过物理核数 8,磁盘 I/O 严重饱和。

T+5min(初步诊断)

  • SHOW ENGINE INNODB STATUS\GBUFFER POOL AND MEMORY 段:

    sql 复制代码
    Buffer pool hit rate 607 / 1000
    Free buffers 12
    Database pages 523114
    Old database pages 521000
    Pages made young 0, not young 500000

    Free buffers 极低,且大量页面被标记为 old,说明冷数据挤占了热点页。(关联第 2 篇 InnoDB Buffer Pool LRU 变体算法的 old/new 子链表机制

  • sys.statement_analysis 查询 Top 资源消耗语句:

    sql 复制代码
    SELECT query, rows_examined_avg, rows_sent_avg 
    FROM sys.statement_analysis 
    WHERE db = 'orders' ORDER BY rows_examined_avg DESC LIMIT 5;

    问题查询 rows_examined_avg = 4,820,000,但 rows_sent_avg = 20,扫描行数与返回行数比例超过 200000:1。

  • SHOW INDEX FROM orders 显示存在 idx_user_id_status (user_id, status)idx_create_time (create_time) 两个索引。

T+15min(深入分析)

  • EXPLAIN ANALYZE 执行慢 SQL(使用一个典型 user_id=12345):

    sql 复制代码
    -> Limit: 20 row(s)  (actual time=18342..18342ms rows=20 loops=1)
      -> Sort: orders.create_time DESC  (actual time=18342..18342ms rows=20 loops=1)
          -> Filter: (orders.`status` = 'PAID')  (actual time=0.065..18250ms rows=12 loops=1)
              -> Index lookup on orders using idx_user_id_status (user_id=12345)  (cost=12.5 rows=100) (actual time=0.064..18250ms rows=4.8e6 loops=1)

    优化器估算 rows=100,实际 rows=4.8e6,统计信息偏差达 48000 倍。(根因详见第 5 篇优化器代价估算模型中统计信息对 rows 估算的影响

  • SHOW VARIABLES LIKE 'innodb_stats_persistent_sample_pages' 返回 20。这意味着 InnoDB 仅对每个索引随机抽取 20 页来估算基数,对于 5 亿行的巨型表严重不足。

  • sys.schema_index_statistics 查看该索引使用趋势,发现 idx_user_id_statusrows_selected 在最近两周逐渐减少,暗示优化器已开始"不信任"该索引。

T+30min(根因确认)

  • 临时将 innodb_stats_persistent_sample_pages 调至 200,执行 ANALYZE TABLE orders,再次 EXPLAIN ANALYZErows 估算修正至约 4.5M。优化器此时选择了 idx_create_time 进行全索引扫描 + 过滤,执行时间仍超过 10s,但至少统计信息真实。
  • 进一步查询发现该 user_id 对应数据占表总量 40%,即使走 idx_user_id_status,回表 480 万行也是灾难。理想情况下应存在 (user_id, status, create_time) 覆盖索引,避免回表和排序。
  • 根因完整链:统计信息过时 → 优化器误判成本选择低效索引 → 大量回表随机读 → 冷数据涌入 Buffer Pool 触发 LRU 淘汰热点页 → 命中率骤降 → 磁盘 I/O 飙升 → CPU 被 I/O 等待占满。

诊断工具链调用

顺序 命令/工具 作用 关联原理
1 SHOW GLOBAL STATUS + PROCESSLIST 评估活跃连接与负载概貌 第 9 篇连接管理
2 vmstat 1 确认 I/O 等待瓶颈 ---
3 SHOW ENGINE INNODB STATUS Buffer Pool 命中率与 LRU 状态 第 2 篇 InnoDB 架构
4 sys.statement_analysis 定位高扫描行查询 第 8 篇慢查询诊断
5 EXPLAIN ANALYZE 对比估算行数 vs 实际行数 第 5 篇优化器代价模型
6 SHOW VARIABLES LIKE 'innodb_stats_persistent_sample_pages' 检查采样参数 第 5 篇统计信息
7 sys.schema_index_statistics 索引使用趋势变化 第 2 篇索引结构

根因定位

  1. 统计信息过时innodb_stats_persistent_sample_pages = 20(默认)对 5 亿行表严重不足,导致优化器大幅低估 user_id=12345 的行数,选择了效率低下的 idx_user_id_status 索引。优化器代价估算模型的根因详见第 5 篇统计信息采样与索引相关性分析。
  2. Buffer Pool 污染:480 万行回表产生大量随机物理读,冷数据页通过 LRU 的"老年代"机制迅速占据 Buffer Pool,将原本缓存的热点数据(如其他用户频繁查询的订单)驱逐出去,命中率从 99% 跌至 60%,CPU 因等待磁盘 I/O 而飙高。Buffer Pool 的 LRU 变体及老年代/新生代机制详见第 2 篇 InnoDB 存储引擎。
  3. 索引设计缺陷 :即使统计信息准确,(user_id, status) 不包含 create_time,导致 ORDER BY 需要额外 filesort,且每次查询均需回表。缺乏覆盖索引是性能瓶颈的底层结构因素。

紧急处理

  • 应用层限流与路由切换 :在 ShardingSphere 配置中临时将 getOrderList 查询强制路由至从库,并设置 SQL 执行超时 5s,减轻主库压力。风险:从库同样存在统计信息问题,需要同步执行 ANALYZE TABLE
  • 索引提示 :在应用代码中紧急加入 FORCE INDEX(idx_create_time) 强制使用纯时间排序索引,利用索引顺序避免 filesort 和部分回表(但状态过滤仍是后置过滤)。风险:若 user_id 数据量极大,仍需扫描大量无效时间范围行,但可有效减少 Buffer Pool 抖动。
  • 缓存预热 :执行 SELECT /*+ READ_BUFFER_POOL(size=4096) */ user_id FROM orders WHERE user_id IN (热点ID列表) 尝试预热部分热点数据页(MySQL 8.0 可通过 Hint 指定读取时直接加载到 LRU 新生代)。风险:短暂加剧 I/O 竞争。

长期修复

  1. 调整采样参数 :全局设置 innodb_stats_persistent_sample_pages = 200 并重启收集统计信息。对大表单独设置 STATS_SAMPLE_PAGES=500。预期效果:EXPLAINrows 估算与实际偏差 < 20%,优化器选择正确执行计划。
  2. 索引重构 :创建覆盖索引 (user_id, status, create_time)。验证命令:EXPLAIN SELECT ... 显示 Extra: Using index,实际执行时间从 20s 降至 10ms 以内。
  3. 监控增强 :在 PMM 上添加告警规则------当 Buffer Pool 命中率 5 分钟内降幅超过 20% 时触发通知;增加 sys.schema_index_statistics 的定时快照对比,一旦发现某索引的 rows_selected 突降则发出告警。
  4. 统计信息更新策略 :在业务低峰期(如凌晨 4 点)通过 cron 对核心表执行 ANALYZE TABLE,并记录执行前后的索引基数变化。

事后复盘

本次故障暴露了统计信息采样策略与表规模增长不匹配的深层风险。应加入每周巡检项:检查大表的 rows 估算偏差(通过采样查询对比 COUNT(*) 与统计信息基数)。此外,索引设计评审必须前置考虑 ORDER BY 和覆盖索引,避免依赖优化器"弥补"设计不足。

sequenceDiagram participant App as 应用层 participant Optimizer as 优化器 participant InnoDB as InnoDB participant Disk as 磁盘 participant BP as Buffer Pool Note over App, BP: "T+0min CPU 95% 命中率降至60%" App->>Optimizer: "SELECT ... WHERE user_id=? AND status=? ORDER BY create_time" Optimizer->>InnoDB: "读取统计信息估算 rows=100" Optimizer->>InnoDB: "选择 idx_user_id_status 索引" InnoDB->>Disk: "按索引顺序回表读取480万行数据页" Disk-->>InnoDB: "大量随机读 I/O" InnoDB->>BP: "冷数据页进入LRU老年代" BP->>BP: "淘汰热点页 Free buffers=12" InnoDB-->>App: "20s 后返回数据 (大量 timeout)" Note over App, BP: "T+15min EXPLAIN ANALYZE 发现 actual rows=4.8e6" Note over App, BP: "T+30min ANALYZE TABLE + 索引重构恢复"

图表主旨概括 :该序列图完整呈现了统计信息偏差导致优化器选择错误索引,进而引发 Buffer Pool 污染和 I/O 瓶颈的故障链路。
逐层分解 :应用层发出带 ORDER BY 的条件查询 -> 优化器基于错误统计(rows=100)选择索引 -> InnoDB 执行回表操作产生海量随机物理读 -> 磁盘 I/O 飙升 -> Buffer Pool 中热点页被冷数据淘汰 -> 命中率骤降,查询超时。
设计原理映射 :体现优化器对统计信息的强依赖、InnoDB Buffer Pool 的 LRU 淘汰机制以及覆盖索引对 I/O 的消减作用。
工程联系与关键结论:统计信息采样策略必须与表规模匹配;索引设计应优先考虑覆盖查询的排序字段以避免回表和 filesort。


场景 2:长事务阻塞 Purge + 无索引外键 + 连接池泄漏

初始告警

磁盘使用率告警:/data/mysql/undo/ 分区使用率在 2 小时内从 30% 飙升至 90%。应用日志中大量 Lock wait timeout exceeded; try restarting transaction,错误量 500 次/分钟。PMM 连接数监控显示 Threads_connected 达到 max_connections 的 90%(270/300)。

时间线推演

T+0min(接警)

  • df -h 确认 Undo 表空间所在分区使用率 92%,information_schema.INNODB_TABLESPACESundo_002 大小为 120GB,正常应 < 30GB。
  • SHOW GLOBAL STATUS LIKE 'Innodb_history_list_length' 返回 85000,Purge 线程严重滞后。(关联第 3 篇 MVCC 版本链与 Purge 线程协作机制
  • SHOW PROCESSLISTSleep 状态的连接有 150 个,其中 40 个连接 Time 超过 300 秒。

T+5min(初步诊断)

  • information_schema.INNODB_TRX 查询:

    sql 复制代码
    SELECT trx_id, trx_state, trx_started, trx_mysql_thread_id,
           TIMESTAMPDIFF(SECOND, trx_started, NOW()) AS duration_sec
    FROM information_schema.INNODB_TRX
    WHERE trx_state = 'RUNNING';

    发现事务 581234 已运行 7200 秒(2 小时),trx_query IS NULL,说明是一个空闲未提交事务。关联 sys.session 确认连接来源为某后台任务服务。

  • SHOW ENGINE INNODB STATUSTRANSACTIONS 段中 History list length 85000Undo log entries 1500000 印证 Undo 膨胀。

T+15min(深入分析)

  • sys.schema_unused_indexes 检查子表 order_items,发现外键列 order_id 上无索引。

  • SHOW ENGINE INNODB STATUSLATEST DETECTED DEADLOCK 段显示:

    sql 复制代码
    *** (1) TRANSACTION: DELETE FROM orders WHERE order_id = 12345
    *** (2) TRANSACTION: INSERT INTO order_items (order_id, ...) VALUES (12345, ...)

    由于 order_items.order_id 无索引,DELETE orders 时需全表扫描 order_items 来校验外键约束,并为所有扫描行加 Next-Key Lock,与并发 INSERT 冲突。(根因详见第 4 篇 Next-Key Lock 与外键加锁规则

  • 连接池监控 /actuator/metrics/hikaricp_connections_active 显示大量连接处于 Sleep 状态且未释放,检查应用配置发现 maxLifetime 未设置,minimumIdle 等于 maximumPoolSize,连接池泄漏严重。(根因详见第 9 篇连接池与 maxLifetime 协调不等式

T+30min(根因确认)

通过 performance_schema.threadsinformation_schema.INNODB_TRX 关联,定位到长事务所属 PROCESSLIST_ID=45,与应用团队核对确认是一个定时任务使用了 SELECT ... FOR UPDATE 后因代码异常未 COMMIT/ROLLBACK。三个现象完整串联:长事务阻止 Purge 导致 Undo 膨胀 → 无索引外键导致锁升级和锁等待 → 连接池泄漏加剧连接数紧张。

诊断工具链

  1. df -h + information_schema.INNODB_TABLESPACES --- Undo 表空间大小监控
  2. SHOW GLOBAL STATUS LIKE 'Innodb_history_list_length' --- Purge 延迟程度
  3. information_schema.INNODB_TRX --- 定位超长事务
  4. SHOW ENGINE INNODB STATUS --- History list length、锁等待分析
  5. sys.schema_unused_indexes --- 外键列索引检查
  6. HikariCP /actuator/metrics --- 连接池泄漏分析

根因定位

  1. 长事务阻塞 Purge :一个持续 2 小时的空闲未提交事务持有旧 ReadView,Purge 线程无法清理该 trx_id 之后的所有 Undo Log,导致 Undo 表空间持续膨胀。Purge 线程依赖 ReadView 确定可清理版本,任何未结束的最老事务均会卡住 Purge 推进。根因详见第 3 篇 MVCC 版本链与 Purge 线程机制。
  2. 无索引外键导致锁升级 :子表 order_items 外键列 order_id 没有索引,当父表执行 DELETE 时,InnoDB 必须全表扫描子表来校验外键约束,并对所有行加 Next-Key Lock(而不是仅对匹配行加锁),直接阻塞并发 INSERT,产生锁等待和死锁。外键加锁的规则详解见第 4 篇锁分类体系。
  3. 连接池泄漏 :应用代码未正确释放连接,HikariCP 未配置 maxLifetime,导致大量 Sleep 连接长期不归还,连接数不断累积逼近 max_connections。连接池参数协同公式见第 9 篇全局协调不等式。

紧急处理

  • 终止长事务KILL 45 立即结束空闲事务,Purge 线程恢复工作,Undo 空间不再增长。
  • 释放泄漏连接 :对 Time > 300 的 Sleep 连接执行 KILL,或临时调低 wait_timeout=120 由 MySQL 自动清理。风险:可能误杀业务连接,需配合应用方确认。
  • 限流父表 DELETE :临时在应用层暂停针对 orders 的大批量删除操作,避免锁冲突进一步恶化。

长期修复

  1. 事务规范 :所有 SELECT ... FOR UPDATE 必须包裹在 try-finally 中,确保 COMMIT/ROLLBACK,并设置 innodb_lock_wait_timeout=10 防止等待过久。
  2. 外键列加索引ALTER TABLE order_items ADD INDEX idx_order_id (order_id); 使外键约束检查走索引,消除全表加锁。验证:EXPLAIN DELETE FROM orders WHERE order_id=?order_items 的访问类型为 ref
  3. 连接池参数标准化 :设置 maxLifetime=580s(< wait_timeout=600s),idleTimeout=300s,并开启 keepaliveTime=60000connectionTestQuery=SELECT 1,防止连接泄漏和失效。
  4. 监控增强 :增加 innodb_trx_duration_sec > 3600 的 PMM 告警;增加 Threads_connected / max_connections > 0.8 的阈值告警。

事后复盘

长事务和连接池泄漏都属于"静默"型故障,初期不会立即暴露,一旦叠加外键索引缺陷就会瞬间雪崩。应建立每周连接池占用率巡检和活跃长事务清单审计。

sequenceDiagram participant Task as 后台任务 participant TX as 事务系统 participant Purge as Purge线程 participant Undo as Undo表空间 participant DEL as DELETE操作 participant Item as order_items表 participant Pool as 连接池 Note over Task, Pool: T+0min Undo空间90% 连接数270/300 Task->>TX: "BEGIN;SELECT ... FOR UPDATE" Task--X Task: 应用异常未 COMMIT TX->>Purge: 持有旧ReadView 阻塞Purge Purge--X Undo: 无法清理Undo Log Undo->>Undo: 持续膨胀 (2h +50GB) DEL->>Item: DELETE orders WHERE order_id=? Item-->>Item: 外键列无索引 全表扫描加Next-Key Lock Item-->>DEL: 阻塞并发INSERT 锁等待/死锁 Pool->>Pool: 连接泄漏 maxLifetime未设置 Sleep堆积 Pool-->>Task: 新连接请求排队 超时

图表主旨概括 :该序列图揭示了长事务、无索引外键、连接池泄漏三个故障点如何相互关联并逐级放大的过程。
逐层分解 :后台事务未提交 -> 阻塞 Purge -> Undo 膨胀;DELETE 因外键无索引全表加锁 -> 阻塞并发写入;连接池因未配置生命周期 -> Sleep 连接堆积 -> 最终连接数逼近上限。
设计原理映射 :体现 MVCC 中 ReadView 对 Purge 的阻塞作用、外键约束检查时的加锁机制、连接池参数对资源回收的控制。
工程联系与关键结论:多因素叠加故障排查需要抓住"时间最长的事务"和"锁持有者"两条主线,快速定位根源。


场景 3:主从延迟 + 读写分离配置错误 + 分片键倾斜

初始告警

业务运营反馈:"用户支付完成后订单状态未更新,刷新后仍显示待支付"。PMM 显示分片 shard_3shard_7 的从库延迟 Seconds_Behind_Master 从 0 秒飙升至 300 秒,其他分片从库延迟正常(< 1s)。

时间线推演

T+0min(接警)

  • SHOW SLAVE STATUS\G 针对延迟从库:

    makefile 复制代码
    Seconds_Behind_Master: 300
    Retrieved_Gtid_Set: 0a1b2c3d-...:1-158000
    Executed_Gtid_Set: 0a1b2c3d-...:1-130000

    差距为 28000 个 GTID,且 Relay_Log_Pos 变化缓慢。

  • 主库 SHOW PROCESSLIST 显示有一个运行 5 分钟的 UPDATE user_orders SET status='COMPLETED' WHERE order_id IN (大列表),涉及 50 万行。

T+5min(初步诊断)

  • performance_schema.replication_applier_status_by_worker 查询:

    sql 复制代码
    SELECT worker_id, last_applied_transaction_original_commit_timestamp,
           APPLYING_TRANSACTION_ORIGINAL_COMMIT_TIMESTAMP
    FROM performance_schema.replication_applier_status_by_worker;

    发现 Worker 0 回放的事务时间戳滞后 280 秒,Worker 1 仅滞后 20 秒,并行复制 Worker 负载严重不均衡。(关联第 6 篇并行复制策略与 binlog_transaction_dependency_tracking

  • 检查 ShardingSphere 读写分离配置:

    yaml 复制代码
    readwrite-splitting:
      data-sources:
        ms_ds:
          write-data-source-name: ds_master
          read-data-source-names: ds_slave0,ds_slave1
          load-balancer-name: ROUND_ROBIN
          transaction-aware: false   # 关键:未开启事务感知

    问题:事务提交后立刻发起的读请求可能路由到延迟从库,读到旧数据。

T+15min(深入分析)

  • sys.schema_table_statistics 查询各分片数据量:

    sql 复制代码
    SELECT TABLE_SCHEMA, TABLE_NAME, TABLE_ROWS
    FROM information_schema.TABLES
    WHERE TABLE_NAME LIKE 'user_orders_%';

    分片 3 和 7 的 TABLE_ROWS 分别为 9800 万和 1.02 亿,而其他分片平均 3500 万,数据倾斜度约 3 倍。

  • 分析分片键 user_id % 16,业务用户 ID 为从第三方同步的递增 ID,导致特定模数分片写入集中,热点分片承受更大复制压力。

T+30min(根因确认)

综合判断:大事务写入导致 Binlog 产生大量事件 → 热点分片从库单 Worker 回放滞后 → 读写分离未感知事务边界,读请求落在延迟从库 → 用户体验"订单状态不回显"。

诊断工具链

  1. SHOW SLAVE STATUS --- 从库延迟关键指标
  2. performance_schema.replication_applier_status_by_worker --- Worker 级延迟分析
  3. SHOW PROCESSLIST --- 主库活跃大事务
  4. sys.schema_table_statistics --- 分片数据量分布
  5. ShardingSphere 配置文件 --- 读写分离策略

根因定位

  1. 主从延迟 :一个大事务(50 万行更新)在提交时一次性写入 Binlog,从库 SQL 线程必须以单线程方式回放(即使开启并行复制,该大事务本身也可能因 WRITESET 依赖无法并行),导致延迟飙升至 300 秒。根因详见第 6 篇主从复制延迟原理与并行复制。
  2. 读写分离配置错误 :ShardingSphere 未配置 transaction-aware: true,事务提交后应用立刻发起的读请求无法保证强一致性,从库可能尚未回放完毕。根因详见第 7 篇 ShardingSphere 读写分离策略与事务感知。
  3. 分片键倾斜user_id % 16 因用户 ID 分配规律导致数据分布不均,两个热点分片数据量是其余分片的 3 倍,从库复制压力远高于其他分片,延迟放大效应显著。根因详见第 7 篇分片键选择与数据均匀性。

紧急处理

  • 读强制走主 :在 ShardingSphere 动态配置中开启 transaction-aware: true,并利用 HintManager.getInstance().setWriteRouteOnly() 在关键支付接口中强制读主库。
  • 延迟从库摘流 :临时将延迟分片的从库权重设为 0,读写分离负载均衡跳过该实例,待 Seconds_Behind_Master 降为 0 后恢复。
  • 限流大事务:与业务沟通,将大批量更新拆分为每批 1000 行,并增加提交间隔。

长期修复

  1. 分片键优化 :改用雪花算法生成的 order_id 作为分片键(order_id % 256),基于时间戳的分布式 ID 天然均匀。保留 user_id 映射表或通过基因法在 order_id 中嵌入 user_id 的低位,支持按用户查询时精确路由。
  2. 并行复制增强 :配置 slave_parallel_type=LOGICAL_CLOCKslave_parallel_workers=16,并设置 binlog_transaction_dependency_tracking=WRITESET 最大化并行度。
  3. 读写分离策略 :配置 ShardingSphere 的 transaction-awaretrue,并对同一事务内及事务提交后 3 秒内的读强制走主,可通过扩展 ReadQueryLoadBalanceAlgorithm 实现。
  4. 分片数据量监控 :创建周度巡检任务,计算 MAX(TABLE_ROWS)/MIN(TABLE_ROWS) 比值,超过 1.5 即告警。

事后复盘

主从延迟不能只看平均值,需要细化到分片粒度。读写分离的一致性保障必须与事务边界对齐,否则会造成用户可感知的数据不一致。


场景 4:半同步超时降级 + maxLifetime 错配 + 网络抖动

初始告警

应用日志出现少量 Communications link failure 错误(约 20 次/分钟)。PMM 面板显示半同步状态 Rpl_semi_sync_master_statusON 降级为 OFF,且 Rpl_semi_sync_master_yes_tx 计数器停滞。

时间线推演

T+0min(接警)

  • SHOW STATUS LIKE 'Rpl_semi_sync_master_status' 返回 OFF
  • SHOW VARIABLES LIKE 'rpl_semi_sync_master_timeout'10000(10 秒)。
  • ping -c 100 <slave_ip> 显示在 T-6min 时刻丢包率 30%,持续约 12 秒后恢复,网络团队确认交换机在该时间进行了主备倒换。

T+5min(初步诊断)

  • 由于网络抖动导致从库 ACK 延迟超过 10 秒,主库自动将半同步降级为异步。降级动作记录在错误日志中:

    less 复制代码
    [Warning] Timeout waiting for reply of binlog (file: mysql-bin.001234, pos: 567890), semi-sync up to file mysql-bin.001234, position 567800.
    [Note] Semi-sync replication switched OFF.
  • 连接池监控 /actuator/metrics 显示 hikaricp_connections_timeout_total 计数器增长。同时 SHOW VARIABLES LIKE 'wait_timeout' 为 300,HikariCP 配置 maxLifetime=600,违反 maxLifetime < wait_timeout 不等式。(根因详见第 9 篇全局协调不等式

T+15min(深入分析)

  • 由于 MySQL 端 wait_timeout=300s,连接空闲 300 秒后被 MySQL 端主动关闭。但应用连接池认为连接仍有效(maxLifetime=600s),当应用 borrow 到一个已被关闭的连接时,抛出 Communications link failure
  • SHOW ENGINE INNODB STATUS 及 PMM 面板显示连接池活跃连接数正常,但因失效连接导致部分请求重试,加重系统负载。

T+30min(根因确认)

网络抖动是外因,但半同步降级后未自动恢复(需手动重新启用或依赖 rpl_semi_sync_master_enabled 持久化与重启),同时连接池超时参数错配放大了连接失效的影响面。

诊断工具链

  1. SHOW STATUS LIKE 'Rpl_semi_sync_master_status' --- 半同步状态
  2. 错误日志 + 网络监控 --- 确认网络事件时间点
  3. SHOW VARIABLES LIKE 'wait_timeout' --- MySQL 连接超时
  4. /actuator/metrics 中的 hikaricp_connections_timeout_total --- 连接池超时指标
  5. dmesg / 交换机日志 --- 根因验证

根因定位

  1. 半同步超时降级 :网络交换机抖动导致从库 ACK 确认延迟超过 rpl_semi_sync_master_timeout(10s),主库为保障写入可用性自动将半同步降级为异步,产生数据丢失窗口。降级机制和恢复策略详见第 6 篇半同步复制 AFTER_SYNC
  2. maxLifetime 错配maxLifetime=600 > wait_timeout=300,MySQL 端先于连接池断开空闲连接,应用在借用连接时发生 Communications link failure。根因详见第 9 篇全局协调不等式 maxLifetime < wait_timeout 的推导与验证。

紧急处理

  • 恢复半同步 :执行 SET GLOBAL rpl_semi_sync_master_enabled = ON; 并确认 SHOW STATUS LIKE 'Rpl_semi_sync_master_status' 恢复为 ON
  • 调整连接池参数 :通过配置中心将 maxLifetime 修改为 280s,并设置 keepaliveTime=60000,应用滚动生效。
  • 清理失效连接 :重启或通过 SHOW PROCESSLIST 手工 KILL 处于 SleepTime > 280 的连接。

长期修复

  1. 半同步策略 :适当缩短 rpl_semi_sync_master_timeout=5000,并配置 rpl_semi_sync_master_wait_for_slave_count=1。同时开发监控脚本,每分钟检查半同步状态,若为 OFF 且网络已恢复,自动尝试重新启用。
  2. 连接池参数标准 :强制 maxLifetime = wait_timeout - 20s,并全局审计所有数据源的参数配置。
  3. 网络冗余:数据库与从库间采用双上联链路,避免单点网络抖动引发超时。
  4. 监控增强 :增加 Rpl_semi_sync_master_status 指标告警,一旦为 OFF 立即通知;增加 hikaricp_connections_timeout_total 的增长速率告警。

事后复盘

半同步降级是无告警静默发生的典型,必须建立主动探测机制。连接池超时参数与 MySQL 参数的一致性必须纳入部署检查清单,防止"配置漂移"。


场景 5:DDL 锁表 + metadata_lock 等待 + 业务超时雪崩

初始告警

全线业务接口响应时间从 P99 50ms 飙升至 30s,网关返回大量 504 超时。PMM 显示 MySQL 连接数从 60 猛增至 200(接近上限 250),应用连接池等待队列长度 > 100。

时间线推演

T+0min(接警)

  • SHOW FULL PROCESSLIST 显示 180 个会话 State = 'Waiting for table metadata lock',目标表 orders
  • 同时存在一个会话正在执行 ALTER TABLE orders ADD COLUMN remark VARCHAR(500),状态为 Waiting for table metadata lock。说明 DDL 本身也在等待锁。

T+5min(初步诊断)

  • 查询 performance_schema.metadata_locks 分析锁等待链:

    sql 复制代码
    SELECT 
      mdl.OBJECT_NAME, mdl.LOCK_TYPE, mdl.LOCK_STATUS, 
      mdl.OWNER_THREAD_ID, t.PROCESSLIST_ID,
      s.trx_state, s.trx_autocommit
    FROM performance_schema.metadata_locks mdl
    JOIN performance_schema.threads t ON mdl.OWNER_THREAD_ID = t.THREAD_ID
    LEFT JOIN information_schema.INNODB_TRX s ON t.PROCESSLIST_ID = s.trx_mysql_thread_id
    WHERE mdl.OBJECT_NAME = 'orders';

    结果清晰:

    • LOCK_TYPE=SHARED_READ, LOCK_STATUS=GRANTED --- 持有者:PROCESSLIST_ID=23,事务状态 RUNNINGtrx_autocommit=0,已空闲 10 分钟。
    • LOCK_TYPE=EXCLUSIVE, LOCK_STATUS=PENDING --- 等待者:DDL 操作。
    • 后面堆积大量 LOCK_TYPE=SHARED_READ, LOCK_STATUS=PENDING --- 业务读写请求。

T+15min(深入分析)

  • DBA 确认 10 分钟前在业务高峰执行了 ALTER TABLE orders ADD COLUMN remark VARCHAR(500),未指定 ALGORITHMLOCK 子句,默认尝试获取 EXCLUSIVE 元数据锁。
  • 阻塞源会话 23 是一个应用事务:BEGIN; SELECT * FROM orders WHERE order_id=...; 由于 autocommit=0,事务一直未提交,持有 SHARED 元数据锁。
  • 元数据锁的优先级机制导致 DDL 的 X 锁请求阻塞了后续所有 S 锁请求,全链路雪崩。(根因详见第 5 篇 SQL 执行流程与元数据锁机制

T+30min(根因确认)

DDL 锁表根因链:未提交事务持有 S 锁 → DDL 请求 X 锁进入等待 → 元数据锁队列阻塞所有新到达的 DML → 连接池等待获取连接超时 → 应用全链路雪崩。

诊断工具链

  1. SHOW PROCESSLIST --- 宏观 Waiting for table metadata lock 状态
  2. performance_schema.metadata_locks --- 精确分析锁持有者与等待者
  3. information_schema.INNODB_TRX + sys.session --- 关联未提交事务信息
  4. SHOW ENGINE INNODB STATUS --- 事务列表辅助验证
  5. 应用日志与 APM --- 全链路超时定位

根因定位

  1. 元数据锁阻塞链 :DDL 需要 EXCLUSIVE 元数据锁,被一个未提交的 SELECT 事务持有的 SHARED 锁阻塞。MySQL 的 MDL 锁队列采用 FIFO 但写锁请求会阻塞后续读锁请求,导致所有读写操作堆积。根因详见第 5 篇 SQL 执行流程与元数据锁机制及第 4 篇锁分类体系。
  2. 应用事务未提交autocommit=0 且查询后未显式 COMMIT,导致事务长期持有元数据锁。根源在于代码未遵循事务最短化原则。
  3. 连接池耗尽 :连接池因所有连接被阻塞在 MDL 等待,无法释放,新请求在 connectionTimeout 内无法获取连接,最终全链路超时。

紧急处理

  • 定位并终止阻塞源 :确认会话 23 后,KILL 23 释放 S 锁。DDL 立即获取 X 锁并快速执行(Inplace DDL),随后所有排队请求得到执行,业务恢复正常。
  • 若无法快速定位 :可临时设置 lock_wait_timeout=5,让长时间等待 MDL 的会话超时断开,缓解连接池压力。风险:部分业务请求失败。

长期修复

  1. DDL 操作规范 :所有生产 DDL 必须使用 ALGORITHM=INPLACE, LOCK=NONELOCK=SHARED,并事先检查 performance_schema.metadata_locks 确认无长事务阻塞。
  2. 应用事务治理 :强制 autocommit=1,或使用 @Transactional 时确保只读事务设置 readOnly=true 并配置超时时间。
  3. 连接池保护 :配置 connectionTimeout=3000 避免无限等待,并监控等待队列长度 > 20 即告警。
  4. MDL 等待监控 :部署 pt-kill 或自定义脚本,当检测到 DDL 等待 MDL 超过 10 秒时自动告警并通知 DBA。

事后复盘

DDL 操作绝不能以默认方式在高峰执行,必须纳入变更流程。元数据锁等待链是瞬发全阻塞的典型,建立 performance_schema.metadata_locks 的快速查询脚本是必备技能。

sequenceDiagram participant App1 as 应用事务A participant DDL as DDL操作 participant App2 as 其他应用请求 participant MDL as 元数据锁队列 participant Pool as 连接池 App1->>MDL: "BEGIN;SELECT ... (获取 SHARED 锁)" App1-->>App1: "未 COMMIT 空闲" DDL->>MDL: "ALTER TABLE (请求 EXCLUSIVE 锁) 进入等待" MDL-->>DDL: "PENDING (被 SHARED 锁阻塞)" App2->>MDL: "SELECT ... (请求 SHARED 锁)" MDL-->>App2: "PENDING (被 EXCLUSIVE 请求阻塞)" loop "雪崩" App2->>Pool: "获取连接" Pool-->>App2: "等待超时" end

图表主旨概括 :该序列图展示了元数据锁队列的优先级阻塞机制如何将一个未提交事务升级为全系统雪崩。
逐层分解 :事务 A 持有 S 锁 -> DDL 请求 X 锁排队 -> 所有后续 DML 请求 S 锁被阻塞在 DDL 之后 -> 连接池所有连接被占用 -> 新请求超时。
设计原理映射 :体现 MySQL 元数据锁的兼容矩阵与队列优先级,以及连接池资源耗尽模式。
工程联系与关键结论 :DDL 操作前务必检查 metadata_locks,紧急止血必须首先定位并终止 S 锁持有者。


场景 6:慢查询堆积 + net_write_timeout 触发 + 连接池等待队列溢出

初始告警

PMM 显示 Threads_connected 从 50 飙升至 180,慢查询堆积超过 120 条。应用日志频繁出现 Connection is not available, request timed out after 30000ms,业务接口错误率升至 5%。

时间线推演

T+0min(接警)

  • SHOW FULL PROCESSLIST 中约 100 个线程状态为 Sending data,正在执行 SELECT * FROM orders WHERE create_time BETWEEN '2024-01-01' AND '2024-06-30',无 LIMIT
  • sys.statements_with_full_table_scans 确认该查询执行次数增加,且 NO_INDEX_USED 计数上升。

T+5min(初步诊断)

  • EXPLAIN 该 SQL:rows=15,000,000Extra: Using where,全表扫描。
  • SHOW VARIABLES LIKE 'net_write_timeout'60(默认)。应用端部分日志显示 CommunicationsException: Connection reset by peer,这些连接恰好执行了 60 秒后被 MySQL 端断开。
  • SHOW GLOBAL STATUS LIKE 'Aborted_clients' 突然增加,印证服务端主动断开连接。

T+15min(深入分析)

  • HikariCP Metrics /actuator/metricshikaricp_connections_pending 始终在 30-50 之间,hikaricp_connections_timeout_total 计数器快速增长。
  • 由于每个慢查询占据连接超过 60 秒(直到 net_write_timeout 触发),连接池所有连接被长时间占用,新请求在连接池队列中排队直至超时。应用侧的重试策略进一步加剧连接池压力。
  • PMM 的 MySQL Network Traffic 面板显示出口流量突增,与慢查询返回大量结果集一致。

T+30min(根因确认)

LIMIT 的报表类查询返回 500 万行结果集,每个查询占用连接 > 60s → 触发 net_write_timeout 连接断开 → 连接池连接失效并排队溢出 → 新请求获取连接超时。

诊断工具链

  1. SHOW PROCESSLIST --- Sending data 状态
  2. sys.statements_with_full_table_scans --- 识别全表扫描查询
  3. SHOW VARIABLES LIKE 'net_write_timeout' + Aborted_clients 状态 --- 连接断开原因
  4. HikariCP Metrics --- hikaricp_connections_pendingtimeout_total
  5. PMM Network Traffic --- 结果集大小激增

根因定位

  1. 慢查询根源 :全表扫描 1500 万行且无 LIMIT,返回数百万行给客户端。数据库需要将结果集从临时表或磁盘读出并通过网络发送,长时间占用线程。根因详见第 8 篇慢查询诊断与全表扫描。
  2. net_write_timeout 断开 :MySQL 在网络写入阻塞超过 60 秒后主动断开连接,应用端收到 connection reset。根因详见第 9 篇 net_write_timeoutnet_read_timeout 超时参数。
  3. 连接池排队溢出:每个慢查询长期占用连接,连接池内无空闲连接,新请求排队超时,部分请求失败,重试加剧压力。

紧急处理

  • 批量 KILL 慢查询SELECT GROUP_CONCAT(ID) FROM information_schema.PROCESSLIST WHERE STATE='Sending data' AND INFO LIKE '%orders%' INTO @ids; CALL mysql.rm_kill(@ids); 快速终止运行中的大结果集查询,释放连接。
  • 网关拦截:在 Nginx/API 网关层临时对该报表接口返回空结果或 429 限流,防止更多请求涌入。
  • 临时调大超时SET GLOBAL net_write_timeout = 300; 暂时延缓断开,为后续修复争取时间。风险:可能进一步堆积慢查询。

长期修复

  1. 查询优化 :增加 LIMIT 并分页,或改为异步导出任务(使用 SELECT ... INTO OUTFILE),实时接口严禁返回全量数据。
  2. 索引覆盖 :为 create_time 建立索引,并改为覆盖查询 SELECT order_id, amount FROM orders WHERE create_time BETWEEN ? AND ? ORDER BY create_time,利用索引避免回表和全表扫描。
  3. 连接池保护 :设置 connectionTimeout=5000,配置合理的 maxLifetime,并启用 leakDetectionThreshold=30000 自动检测泄漏。
  4. 慢查询监控关联 :增加 Rows_sent 的异常阈值监控,当单个查询返回行数 > 10000 时触发告警。

事后复盘

大结果集查询是慢查询的一种特殊形式,它不一定消耗大量 I/O,但会长时间占用线程和网络资源。数据库端和应用端必须同时防护。


场景 7:分库分表跨分片 JOIN + 结果归并内存溢出 + GC 停顿

初始告警

应用 JVM 监控告警:Full GC 频率从平均 1 次/小时骤升至 2 分钟一次,每次耗时 5 秒。接口响应 P50=200ms 但 P99=30s。PMM 显示连接池 hikaricp_connections_pending > 0

时间线推演

T+0min(接警)

  • jstat -gcutil <pid> 1000 显示 E 区 100%,O 区 95%,FGC 次数 120(启动后累计),FGCT 总耗时 600s。
  • Arthas dashboard 显示堆内存峰值 3.9GB(最大 4GB),GC 线程 CPU 占用高。

T+5min(初步诊断)

  • 打开 ShardingSphere SQL 日志 SQL_SHOW=true,发现一个查询产生了 16 条 Actual SQL:

    log 复制代码
    Actual SQL: ds_0 ::: SELECT o.order_id, d.detail FROM orders_0 o LEFT JOIN order_details_0 d ON o.order_id=d.order_id WHERE o.user_id=?
    Actual SQL: ds_1 ::: ...
    ...

    这是一个跨分片 LEFT JOIN:ordersuser_id 分片,order_detailsorder_id 分片,两者未配置为绑定表。

  • ShardingSphere 归并日志 ShardingSphereLogger 显示模式为 MemoryMerge,意味着所有分片的结果集被完全拉取到内存中进行笛卡尔积归并。

T+15min(深入分析)

  • sys.schema_table_statistics 查询 order_details 表:每分片约 200 万行,16 个分片总计 3200 万行。归并前的总数据量约 3200 万行 × 平均行大小 200 字节 = 6.4GB,远超 JVM 堆上限。
  • Arthas heapdump 分析显示 com.shardingsphere.infra.merge.result.impl.memory.MemoryMergedResult 对象占据堆内存的 80%。
  • 由于归并过程内存溢出,频繁 Full GC 导致应用线程停顿,连接在 GC 期间无法归还连接池,产生连接池排队超时。

T+30min(根因确认)

跨分片 JOIN 且两表分片键不一致 → ShardingSphere 无法下推 JOIN → 所有分片结果加载到内存进行笛卡尔积归并 → 内存溢出 → Full GC → 连接池阻塞。

诊断工具链

  1. jstat -gcutil --- JVM GC 频率与耗时
  2. Arthas dashboard / heapdump --- 堆内存占用分析
  3. ShardingSphere SQL_SHOW=true 日志 --- 路由与归并模式
  4. sys.schema_table_statistics --- 各分片数据量评估
  5. HikariCP Metrics --- 连接池排队情况

根因定位

  1. 跨分片 JOINordersorder_details 的分片键不同,ShardingSphere 无法识别它们之间的关联关系(未配置绑定表),只能对每个分片组合分别执行 SQL 并归并结果。根因详见第 7 篇 ShardingSphere 结果归并引擎(流式归并与内存归并)与第 2 篇索引设计。
  2. 内存归并 OOM:笛卡尔积归并产生巨大临时对象,JVM 堆内存不足触发 Full GC,导致长时间 Stop-The-World 暂停。
  3. 连接池阻塞:GC 期间线程冻结,占用的数据库连接无法归还连接池,进而导致其他线程获取连接超时。

紧急处理

  • 接口降级 :临时关闭跨分片 JOIN 查询接口,拆分为两次单表查询:先按 user_idorders 获取 order_id 列表,再根据 order_id 查询 order_details,应用层组装。风险:增加业务延迟,但可立即恢复。
  • JVM 参数临时调整 :增加 -Xmx8g -Xms8g 并重启,短暂缓解 OOM,但非根本解决。

长期修复

  1. 绑定表与分片键统一 :将 order_details 的分片键修改为 user_id,与 orders 一致,并在 ShardingSphere 配置 binding-tables: orders, order_details。这样关联查询可以精确路由到同一分片,完全避免跨分片 JOIN。
  2. 流式归并模式 :对于无法避免的跨片查询,配置 merge.type=MERGE 强制使用流式归并以避免全量加载。
  3. 架构评审红线:跨分片 JOIN 必须经过特殊审批,优先考虑反范式设计或分布式 SQL 引擎(如 Presto/ClickHouse)。
  4. JVM 与 GC 监控 :设置 FGC 频率告警(> 1 次/10min 即告警),并监控堆内存使用率。

事后复盘

分片键设计决定了查询能力的边界,关联表必须共享分片键。架构上应强制避免无绑定关系的跨片 JOIN,技术债务必须通过重构偿还。


场景 8:Binlog 不一致的隐蔽故障:STATEMENT 格式 + 非确定性函数 + 无告警的数据漂移

初始发现(非告警触发)

DBA 执行月度数据质量审计,通过 pt-table-checksum 校验主从数据一致性时,发现核心表 user_statisticslast_login_count 字段有 0.5% 的记录存在差异,累计约 5 万行不一致。此时 PMM 所有指标正常:无慢查询告警,无主从延迟告警(Seconds_Behind_Master=0),无连接异常,业务方未感知任何问题。

时间线推演

T+0min(发现)

  • 执行校验:

    bash 复制代码
    pt-table-checksum h=master,u=checksum,p=... --databases=app --tables=user_statistics --replicate=percona.checksums

    结果中 this_crc != master_crc 的记录有 50000 行,占比 0.5%。

  • SHOW SLAVE STATUS 显示 Executed_Gtid_Set 与主库完全一致,即从库执行了所有事务,但数据却不同。

T+15min(初步诊断)

  • SHOW VARIABLES LIKE 'binlog_format' 返回 STATEMENT

  • 解析 Binlog:mysqlbinlog --base64-output=DECODE-ROWS mysql-bin.000567 发现大量:

    sql 复制代码
    UPDATE user_statistics SET counter = counter + 1, last_login_count = last_login_count + 1 WHERE user_id = 123;

    同时期还有 INSERT INTO user_statistics (user_id, ...) VALUES (..., NOW(), ...) 涉及非确定性函数 NOW()

  • STATEMENT 格式下,NOW() 返回的时间在主从执行时可能不同;且 counter = counter + 1 这种依赖当前值的更新,在并发环境下主从自增值分配顺序不同(innodb_autoinc_lock_mode=2 交叉插入),导致累积计数偏移。

T+30min(根因确认)

通过对比差异记录的 user_id,发现均集中在有大量并发插入和更新的时间段。确认根本原因:STATEMENT 格式 Binlog 无法保证非确定性 SQL 在主从执行结果的一致性,数据在静默中漂移。

诊断工具链

  1. pt-table-checksum --- 发现数据差异
  2. SHOW VARIABLES LIKE 'binlog_format' --- 确认 Binlog 格式
  3. mysqlbinlog --- 解析 Binlog 中的具体 SQL 文本
  4. information_schema.INNODB_TABLESTATS + 手动抽样 SELECT --- 差异对比

根因定位

  1. Binlog 格式缺陷STATEMENT 格式记录原始 SQL 文本,对于包含非确定性函数(NOW()SYSDATE())或依赖并发执行顺序的更新(如 counter = counter + 1)的场景,从库执行时可能产生不同的结果,导致主从数据不一致。物理差异详解见第 6 篇 Binlog 三种格式的数据一致性风险(STATEMENT 格式的非确定性函数陷阱)。
  2. 静默漂移:每次差异极小(逐行累积),并未导致立即的业务错误,因此长期未被发现,属于典型的无告警隐蔽故障。

紧急处理

  • 修复不一致数据 :使用 pt-table-sync --execute --sync-to-master h=slave,u=root 以主库为准修复从库数据。注意要在低峰期执行,避免对业务造成锁竞争。
  • 暂停相关非确定性写入 :在修复前建议与业务沟通,暂停涉及 NOW() 和自增更新的事务,或临时将相关表的数据写入切换到单库执行,减少差异扩大。

长期修复

  1. 切换 Binlog 格式为 ROWSET GLOBAL binlog_format=ROW,并重启从库 IO/SQL 线程使其生效。ROW 格式记录行变更,彻底消除非确定性函数主从不一致的风险。验证:连续一周 pt-table-checksum 无差异。
  2. 代码规范 :禁止在 STATEMENT 格式下使用 SYSDATE() 或自增更新模式,若必须使用,需在代码评审中确认 Binlog 格式为 ROW。
  3. 数据一致性巡检 :将 pt-table-checksum 加入自动化周度/日度任务,结果写入监控系统并设置告警阈值(差异行数 > 0)。

事后复盘

隐蔽的数据漂移比瞬时故障更致命,因为它破坏的是数据的正确性,且往往在造成巨大业务损失后才被发现。必须将 Binlog 格式作为架构基线,默认为 ROW。


场景 9:大批量数据清理 + 死锁 + 从库延迟雪崩

初始告警

凌晨 03:00 定时数据清理任务启动后,从库延迟 Seconds_Behind_Master 从 0 飙升至 600 秒。PMM 显示主库 Innodb_row_lock_waitsInnodb_deadlocks 计数器同时增长,Binlog 写入速率 Binlog Bytes Written/s 突增 5 倍。

时间线推演

T+0min(接警)

  • SHOW SLAVE STATUS 从库延迟 600 秒,Relay_Log_Pos 几乎停滞。

  • SHOW ENGINE INNODB STATUSLATEST DETECTED DEADLOCK 显示:

    sql 复制代码
    *** (1) TRANSACTION: DELETE FROM logs WHERE create_time < '2024-01-01' LIMIT 10000
    *** (2) TRANSACTION: INSERT INTO logs (user_id, action, create_time) VALUES (...)

    死锁双方:清理任务和业务正常写入。

T+5min(初步诊断)

  • SHOW PROCESSLIST 多个 State = 'Updating',清理脚本在循环执行 DELETE ... LIMIT 10000,每批删除都持有 Next-Key Lock 并等待 InnoDB 行锁。
  • Binlog 文件大小监控:du -sh /var/lib/mysql/binlog/ 发现每分钟增长 200MB,是平时的 5 倍。
  • SHOW MASTER STATUS 确认 Binlog 为 ROW 格式,每一行被删除都产生一个完整的行变更事件。

T+15min(深入分析)

  • 清理脚本为手动编写的存储过程,循环 DELETE LIMIT--sleep--txn-size 控制,导致长事务和大量锁竞争。
  • 死锁原因:RR 隔离级别下 DELETE 对扫描到的行加 Next-Key Lock,与并发的 INSERT 产生间隙锁冲突(insert intention lock 与 gap lock 互斥),形成死锁。(根因详见第 4 篇加锁规则与死锁分析
  • 从库延迟:每行删除产生约 300 字节的 ROW 格式 Binlog 事件,500 万行清理将产生约 1.5GB Binlog,从库单线程回放积压导致延迟雪崩。

T+30min(根因确认)

大批量 DELETE 循环 → 持有 Next-Key Lock → 与业务 INSERT 死锁 → 大量 Binlog 事件 → 从库回放延迟 → 读写分离读到旧数据。

诊断工具链

  1. SHOW ENGINE INNODB STATUS --- 死锁分析
  2. SHOW SLAVE STATUS / Binlog 文件大小监控 --- 延迟与 Binlog 负载
  3. SHOW PROCESSLIST --- 识别 Updating 清理操作
  4. SHOW GLOBAL STATUS LIKE 'Innodb_deadlocks' --- 死锁计数趋势
  5. du -sh Binlog 目录 --- Binlog 速率

根因定位

  1. 死锁 :RR 隔离级别下 DELETE ... LIMIT 10000 对扫描到的所有行及其间隙加 Next-Key Lock,当与 INSERT 的插入意向锁冲突时,形成死锁。根因详见第 4 篇加锁规则与死锁分析。
  2. 从库延迟:ROW 格式 Binlog 每行删除记录一个事件,产生大量 IO 和回放负载,从库 SQL 线程无法及时消化。根因详见第 6 篇复制延迟与 Binlog 传输。

紧急处理

  • 终止清理任务KILL <清理会话ID> 停止循环 DELETE,死锁立即停止,业务写入恢复正常。
  • 从库读流量摘除:在 ShardingSphere 中将延迟从库的读权重设为 0,让读请求全部路由至延迟较小的从库,待延迟追平后恢复。
  • 跳过死锁(慎用) :极端情况下可临时 SET GLOBAL innodb_deadlock_detect=OFF(MySQL 8.0.18+),依赖于 innodb_lock_wait_timeout 回滚,风险极高。

长期修复

  1. 使用 pt-archiver 替代手动循环pt-archiver --source h=master,D=app,t=logs --where "create_time < '2024-01-01'" --limit 1000 --txn-size 1000 --sleep 0.1 --purge。该工具自动控制事务大小、加锁范围、休眠间隔,大幅降低锁冲突和 Binlog 负载。验证:运行期间主库无死锁,从库延迟 < 1 秒。
  2. 业务低峰清理窗口 :设定清理任务的执行时间窗口,并配置 innodb_deadlock_detect=ON 确保死锁检测。
  3. 并行复制加速 :配置 slave_parallel_workers=16slave_preserve_commit_order=ON,提升回放能力。
  4. 监控增强 :增加 Innodb_deadlocks 计数告警(> 10 次/min)和 Binlog 生成速率告警(> 200MB/min)。

事后复盘

手动的大批量 DML 操作是生产环境的大忌,必须用成熟的工具(pt-archiver)规范清理流程,将锁影响和复制负载降至最低。


场景 10:连接池默认配置 + 微服务扩容 + Too many connections

初始告警

为应对大促,微服务实例从 10 个扩容到 20 个后,部分新实例日志出现 java.sql.SQLNonTransientConnectionException: Too many connections。原有实例部分请求出现 Communications link failure

时间线推演

T+0min(接警)

  • SHOW VARIABLES LIKE 'max_connections' 返回 300
  • SHOW STATUS LIKE 'Threads_connected' 当前为 298,已接近上限。
  • SHOW FULL PROCESSLISTHost 分组统计连接数,新扩容实例每个占有约 20 个连接,总连接数约 400,超过 max_connections

T+5min(初步诊断)

  • 检查应用连接池配置(统一配置中心):maximumPoolSize=20minimumIdle=10。因历史原因运维将 HikariCP 的默认 maximumPoolSize=10 提升为 20,但未评估全局连接数。
  • 扩容前全局连接数:10 实例 × 20 = 200,安全。扩容后:20 × 20 = 400,突破 300 上限。
  • performance_schema.host_cacheCOUNT_CONNECT_ERRORS 增加,证实新实例连接被拒绝。

T+15min(深入分析)

  • 部分原有实例出现 Communications link failure 的原因:MySQL 端 wait_timeout=300,部分空闲连接被 MySQL 主动关闭,应用连接池未设置 keepalive 和合理的 maxLifetime,借用了已关闭的连接。(根因详见第 9 篇连接池协调不等式
  • 连接池等待队列 hikaricp_connections_pending 在原有实例中也轻微上升,因为部分连接失效导致池中可用连接减少。

T+30min(根因确认)

全局连接不等式 实例数 × maximumPoolSize < max_connections - 保留连接 被扩容打破。叠加 maxLifetime 错配加剧了连接失效。

诊断工具链

  1. SHOW VARIABLES LIKE 'max_connections' + Threads_connected --- 连接数上限检查
  2. SHOW PROCESSLISTHost 分组 --- 连接来源分布
  3. performance_schema.host_cache --- 连接错误计数
  4. /actuator/metrics HikariCP 指标 --- 连接池状态
  5. SHOW VARIABLES LIKE 'wait_timeout' --- 空闲超时

根因定位

  1. 连接数突破上限 :微服务实例数翻倍,每个实例连接池 maximumPoolSize=20,总连接请求 400,超过 max_connections=300,新连接被拒绝。全局连接预算公式及约束详见第 9 篇全局协调不等式。
  2. 空闲连接失效wait_timeout=300 且连接池未配置 keepalive,空闲连接被 MySQL 单方面断开,应用借用时出现异常,进一步增加连接池压力。

紧急处理

  • 动态减少连接池大小 :通过配置中心将每个实例的 maximumPoolSize 调整为 10,总连接数降为 200,立即释放压力。对于 HikariCP,修改 maximumPoolSize 会自动缩小池。
  • 临时提升 max_connectionsSET GLOBAL max_connections = 500; 为连接池调整和事务提交争取时间。风险:需确保系统内存足够。
  • 清理空闲连接 :手动 KILL 长时间 Sleep 连接或调低 wait_timeout 至 120 秒加速清理。

长期修复

  1. 容量规划纳入连接预算 :建立全局连接公式 N_instances × maxPoolSize + 10% 预留 < max_connections,任何扩容操作前必须计算并审批。
  2. 连接池标准化 :推荐 maximumPoolSize=10(或基于压测),minIdle=2maxLifetime=280s(< wait_timeout),keepaliveTime=60000,避免连接失效。
  3. 监控与告警 :设置 Threads_connected / max_connections > 0.8 的告警,以及 hikaricp_connections_timeout_total 速率告警,提早发现连接数紧张。

事后复盘

微服务架构下的数据库连接数是一个全局资源,必须作为容量预算的一部分严格管控。任何实例数变更都需要触发连接数评审。


系统设计类场景

场景 11:亿级订单系统数据架构设计

业务需求与约束

  • 业务背景:电商核心订单系统,日均订单 2000 万笔,峰值 QPS 写 5000 / 读 20000。
  • 核心查询
    1. user_id 查询分页订单列表(WHERE user_id = ? ORDER BY create_time DESC LIMIT 20),要求 P99 < 50ms。
    2. order_id 精确查询订单详情,要求 P99 < 10ms。
    3. create_time 范围生成经营报表(SELECT ... WHERE create_time BETWEEN ? AND ?),可接受 P99 < 500ms。
  • 数据量级:当前单表 5 亿行,数据保留 1 年,预计 3 年后 50 亿行。
  • SLA 要求:可用性 99.99%,RPO < 10s,RTO < 60s。

分片与索引方案

分片键选择

选择 user_id 作为分片键。理由:绝大多数查询以 user_id 为过滤条件,可以精确路由到单一分片,避免跨分片查询。验证数据均匀性:

sql 复制代码
SELECT user_id % 256 AS shard, COUNT(*) AS cnt FROM orders GROUP BY shard ORDER BY cnt DESC;

要求最大分片与最小分片的行数偏差 < 15%。若存在热点用户(如 B2B 商户),可引入二级分片或独立热点库。

分片策略

采用 16 库 × 16 表(共 256 个物理分片)。user_id 哈希取模:shard_no = user_id % 256。库索引 = shard_no / 16,表索引 = shard_no % 16。预计 3 年后 50 亿行,单分片约 1953 万行,处于 B+Tree 高效范围。

订单 ID 设计

使用雪花算法生成 64 位 order_id,结构:[41位时间戳][10位机器ID][12位序列号]。时间戳保证递增趋势,便于时序查询时结合 create_time 进行范围过滤。同时在 order_id 中嵌入 user_id 的低 8 位(基因法),使得通过 order_id 查询时可以推导出分片,避免扫全库。例如:user_id_low8 = order_id >> (64-8) & 0xFF,由此确定分片。

核心表 DDL (以分片表 orders_0 为例):

sql 复制代码
CREATE TABLE orders_0 (
    order_id   BIGINT NOT NULL,
    user_id    BIGINT NOT NULL,
    status     VARCHAR(20) NOT NULL,
    amount     DECIMAL(10,2) NOT NULL,
    create_time DATETIME NOT NULL,
    update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (order_id),
    KEY idx_user_time (user_id, create_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

索引使用路径

  • 查询 1(按 user_id + 分页):SELECT order_id, status, amount, create_time FROM orders WHERE user_id = ? ORDER BY create_time DESC LIMIT 20。使用 idx_user_time 覆盖索引,EXPLAIN 显示 Using index,只需扫描索引树,无需回表。
  • 查询 2(按 order_id):通过基因法解析分片,直接定位分片走主键索引,EXPLAIN 显示 consteq_ref
  • 查询 3(按 create_time 范围):由于无法定位到特定分片,使用 ShardingSphere 的广播查询,每个分片并行执行 SELECT ... WHERE create_time BETWEEN ? AND ?,利用 create_time 的索引(可在每个分片表上额外建立 KEY idx_create_time (create_time)),并行归并结果。为避免大范围查询拖垮系统,限制时间跨度 ≤ 7 天,或异步实时同步至 ClickHouse 列存引擎供报表查询。

冷热分离 :数据保留 1 年,6 个月后数据自动归档至历史库(MySQL 归档表或 ClickHouse)。使用 pt-archiver 定期(每周)将 create_time < NOW() - INTERVAL 6 MONTH 的数据从在线库归档至历史表并删除,保持在线表行数稳定。

复制与高可用拓扑

flowchart LR M[主库 M] S1[从库 S1 半同步] S2[从库 S2 异步] DR[灾备库 DR 异步] M -->|半同步 AFTER_SYNC| S1 M -->|异步| S2 S1 -->|异步 GTID| DR
  • 主库 M :承接所有写入,binlog_format=ROWrpl_semi_sync_master_enabled=ONrpl_semi_sync_master_timeout=5000ms
  • 从库 S1 :半同步从库,用于读写分离中的高一致性读(事务后读)。开启 slave_parallel_type=LOGICAL_CLOCKslave_parallel_workers=8
  • 从库 S2:异步从库,用于非关键读、报表查询和备份。
  • 灾备库 DR :跨机房异步复制,通过 GTID 自动定位,定期 pt-table-checksum 验证数据。
  • 高可用切换:基于 Orchestrator 或 Consul + 脚本,检测主库故障后自动提升 S1 为新主库,更新 ShardingSphere 数据源指向。

连接池与资源规划

全局连接不等式
max_connections=1200,预留 200 个连接给系统用户和运维。应用实例数 50,每实例 HikariCP 参数:

yaml 复制代码
spring:
  datasource:
    hikari:
      maximum-pool-size: 20
      minimum-idle: 5
      max-lifetime: 280000       # 280s < wait_timeout=300
      idle-timeout: 180000
      connection-timeout: 5000
      keepalive-time: 60000
      validation-timeout: 3000

总连接数:50 × 20 = 1000 ≤ 1000,满足不等式。

ShardingSphere 数据源配置(关键片段):

yaml 复制代码
spring:
  shardingsphere:
    datasource:
      names: ds_master, ds_slave1, ds_slave2
      ds_master:
        jdbc-url: jdbc:mysql://master:3306/orders?useSSL=false&allowPublicKeyRetrieval=true
        type: com.zaxxer.hikari.HikariDataSource
      ds_slave1:
        jdbc-url: jdbc:mysql://slave1:3306/orders?...
      ds_slave2:
        jdbc-url: jdbc:mysql://slave2:3306/orders?...
    rules:
      sharding:
        tables:
          orders:
            actual-data-nodes: ds_master.orders_${0..255}
            table-strategy:
              standard:
                sharding-column: user_id
                sharding-algorithm-name: mod_hash
        sharding-algorithms:
          mod_hash:
            type: MOD
            props:
              sharding-count: 256
      readwrite-splitting:
        data-sources:
          ms_ds:
            write-data-source-name: ds_master
            read-data-source-names: ds_slave1, ds_slave2
            load-balancer-name: round_robin
            transaction-aware: true

验证策略

压测工具 :Sysbench + JMeter 混合,预填充 20 亿行数据。
压测模型

  • 场景 A(点查) :按 order_id 精确查询,使用 oltp_point_select 脚本,100 并发,持续 10 分钟。通过标准:P99 ≤ 10ms,QPS ≥ 20000。
  • 场景 B(用户列表) :按 user_id 分页查询,使用自定义 Sysbench 脚本,200 并发,70% 读 30% 写,模拟真实混合负载,持续 30 分钟。通过标准:P99 读 ≤ 50ms,P99 写 ≤ 100ms,Buffer Pool 命中率 > 98%。
  • 场景 C(报表)create_time 范围查询(跨度 1 天),5 并发,持续 5 分钟。通过标准:P99 ≤ 500ms,且不产生连接池排队超时。 观察指标 :PMM 监控 QPS、P99/P999 延迟、主从延迟(< 1s)、Buffer Pool 命中率(> 98%)、hikaricp_connections_pending(< 5)、无死锁。

方案评估与潜在风险

  • 热点风险 :若少数 user_id 产生海量订单,可能导致分片倾斜。解决方案:实时监控分片数据量,超过阈值 2 亿行/分片时启动热点迁移,或二级分片。
  • 跨分片查询:严格禁止按非分片键的 JOIN 或复杂过滤。业务必须接受通过 ES/ClickHouse 实现复杂检索。
  • 扩容路径:从 256 分片扩容到 1024 分片时,需使用 ShardingSphere Scaling 进行在线数据迁移,注意迁移期间的读写一致性。
  • 技术债务:归档到 ClickHouse 的异构查询链路需额外维护,报表查询可能存在秒级延迟。

场景 12:跨机房灾备架构与故障切换方案

业务需求与约束

核心交易系统部署在 A 城市双机房(机房 A 与机房 B 相距 30km,网络 RTT < 2ms)。要求:

  • RPO < 10 秒,RTO < 60 秒。
  • 机房 A 整体故障时,机房 B 自动接管全部业务流量。
  • 数据量 5TB,写 TPS 5000,读 TPS 20000。
  • 日常正常时,机房 B 可承担部分非关键读流量。

分片与索引方案

与场景 11 相同,沿用 256 分片及索引设计,确保跨机房表结构完全一致。

复制与高可用拓扑

flowchart LR subgraph 机房A M_A[(主库 A)] S_A1[(从库 A1 半同步)] S_A2[(从库 A2 异步)] end subgraph 机房B DR_B[(灾备库 B)] S_B1[(从库 B1 异步)] end M_A -->|半同步 AFTER_SYNC| S_A1 M_A -->|异步| S_A2 S_A1 -->|异步 GTID AUTO_POSITION| DR_B DR_B -->|异步| S_B1

复制链路

  1. 机房 A 内部 :主库 A 到从库 A1 使用半同步 AFTER_SYNCrpl_semi_sync_master_wait_for_slave_count=1rpl_semi_sync_master_timeout=5000ms。保证机房 A 内至少有一份同步数据。
  2. 跨机房 :由 S_A1 作为中继,向机房 B 灾备库 DR_B 进行异步复制,使用 GTID + MASTER_AUTO_POSITION=1,确保自动定位 Binlog 偏移。网络延迟 < 2ms 可保证较低的复制滞后。
  3. 机房 B 内部:DR_B 作为"准主库",下挂一个异步从库 B1,用于分担机房 B 的读请求。

故障切换 SOP(自动脚本核心流程):

  1. 检测与告警 :Consul 健康检查发现 M_A 不可达,pt-heartbeat 监控到心跳表 percona.heartbeat 最后更新时间 > 5s。确认网络分区不是仅监控链路中断。

  2. 停止残余写入 :若 M_A 部分存活,执行 SSH 远程命令 SET GLOBAL read_only=ON; SET GLOBAL super_read_only=ON; 或通过网络 ACL 隔离。

  3. 提升灾备库为主库 (在 DR_B 上执行):

    sql 复制代码
    STOP SLAVE;
    RESET SLAVE ALL;   -- 清除复制信息
    SET GLOBAL read_only=OFF;
    SET GLOBAL super_read_only=OFF;

    验证:SHOW MASTER STATUS 获取当前 GTID,与故障前最后同步的 GTID 对比,确认 RPO 内的数据已就绪。

  4. 更新数据源:通过配置中心(如 Spring Cloud Config)将 ShardingSphere 中写数据源指向 DR_B,读数据源指向 DR_B 和 S_B1。应用实例通过消息总线刷新配置并生效。

  5. 业务流量切换 :修改负载均衡器(如 Nginx/SLB)将交易流量后端实例指向机房 B。同时 pt-table-checksum 快速校验核心表一致(可选,可延迟执行以优先 RTO)。

  6. 回切准备:当机房 A 恢复后,以 DR_B 为主库反向建立异步复制到 M_A,数据追平后反向切换流量,恢复原始拓扑。

脑裂防护 :切换脚本必须获取 Consul 分布式锁 lock/mysql_master,确保只有一个节点发起切换。且 M_A 恢复后不能自动成为主库,需人工确认并重新加入拓扑。

连接池与资源规划

连接池配置与场景 11 保持一致,但需在 ShardingSphere 中预先定义多机房数据源:

yaml 复制代码
spring:
  shardingsphere:
    datasource:
      names: ds_master_a, ds_slave_a1, ds_slave_a2, ds_dr_b, ds_slave_b1
      # 机房A
      ds_master_a: ...
      ds_slave_a1: ...
      ds_slave_a2: ...
      # 机房B 灾备
      ds_dr_b: ...
      ds_slave_b1: ...
    rules:
      readwrite-splitting:
        data-sources:
          ms_ds:
            write-data-source-name: ds_master_a
            read-data-source-names: ds_slave_a1, ds_slave_a2, ds_dr_b, ds_slave_b1
            load-balancer-name: round_robin

切换时,将 write-data-source-name 动态更新为 ds_dr_b,读列表保留 ds_slave_b1。应用无需重启。

验证策略

切换演练

  1. 使用 Sysbench oltp_read_write.lua 持续以 5000 TPS 写入,10000 TPS 读取,模拟全负载。
  2. 断开主库 A 的 MySQL 进程:systemctl stop mysqld
  3. 自动检测(心跳超时 30s)触发切换脚本。
  4. 记录指标:RTO(最后一次写入成功到新主库开始接受写入的时间)< 60s,RPO(通过 pt-heartbeat 表计算丢失的更新数对应的时间窗口)< 10s。业务接口错误率在切换瞬间 < 1%,并在 30 秒内恢复。
  5. 切换后运行 pt-table-checksum 验证 DR_B 与业务预期一致。 定期演练:每月执行一次,并不断优化脚本耗时。

方案评估与潜在风险

  • 网络分区与脑裂:如果仅监控网络中断而实际 M_A 仍运行,提升 DR_B 可能导致双主。必须在切换前通过多个独立探测点(如同机房 agent)确认 M_A 不可达,并使用分布式锁。
  • 数据丢失风险:跨机房异步复制本身允许最大网络延迟 + 复制延迟的数据丢失(RPO < 10s 相对容易达成,但若网络波动可能出现 20s 以上延迟,需要监控并动态调整切换超时)。
  • 成本:灾备库需要与主库同等的计算和存储资源,成本高。可在日常通过读写分离承担非关键只读流量,分摊成本。
  • 回切复杂性 :反向复制回机房 A 时,需要处理可能的数据冲突(如切换期间 B 上的写入)。建议通过 GTID 反向复制并开启 skip-errors 仅针对回切期间的重复 key 错误,或临时暂停写入后通过 pt-table-sync 同步。
  • 未来演进:可考虑使用 MySQL Group Replication 多主模式在同城双机房实现自动故障转移,但需要更严格的网络要求和冲突处理机制。

从诊断到架构:完整能力图谱总结

经过 10 个故障排查场景的逐级推演和 2 个系统设计场景的架构训练,读者应已建立起从单领域问题定位到跨领域关联分析,再到系统性架构设计的完整能力。故障排查的核心不是记忆命令,而是形成"现象→假设→验证→根因"的诊断闭环;系统设计的核心不是模式堆砌,而是在约束条件下做出一系列可验证的权衡决策。

能力地图

  • 索引与优化器诊断 :快速通过 EXPLAIN ANALYZEsys.schema_index_statisticsinnodb_stats_persistent_sample_pages 定位统计信息与索引设计问题。
  • 事务与锁诊断 :利用 information_schema.INNODB_TRXperformance_schema.metadata_locksSHOW ENGINE INNODB STATUS 分析长事务、锁等待、死锁。
  • 复制与一致性 :掌握 SHOW SLAVE STATUS、GTID 校验、pt-table-checksum,防范数据漂移和延迟雪崩。
  • 连接池与资源协调 :熟练运用全局连接不等式,配置 maxLifetimeconnectionTimeout,并利用 PMM 和连接池 Metrics 监控。
  • 分库分表架构:理解分片键选择、绑定表、结果归并模式,避免跨分片 JOIN 与内存溢出。
  • 系统设计决策:能基于业务量级、SLA 设计分片拓扑、复制链、连接池预算和验证策略。

将本文的 12 个场景作为模拟训练的"案例库",反复推演,直至在面对陌生告警时,大脑能自动激活关联知识链,那便是高级数据库工程师的真正标志。


面试高频专题

本专题所有题目均为复合场景,综合考察前 10 篇知识的融会贯通能力。建议在完成本系列全部学习后,以模拟面试形式进行限时回答训练。

场景题 1 :CPU 飙高 + 慢查询暴涨 + Buffer Pool 命中率下降的复合排查。请列出前 5 条诊断命令及推理路径。 场景题 2 :磁盘 Undo 暴涨 + 应用锁等待 + 连接数高涨。分析可能根因并给出紧急处理步骤。 场景题 3 :用户反馈订单状态更新后立即查询不返回结果,监控显示某分片从库延迟 5 分钟,读写分离正常。如何定位根因? 场景题 4 :在线执行 DDL 后所有业务超时,SHOW PROCESSLIST 中大量 Waiting for table metadata lock。描述锁定链并给出恢复方案。 场景题 5 :微服务扩容后 Too many connections 异常,如何在 2 分钟内止血?长期如何规划? 场景题 6 :分库分表环境中某查询偶尔引起 Full GC,但单分片 SQL 执行很快。分析可能的原因和解决方案。 场景题 7pt-table-checksum 发现主从不一致,但复制延迟为 0,如何排查并修复? 场景题 8 :手动批量清理日志表导致线上死锁和从库延迟巨大。设计一个安全的清理方案。 设计题 1 :设计一个支持亿级用户的高并发订单系统数据层,给出分片、索引、复制和验证方案。 设计题 2:设计一套跨机房 MySQL 灾备方案,RPO < 10s,RTO < 60s。画出拓扑,写出切换 SOP 关键步骤和验证方法。


MySQL 线上故障应急速查卡

常见告警现象 可能性排序 首选诊断命令 紧急止血操作
CPU 飙高 + Buffer Pool 命中率降 1. 统计信息过时 2. 大事务全表扫描 EXPLAIN ANALYZE; SHOW ENGINE INNODB STATUS KILL 慢查询;ANALYZE TABLE;强制索引
磁盘 Undo 增长 1. 长事务未提交 2. 大事务并发 SELECT * FROM INNODB_TRX 按时间排序 KILL 长事务
主从延迟突增 1. 大事务写入 2. 网络延迟 3. 并行不足 SHOW SLAVE STATUS; SHOW ENGINE INNODB STATUS 暂停大事务;摘除延迟从库读流量
Too many connections 1. 连接池过大 2. 实例扩容 3. 连接泄漏 SHOW PROCESSLIST 按 Host 分组;SHOW VARIABLES 减小 maxPoolSize;临时提高 max_connections
锁等待/死锁 1. 无索引外键 2. DDL 锁表 3. 批量 DML SHOW ENGINE INNODB STATUS LATEST DEADLOCK KILL 持锁会话/阻塞事务
数据不一致(无告警) 1. STATEMENT 格式 + 非确定性函数 pt-table-checksumSHOW VARIABLES LIKE 'binlog_format' pt-table-sync 修复;切换 ROW 格式

系统设计速查卡

设计维度 关键决策点 推荐方案 验证标准
分片键 均匀性、查询亲和性 user_id 哈希取模(基因法嵌入 order_id 分片数据量偏差 < 15%
索引 覆盖索引、避免回表 联合索引 (user_id, create_time),主键 order_id EXPLAIN 显示 Using index;P99 < 50ms
复制拓扑 高可用与延迟平衡 同机房半同步 + 跨机房异步 GTID 主从延迟 < 1s,RPO < 10s
连接池 全局连接不等式 实例数 × maxPoolSize < max_connections - 预留 扩容后零连接拒绝
灾备切换 RPO/RTO 目标与防脑裂 Consul 锁 + pt-heartbeat 监控 + 自动化脚本 演练 RTO < 60s,数据差异 < 10s

延伸阅读

相关推荐
醇氧1 小时前
CentOS 7安装 mysql-8.0.27-1.el7.x86_64.rpm 安装包
android·mysql·centos
敖正炀1 小时前
慢查询与性能诊断:PMM、pt-query-digest 与 sys schema
mysql
敖正炀1 小时前
主从复制与 GTID:半同步、并行复制
mysql
敖正炀1 小时前
InnoDB 引擎深度:B+Tree、页与行格式
mysql
敖正炀1 小时前
事务与 MVCC:Undo Log、ReadView 与隔离级别
mysql
老码观察2 小时前
K8s集群断电后MySQL恢复实录:从InnoDB崩溃到数据完整迁移
mysql·adb·kubernetes
敖正炀2 小时前
MySQL 架构全景与特性总览
mysql
tkevinjd2 小时前
MySQL1:分层架构
数据库·mysql·缓存
承渊政道2 小时前
从ROWNUM到LIMIT:KES、Oracle与PostgreSQL的执行顺序差异解析
数据库·数据仓库·sql·mysql·安全·postgresql·oracle