MySQL(关系型数据库)事务、索引、锁与迁移失败追问
本册只负责编排 MySQL(关系型数据库)失败追问、证据链与恢复表达。机制回链模块 13,项目事实等级沿用模块 15 唯一事实账本;量级统一标记 E0(待核对)、E1(直接证据)、E2(既有材料映射)或 E3(演练设计)。

1. 覆盖索引退化与执行计划漂移
1.1 从访问路径变化到业务不变量
覆盖索引不是某个索引的固定属性,而是“当前查询所需列能否全部从当前访问路径取得”的关系。字段、排序、过滤条件、字符集、隐式类型转换、统计信息、数据倾斜或优化器版本变化,都可能让原本的覆盖访问退化为回表、临时表、文件排序甚至全表扫描。执行计划是成本模型在当时元数据、统计信息和参数下的选择,不是永久合同;排查必须同时保存 SQL(结构化查询语言)文本与绑定值、表结构、索引、统计信息、优化器开关、版本和计划,而不能只截一张 EXPLAIN(执行计划分析)。
flowchart LR
A["固定 SQL(结构化查询语言)摘要与参数分布"] --> B["对比发布前后表结构、索引与统计信息"]
B --> C{"访问路径是否变化"}
C -- "否" --> D["检查缓存命中、并发、锁和存储延迟"]
C -- "是" --> E["比较估算行数与实际行数"]
E --> F{"统计偏差还是查询形态变化"}
F -- "统计偏差" --> G["受控更新统计信息并观察"]
F -- "查询变化" --> H["重写查询或补最小有效索引"]
G --> I["影子流量与业务不变量验收"]
H --> I
D --> I图解读: 箭头先固定可重放输入,再把“计划变了”和“计划没变但运行环境变了”分流。正常路径用估算行数与实际行数解释选择;失败路径若没有完整参数和统计快照,只能保持 E0(待核对)。前提是测试不得在高峰直接执行无边界的 EXPLAIN ANALYZE(执行计划实测分析)。结论要落到支付查询正确、库存扣减不越界和尾延迟恢复,而非只看索引名。
| 现象 | 可证伪假设 | 关键证据 | 止血 | 修复与退出条件 |
|---|---|---|---|---|
Using index 消失 | 新增返回列导致不再覆盖 | 查询投影、索引列序和计划 | 回退投影或只读版本 | 回表数和尾延迟恢复 |
| 选错低选择性索引 | 统计信息过旧或数据倾斜 | 直方图、基数、参数分布 | 固定高风险入口配额 | 估算与实际行数接近 |
| 扫描行数突增 | 隐式转换或函数包裹索引列 | 字段类型、警告、查询改动 | 回退查询版本 | 恢复范围访问且结果一致 |
| 排序落盘 | 联合索引顺序不支持过滤加排序 | 排序键、临时表和磁盘指标 | 限制大页与深分页 | 排序成本有界 |
| 强制索引后另一批参数变慢 | 参数分布存在多峰 | 按参数桶的实际耗时 | 分流异常参数 | 各参数桶均满足预算 |
数据演绎 1:覆盖退化的放大效应。 E3(演练设计)订单状态查询原本使用 (tenant_id, status, created_at, order_id) 联合索引,返回列也都在索引中。某次发布新增 channel_remark 返回列后,每次查询命中 20,000 条索引记录并发生 20,000 次回表;若随机页命中率为 85%,约有 20,000 × 15% = 3,000 次需要触达未缓存数据页。并发 80 个请求时,潜在随机页访问机会达到 3,000 × 80 = 240,000 次。状态从“二级索引定位并直接返回”变为“二级索引定位—主键回表—拼接结果”。观测信号是实际扫描行、回表、缓冲池未命中、磁盘等待和第 95 百分位耗时。数字只说明风险,不是生产规模;真正验收还要比较结果行和订单状态摘要。
失败注入、证据链与项目落地: E3(演练设计)在影子环境依次注入新增投影列、把数值参数改为字符串、将热点状态占比从 5% 放大到 80%、删除直方图并刷新统计信息。每轮保存 SQL(结构化查询语言)摘要、参数桶、EXPLAIN FORMAT=JSON(JSON 格式执行计划)、受控 EXPLAIN ANALYZE(执行计划实测分析)、表索引和运行指标。止血优先回退查询、限制深分页和隔离报表流量,不在事故高峰直接建大索引。修复可采用减少投影、匹配类型、重排联合索引或按参数形态拆查询,并以不可见索引做删除前验证。恢复要求计划在代表性参数桶稳定、扫描与返回比例下降、支付和库存结果摘要不变。机制为 E2(既有材料映射),生产计划和收益为 E0(待核对)。参考 MySQL(关系型数据库)官方索引优化说明、优化器统计信息 与 不可见索引。
热门面试题
问题(基础题):什么情况下覆盖索引会退化?
- 考点:查询投影、联合索引、回表边界。
- 回答思路:先说明覆盖是查询与索引的关系,再列结构和查询变化。
- 详细答案:只要查询需要的过滤、排序或返回列不能全部从所选索引取得,就可能回表或改走其他路径。新增返回列、联合索引列序变化、表达式包裹、类型不一致和优化器改选索引都可能触发退化,不能把一次计划中的
Using index当永久属性。 - 进阶追问:把所有返回列都加进索引是否最好?
- 进阶回答:不是;宽索引会增加空间、写放大和维护成本,应按高价值查询、选择性和写入预算权衡。
问题(原理题):为什么同一条 SQL(结构化查询语言)的执行计划会漂移?
- 考点:成本模型、统计信息、参数分布和版本。
- 回答思路:把计划视为输入集合上的成本选择,而不是固定脚本。
- 详细答案:表行数、索引基数、直方图、数据倾斜、缓存与成本常量、优化器开关、软件版本和绑定值分布变化,都会改变候选路径的估算成本。排查应比较完整环境快照和估算误差,避免只靠强制索引掩盖统计或查询模型问题。
- 进阶追问:强制索引为什么可能制造新故障?
- 进阶回答:它把某个参数桶的局部最优固化,数据分布变化后可能阻止更优路径,还会隐藏统计信息失真。
问题(项目题):支付订单查询发布后变慢,怎样在不扩大事故的前提下取证?
- 考点:现场保护、计划对比、业务校验。
- 回答思路:先冻结变化并降载,再用副本或影子流量做受控实测。
- 详细答案:固定 SQL(结构化查询语言)摘要、参数桶、实例和时间窗,保存发布前后结构、统计和执行计划;线上只做低风险的估算分析,实际执行放到数据分布接近的隔离环境。止血回退查询或限制异常参数,修复后同时验收扫描量、尾延迟、结果行和支付状态摘要。
- 进阶追问:延迟恢复就能结束吗?
- 进阶回答:不能;还要确认没有漏单、错单、缓存污染和因超时重试产生的重复副作用。
2. MVCC(多版本并发控制)可见性与事务恢复
2.1 从 Read View(读视图)到三日志恢复边界
MVCC(多版本并发控制)通过事务标识、行隐藏字段、Read View(读视图)和 undo log(回滚日志)版本链,让一致性读在不阻塞普通写的情况下重建可见版本。Read Committed(读已提交)通常按语句建立新的 Read View(读视图),Repeatable(可重复注解) Read(可重复读)的一致性读通常复用事务内首次读视图;当前读与加锁读则面向最新可用版本并参与锁竞争。长事务会让旧版本不能及时清理,扩大 undo log(回滚日志)、历史链和恢复成本,因此“没写多少数据”不等于对系统无害。
sequenceDiagram
participant T1 as 长事务 T1
participant R as Read View(读视图)
participant T2 as 更新事务 T2
participant U as undo log(回滚日志)
participant C as 崩溃恢复
T1->>R: 建立一致性快照
T2->>U: 写入旧版本
T2->>T2: 修改数据并提交
T1->>U: 沿版本链重建可见行
Note over T1,U: T1 未结束时旧版本受保护
T1-->>R: 提交并释放快照
C->>C: 用 redo log(重做日志)重放页修改
C->>U: 回滚未提交事务图解读: 上半段表达逻辑可见性,下半段表达物理崩溃恢复,两者不能混为一谈。正常路径中旧版本在最老快照释放后才具备清理条件;失败路径是长事务持续保护历史版本。前提是隔离级别、是否一致性读以及提交状态已确认。结论是“读到旧值”可能符合隔离语义,也可能是读副本延迟,必须先分层。
| 组件或语义 | 解决的问题 | 不能单独保证 | 关键证据 | 恢复动作 |
|---|---|---|---|---|
| Read View(读视图) | 判断版本对一致性读是否可见 | 外部系统一致性 | 事务标识、隔离级别、快照时间 | 结束异常长事务 |
| undo log(回滚日志) | 回滚与历史版本重建 | 已提交页持久化 | 历史链长度、最老事务 | 清除根因后等待受控清理 |
| redo log(重做日志) | 崩溃后重放已记录页修改 | 跨实例复制语义 | 日志序列、检查点、恢复日志 | 重放后校验提交结果 |
| binlog(二进制日志) | 复制与时间点恢复的逻辑变更 | 存储引擎页级恢复 | 事务事件、位置或 GTID(全局事务标识) | 重放到目标时间点 |
| 业务账本 | 裁决支付或库存是否生效 | 数据库内部页恢复 | 业务键、状态、分录与流水 | 补偿、冲正或人工裁决 |
数据演绎 2:长事务如何放大历史版本。 E3(演练设计)报表事务在 10:00 建立 Read View(读视图)后运行 30 分钟;期间订单表每秒更新 2,000 行,每个旧版本平均占 220B(字节),理论产生 2,000 × 1,800 = 3,600,000 个旧版本,原始载荷约 3,600,000 × 220B ≈ 755MiB(兆字节),尚未计算页、索引和元数据开销。状态为“快照建立—持续更新—历史版本受保护—事务结束—清理推进”。观测信号是最老活跃事务年龄、历史链、undo log(回滚日志)空间、清理速率和一致性读延迟。终止事务后空间未立即回落不代表失败,需观察清理推进及业务负载。
失败注入、证据链与项目落地: E3(演练设计)启动一个 Repeatable(可重复注解) Read(可重复读)只读事务并保持连接,持续更新支付订单和库存版本;同时注入提交响应丢失,再重启隔离实例。证据包保存事务列表、快照年龄、历史链、redo log(重做日志)与 binlog(二进制日志)位置、恢复日志、支付请求号和库存流水。止血是暂停大报表、限制长事务入口并保护在线连接,不直接删除 undo log(回滚日志)文件。修复包括分页短事务、超时与连接归还、未知提交按原业务键查询;恢复后以支付状态单调、账务分录唯一、库存守恒裁决。机制为 E2(既有材料映射),演练数字为 E3(演练设计),生产影响为 E0(待核对)。参考 MySQL(关系型数据库)官方一致性读说明、多版本机制 与 redo log(重做日志)。
热门面试题
问题(基础题):MVCC(多版本并发控制)怎样判断一行是否可见?
- 考点:事务标识、Read View(读视图)、版本链。
- 回答思路:从当前版本的创建事务开始,按可见性规则和回滚指针查找。
- 详细答案:一致性读比较行版本的事务标识与 Read View(读视图)中的活跃事务边界;当前版本不可见时,沿 undo log(回滚日志)记录重建更旧版本,直到找到可见版本或链结束。具体规则要结合隔离级别和读视图创建时机。
- 进阶追问:MVCC(多版本并发控制)是否完全不加锁?
- 进阶回答:不是;它主要优化一致性读,更新、当前读、唯一性检查和外键等路径仍可能加锁。
问题(原理题):长只读事务为什么也可能拖垮数据库?
- 考点:最老快照、历史版本保留、清理滞后。
- 回答思路:说明它虽不产生大量写,却延长旧版本生存期。
- 详细答案:长只读事务持续持有旧 Read View(读视图),并发更新产生的历史版本不能越过该快照清理,导致 undo log(回滚日志)和历史链增长,增加查询重建、空间和恢复压力。治理应缩短报表事务、分页读取并监控最老事务年龄。
- 进阶追问:杀掉长事务后空间为何没有立刻下降?
- 进阶回答:释放快照只恢复清理资格,后台仍需按速率处理历史版本,物理空间回收还受表空间策略影响。
问题(项目题):支付提交超时后,为什么不能直接重试整个事务?
- 考点:未知提交、幂等键、业务裁决。
- 回答思路:区分客户端没收到响应与数据库没提交。
- 详细答案:响应可能在提交成功后丢失,直接换请求号重试会重复记账。应保留原商户请求号,查询支付单、事务结果和唯一账务分录;已成功则返回原结果,明确失败才重试,仍未知则进入查证与人工裁决流程。
- 进阶追问:数据库恢复完成是否等于支付恢复完成?
- 进阶回答:不等于;还要核对渠道状态、支付单、账务分录、订单和对账差异。
3. 锁等待、死锁与业务热点
3.1 从等待图到库存与支付裁决
锁等待是请求正在等待其他事务释放兼容资源,死锁则是等待依赖形成环且无法自行推进。InnoDB(事务存储引擎)的锁对象与访问索引有关:精确唯一索引可能收敛到记录,范围扫描可能覆盖记录与间隙,缺失合适索引时扫描范围会扩大。MDL(元数据锁)还会让一个未结束事务阻塞 DDL(数据定义语言),后续普通查询再排在 DDL(数据定义语言)之后形成队头阻塞。排查不能只说“有行锁”,必须画出谁持有什么、谁等待什么、对应哪个业务键和事务。
stateDiagram-v2
[*] --> Running: 开始事务
Running --> Waiting: 请求不兼容锁
Waiting --> Running: 持有者提交
Waiting --> Victim: 等待图形成环并被选为牺牲者
Running --> Committed: 提交
Running --> RolledBack: 业务或超时回滚
Victim --> RolledBack: 数据库回滚当前事务
RolledBack --> Reconcile: 查询业务流水再决定重试
Committed --> Reconcile: 核对库存或支付不变量
Reconcile --> [*]图解读: 状态图把技术事务结束和业务恢复分开。正常等待在持有者提交后继续;死锁牺牲者虽然被回滚,业务调用仍需按稳定键判断是否重试。前提是获取了事务、线程、锁对象和业务键映射。结论是重启会释放当前锁,却不能修复反向锁序、范围过大或热点模型。
| 现象 | 等待图特征 | 常见根因 | 证据 | 根治方向 |
|---|---|---|---|---|
| 普通短等待 | 单向边且持有者持续变化 | 正常热点竞争 | 锁年龄、完成率 | 缩短事务并限流热点 |
| 长锁等待 | 单向长链 | 事务内慢调用或忘记提交 | 持有事务 SQL(结构化查询语言)、开始时间 | 移出外部调用、补超时 |
| 死锁 | 存在闭环 | 锁序相反、范围交叉 | 最近死锁与全量日志 | 统一顺序并缩小范围 |
| MDL(元数据锁)队头阻塞 | 长事务—DDL(数据定义语言)—普通请求排队 | 在线变更前置检查不足 | 元数据锁与会话链 | 先清长事务再受控变更 |
| 热点库存串行 | 多事务集中同一索引记录 | 单 SKU(库存单位)突发 | 业务键分布、锁等待分位 | 入口整形、分段或条件更新 |
数据演绎 3:一笔慢事务如何传播等待。 E3(演练设计)库存扣减事务先更新热点 SKU(库存单位),再同步调用下游 3 秒;该 SKU(库存单位)请求到达率为 120 个/秒,等待期间理论新增 120 × 3 = 360 个请求。若每个请求超时后再重试 2 次,最坏附加尝试为 360 × 2 = 720 次,总尝试机会达到 1,080。状态从“持锁—慢调用—等待队列—超时—重试回流”形成正反馈。观测信号是锁持有年龄、等待者数、相同业务键占比、超时和重试率。把超时从 3 秒调到 10 秒只会延后失败;修复应把外部调用移出事务并让条件更新与唯一流水承担裁决。
失败注入、证据链与项目落地: E3(演练设计)用两个事务按“库存行—订单行”和“订单行—库存行”相反顺序更新,并在其中一条路径注入慢渠道调用;另启动长事务后执行 Online DDL(在线数据定义语言)观察 MDL(元数据锁)队列。保存 performance_schema(性能模式) 锁等待、事务开始时间、最近死锁、错误日志、SQL(结构化查询语言)摘要、仓库与 SKU(库存单位)、订单和支付请求号。止血为暂停问题入口、关闭自动无脑重试、限流热点键并在保留证据后终止明确异常事务。修复统一锁序、缩短事务、增加准确索引、把外部调用改为事务后投递;验证 100 轮交错注入无环、等待年龄受控、库存不为负、唯一流水不重复。机制为 E2(既有材料映射),演练为 E3(演练设计),真实事故为 E0(待核对)。参考 MySQL(关系型数据库)官方锁模型 与 死锁说明。
热门面试题
问题(基础题):锁等待与死锁有什么区别?
- 考点:可推进性、等待图、超时和死锁检测。
- 回答思路:用有向等待边是否成环回答。
- 详细答案:普通锁等待存在持有者到等待者的单向依赖,持有者提交后可以推进;死锁存在闭环,参与事务互相等待,必须回滚至少一个事务打破环。长等待也可能很严重,但不能只按持续时间称为死锁。
- 进阶追问:死锁牺牲者回滚后可否立即重试?
- 进阶回答:先按原业务键查询已发生副作用,再做带退避和预算的重试;否则可能放大热点或重复业务动作。
问题(原理题):为什么没有合适索引会扩大锁范围?
- 考点:访问路径、索引记录、范围锁。
- 回答思路:锁住的是扫描遇到的索引范围,不是抽象查询条件文字。
- 详细答案:InnoDB(事务存储引擎)沿执行计划访问索引记录并加锁;缺少选择性索引时需扫描更大范围,可能触及更多记录与间隙,使无关业务键也竞争。增加索引前仍要用实际计划和隔离级别验证锁范围。
- 进阶追问:加索引一定能消除死锁吗?
- 进阶回答:不能;它可缩小范围,但反向锁序、跨表更新和业务重试仍可能形成环。
问题(项目题):库存死锁恢复后怎样证明没有超卖?
- 考点:技术恢复、业务守恒、历史数据修复。
- 回答思路:从死锁事务结果追到条件更新、唯一流水和库存账本。
- 详细答案:先确定被回滚与已提交事务,按订单行和请求号核对唯一扣减流水;再复算期初、入库、冻结、扣减、释放和期末可售,检查任何成功扣减都未越过条件。必要时冻结问题 SKU(库存单位)并差异修复,不能用“数据库已无死锁”替代业务验收。
- 进阶追问:库存总量相等是否足够?
- 进阶回答:不够;还要验证每个订单行的幂等、状态单调和冻结归属,防止总量碰巧相等但明细错配。
4. 复制延迟、读写分离与故障切换
4.1 从提交确认到副本可见
主库提交成功只证明主库完成了约定的持久化边界,不代表异步副本已经应用同一事务。复制链路至少包含主库生成 binlog(二进制日志)、发送、从库接收为 relay log(中继日志)、协调与工作线程应用;网络、单个大事务、热点行冲突、磁盘、工作线程依赖或从库查询负载都可能制造延迟。Seconds_Behind_Source(落后源库秒数) 是线索,不是完整真相:线程停止、空闲、时钟和单个阻塞工作线程都可能让单值失真,应结合接收位置、应用位置、GTID(全局事务标识)、工作线程错误和业务提交时间判断。
flowchart LR
A["主库提交"] --> B["binlog(二进制日志)生成"]
B --> C["复制接收线程"]
C --> D["relay log(中继日志)"]
D --> E["协调线程分发"]
E --> F1["应用工作线程 1"]
E --> F2["应用工作线程 2"]
E --> F3["应用工作线程 N"]
F1 --> G["副本可见水位"]
F2 --> G
F3 --> G
G --> H{"读请求要求写后读吗"}
H -- "是" --> I["主库或达到令牌水位的副本"]
H -- "否" --> J["普通副本读取"]图解读: 箭头区分“已接收”和“已应用”,并把业务读一致性放在复制水位之后。正常路径由写入返回提交令牌或版本,后续强一致读选择主库或已追平副本;失败路径是无条件读从库后把旧值误判为失败并触发重复支付。前提是读请求已分类。结论不是所有读都回主,而是让资金、库存等关键读有明确一致性合同。
| 延迟类型 | 典型信号 | 证据 | 止血 | 修复 |
|---|---|---|---|---|
| 网络或接收慢 | 接收位置落后主库 | 发送与接收位置、网络 | 保护主库并切健康链路 | 网络和复制通道治理 |
| 应用线程慢 | 已接收但应用水位滞后 | 工作线程状态、磁盘和冲突 | 摘除强一致读 | 并行应用与事务拆分 |
| 单个大事务 | 延迟阶跃且工作线程长时间处理同一事务 | GTID(全局事务标识)、事务大小 | 暂停大批次 | 分批且保持业务原子边界 |
| 副本查询争用 | 复制与报表同时变慢 | 输入输出、锁、查询负载 | 限制报表 | 隔离分析副本 |
| 工作线程停止 | 延迟值可能为未知或不可信 | 错误号、服务状态 | 立刻摘除副本 | 修复数据或配置后重放 |
数据演绎 4:延迟为何导致重复支付动作。 E3(演练设计)支付成功后主库每秒产生 8,000 个事务,故障副本只能应用 5,000 个事务,净积压为 8,000 - 5,000 = 3,000 个/秒;持续 120 秒积压 3,000 × 120 = 360,000 个事务。若前端在 2 秒后从该副本仍读到“处理中”,其中 1% 用户再次支付,就可能产生 3,600 次重复意图。唯一支付请求号可以阻止重复生效,但体验和下游压力仍会恶化。状态是“主库成功—副本未应用—用户读旧值—再次提交—幂等裁决”。观测信号要同时包含复制水位、读路由、业务版本、幂等命中和渠道请求。
失败注入、证据链与项目落地: E3(演练设计)在副本暂停一个应用工作线程,注入 200,000 行单事务更新和慢磁盘,同时让支付结果页、库存可售查询分别走副本。保存主库提交 GTID(全局事务标识)、副本接收与应用水位、工作线程错误、请求读路由、支付单版本和库存流水。止血是摘除落后副本的关键读、让写后读临时回主、暂停大事务和报表,不通过跳过复制事件制造“追平”。修复按根因拆大事务、隔离副本负载、校准并行应用,并给关键读携带提交令牌。恢复要求接收与应用水位持续收敛、工作线程无错、分档恢复读流量后支付状态单调且库存账实一致。依据 MySQL(关系型数据库)官方副本状态说明 与 复制工作线程监控;方案为 E2(既有材料映射),量级为 E3(演练设计),生产事实为 E0(待核对)。
热门面试题
问题(基础题):复制延迟的链路有哪些阶段?
- 考点:生成、传输、接收、应用和业务可见。
- 回答思路:按主库日志到副本应用水位顺序回答。
- 详细答案:主库提交并生成 binlog(二进制日志),复制接收线程拉取后写入 relay log(中继日志),协调线程再交给一个或多个工作线程应用。延迟可能发生在主库生成、网络接收、磁盘写入、依赖调度和实际应用任何阶段。
- 进阶追问:
Seconds_Behind_Source(落后源库秒数)为 0 就一定无延迟吗? - 进阶回答:不一定;需确认线程运行、接收与应用位置、工作线程错误和业务事务是否真的可见。
问题(原理题):为什么并行复制仍会被一个大事务拖慢?
- 考点:事务原子性、依赖调度、工作线程占用。
- 回答思路:说明事务不能任意拆成独立提交单元。
- 详细答案:一个大事务作为整体占用工作线程并产生大量写入,可能受热点行、磁盘和提交顺序约束;其他事务即使可并行,也可能在依赖或提交阶段等待。根治应在业务侧拆成有检查点的小批,但不能破坏资金和库存原子边界。
- 进阶追问:直接增加工作线程是否足够?
- 进阶回答:不足;若瓶颈是单事务、热点冲突或磁盘,更多线程可能增加争用,需先用工作线程证据定位。
问题(项目题):支付成功后立即查询不到,如何避免重复支付?
- 考点:写后读、提交令牌、幂等和状态单调。
- 回答思路:先判断读路由和复制水位,再由原请求号裁决。
- 详细答案:支付写入返回版本或事务令牌,结果页在一致性窗口内读主库或达到该水位的副本;即便读到处理中,重复提交仍沿用原商户请求号并查询原支付单,不能创建新支付意图。最终以渠道、支付单和账务分录对账。
- 进阶追问:所有请求永久读主库是否更简单?
- 进阶回答:会损失扩展性和故障隔离,应只对写后读和关键状态读提供明确强度,其余允许有界陈旧。
5. 在线迁移双写分叉与校验修复
5.1 从影子写入到切流回滚
在线迁移不是“复制完数据再改配置”,而是基线、增量、双写、校验、切读、切写、观察和回滚组成的状态机。双写最危险的不是一次失败,而是两边各自成功一部分后应用不知道谁是权威;如果新旧库没有共同业务键、版本、操作标识和可追踪尝试,差异只能靠模糊时间窗猜测。迁移期间必须定义单一权威源,影子侧写失败进入可重放账本;切读之前做分层校验,切写之后仍保留反向补偿或快速回滚窗口。
sequenceDiagram
participant A as 应用
participant O as 旧库权威
participant J as 迁移账本
participant N as 新库影子
participant V as 校验器
A->>O: 按业务键写入并提交
O-->>A: 返回版本
A->>J: 记录待同步操作
J->>N: 幂等写入同一业务键与版本
alt 新库成功
N-->>J: 确认版本
else 超时或失败
N-->>J: 保持未知或待重试
end
V->>O: 按分片读取键、版本和摘要
V->>N: 对比键、版本和摘要
V-->>A: 只在差异收敛后允许切读图解读: 旧库先保持权威,新库是可重放影子,双写失败不回滚已成功的旧库业务,而是由迁移账本恢复。正常路径以版本和摘要校验后切读;失败路径保持未知态并按原操作标识查证。前提是业务允许阶段性最终一致,资金账务若要求更强边界必须单独设计。结论是“两个写请求都返回成功”不能替代持续差异校验。
| 阶段 | 权威源 | 主要风险 | 门禁证据 | 回滚方式 |
|---|---|---|---|---|
| 基线复制 | 旧库 | 漏行、快照漂移 | 水位、总量、分桶摘要 | 清空影子重做 |
| 增量追平 | 旧库 | 乱序、重复、删除遗漏 | 操作标识、版本、积压年龄 | 从检查点重放 |
| 双写观察 | 旧库 | 一边成功一边未知 | 迁移账本与差异率 | 停影子写,不影响主链 |
| 切读 | 旧库仍可裁决 | 新库旧值或路由遗漏 | 影子读对比、关键键抽样 | 读流量回旧库 |
| 切写 | 新库 | 旧消费者仍写旧库 | 写入来源审计、反向同步 | 在窗口内回旧库 |
| 收口 | 新库 | 过早删旧库和账本 | 完整峰值、差异清零、备份 | 延长保留期 |
数据演绎 5:小差异率为何仍不可接受。 E3(演练设计)迁移 50,000,000 条库存记录,按 1,000 个桶比较主键数量、版本和金额摘要。若差异率仅 0.002%,差异仍有 50,000,000 × 0.002% = 1,000 条;其中 2% 若是支付或高价值库存,就是 20 条关键错误。某桶旧库数量 50,010、新库 50,008,只看总量差 2 无法发现“漏 5 条、重复 3 条”;必须下钻稳定业务键、版本和操作标识。状态从桶级发现、键级分类、原操作重放到再次校验。观测信号为差异数量、最老未同步年龄、未知态、重放结果和业务金额守恒。
失败注入、证据链与项目落地: E3(演练设计)依次注入旧库成功而新库超时、新库成功但回执丢失、删除事件乱序、迁移消费者重启、切读规则漏一个租户和切写后旧版本实例恢复。证据包保存业务键、源版本、目标版本、操作标识、迁移水位、路由版本、两库提交结果和支付或库存账本。止血是冻结切流、把读写恢复到当前权威源、暂停自动补偿并隔离旧实例;未知写先按原键查证。修复采用事务内 Outbox(发件箱)或可靠变更流、幂等版本更新、租户级路由门禁和可逆切换。验证以桶级摘要加键级全量差异、迟到删除、重复重放和旧实例恢复共同注入,要求差异清零且支付金额、分录、库存数量守恒。依据 MySQL(关系型数据库)官方 Online DDL(在线数据定义语言)操作矩阵、Online DDL(在线数据定义语言)失败条件 与 Percona Toolkit(数据库运维工具集)在线变更说明。方案为 E2(既有材料映射),数字为 E3(演练设计),生产迁移效果为 E0(待核对)。
热门面试题
问题(基础题):在线迁移为什么需要单一权威源?
- 考点:冲突裁决、回滚、阶段状态。
- 回答思路:说明双写不等于双主,必须有最终裁决方向。
- 详细答案:迁移期间新旧库可能短暂不一致;若两边都可独立修改且无冲突规则,差异出现后无法决定覆盖方向。每个阶段应明确权威源、影子源、同步方向和切换门禁,使失败可重放、切流可回滚。
- 进阶追问:切读后权威源是否立即变成新库?
- 进阶回答:不一定;切读只是读取验证阶段,写权威通常仍在旧库,需按状态机明确区分。
问题(原理题):双写一边超时为什么不能直接反向删除?
- 考点:未知态、补偿风险、幂等查证。
- 回答思路:说明超时不代表失败,删除可能破坏已成功事实。
- 详细答案:目标库可能已提交但回执丢失,立即删除或反向回滚会把正确数据删掉,并可能与后续重试竞争。应保留操作标识与版本,先按原业务键查证,再决定确认、重放或补偿。
- 进阶追问:怎样防止旧事件覆盖新值?
- 进阶回答:目标写入携带单调版本并使用条件更新,低版本事件只记录审计,不得推进状态。
问题(项目题):库存迁移的校验为什么不能只比较总行数?
- 考点:抵消误差、键级差异、数量守恒。
- 回答思路:用漏行与重复相互抵消的反例展开。
- 详细答案:两库总行数相同仍可能是一边漏一条、另一边重复一条。应先按稳定分桶比较数量、版本和数量摘要,再下钻业务键,核对每个仓库与 SKU(库存单位)的可售、冻结、已扣和流水归属,差异修复后重新全量校验。
- 进阶追问:抽样通过能否切流?
- 进阶回答:关键资金和库存不能只靠随机抽样;需全量轻量摘要、差异键全查并覆盖完整峰值窗口。
6. 分库分表路由与全局不变量
6.1 从分片键到扩容迁移
分库分表把单库容量问题转化为路由、全局唯一、跨片查询、事务和迁移问题。路由函数必须输入稳定、版本可识别且全链路一致;同一个业务键若在应用、缓存、消息消费者和补偿任务中使用不同归一化规则,就可能写入不同分片。扩容不能直接把哈希取模从 8 改成 16,因为大量键会瞬间重映射;通常需要虚拟节点、映射表或带路由版本的迁移状态机,并让旧版本实例无法在切换后继续写旧片。
flowchart TD
A["请求携带租户、业务键和路由版本"] --> B["统一归一化"]
B --> C["路由表解析逻辑分片"]
C --> D{"分片是否迁移中"}
D -- "否" --> E["写当前权威分片"]
D -- "是" --> F["写权威分片并记录迁移操作"]
F --> G["影子分片幂等应用"]
E --> H["全局业务流水"]
G --> H
H --> I{"版本、金额、数量是否守恒"}
I -- "是" --> J["允许分片级切流"]
I -- "否" --> K["冻结该分片并按键修复"]图解读: 路由版本与业务键共同决定落点,迁移按分片逐步推进而非全局瞬切。正常路径将技术分片写入汇总到全局业务流水;失败路径冻结单个分片并修复。前提是应用、异步消费者和补偿任务使用同一套路由库。结论是分片本身不是业务事实,支付分录与库存流水才是跨片裁决依据。
| 风险 | 错误做法 | 证据 | 止血 | 长期治理 |
|---|---|---|---|---|
| 路由错片 | 各服务自行拼接分片规则 | 同键多片、路由版本 | 冻结键并双片查询 | 中央路由库与契约测试 |
| 扩容重映射 | 直接修改取模基数 | 新旧函数结果差异 | 回退路由配置 | 映射表或虚拟分片 |
| 跨片重复 | 重试生成新全局标识 | 重复业务键与分录 | 按全局键拒绝推进 | 全局幂等账本 |
| 跨片聚合错 | 分页后在应用随意合并 | 各片水位和排序键 | 降级导出 | 固定快照与稳定归并 |
| 旧实例污染 | 切流后旧版本继续消费 | 实例版本、写入来源 | 摘除旧实例 | 栅栏式路由版本 |
数据演绎 6:直接改取模为何造成大面积错路由。 E3(演练设计)旧规则为 hash(key) % 8,新规则直接改为 hash(key) % 16。对于均匀哈希,约一半键的新余数仍在 0—7 且与旧余数相同,另一半落到 8—15,因此约 50% 键改变位置;1 亿订单可能涉及约 5,000 万次重映射。若迁移窗口有 20 个旧实例和 20 个新实例并存,同一键还可能被两个规则并发写入。状态从单路由变为双规则分叉。观测信号是路由版本分布、同键跨片数量、旧实例写入、差异积压和全局账本。可行方案应按虚拟分片小批迁移,让每批可校验和回退。
失败注入、证据链与项目落地: E3(演练设计)让应用把租户标识按字符串哈希,消费者却先转数字;在 8 片扩 16 片时恢复一个旧版本实例,并让支付重试生成新全局标识。证据包保存原始业务键、归一化值、路由版本、逻辑与物理分片、实例版本、消息标识、支付分录和库存流水。止血按路由版本摘除旧实例,冻结受影响虚拟分片,双片查询但只允许权威片写入;支付未知态按原商户请求号查证。修复统一路由组件、契约测试、全局幂等账本和分片级迁移状态机。验证覆盖负数、前导零、大小写、空值、旧消息、重复重放和扩缩容,要求同键唯一落点、支付分录借贷平衡、库存不为负且跨片汇总守恒。方案为 E2(既有材料映射),数字为 E3(演练设计),生产路由和规模为 E0(待核对)。机制回链 分库分表与迁移唯一正文 与 支付资金一致性项目串讲。
热门面试题
问题(基础题):分片键选择要满足哪些条件?
- 考点:稳定性、分布、查询局部性和迁移。
- 回答思路:从写入均衡与核心访问路径两端权衡。
- 详细答案:分片键应稳定、基数足够、分布可控,并让高频查询尽量定位单片;还要考虑热点租户、数据生命周期、扩容和合规隔离。只追求均匀会让业务查询跨片,只追求局部又可能制造热点。
- 进阶追问:支付单按用户分片有什么风险?
- 进阶回答:商户对账、渠道流水和时间范围查询可能跨片,需要独立索引、汇总账本或离线分析通道。
问题(原理题):为什么扩容时不能直接修改取模基数?
- 考点:键重映射、混合版本、数据位置。
- 回答思路:用 8 片到 16 片约半数键变化说明。
- 详细答案:取模基数参与每个键的落点计算,改变后大量已有键被映射到新位置;混合版本实例还会同时按两套规则读写。应通过映射表、虚拟分片或一致性哈希控制迁移单元,并携带路由版本。
- 进阶追问:一致性哈希是否自动解决业务迁移?
- 进阶回答:不能;它只减少重映射,双写、校验、旧消息、事务和回滚仍需迁移状态机。
问题(项目题):路由错片后怎样修复支付与库存数据?
- 考点:双片查证、权威事实、补偿和审计。
- 回答思路:先阻断继续分叉,再按业务键和版本裁决。
- 详细答案:冻结受影响路由范围并摘除错误版本,按原业务键在候选分片查询,结合支付请求号、不可变分录或库存流水决定权威记录;通过有审计的迁移操作补齐正确片并标记错误片记录,不能直接物理删除。完成后全量校验同键唯一落点和业务守恒。
- 进阶追问:为什么不把两片较新的记录直接覆盖合并?
- 进阶回答:时间新不等于业务正确,迟到事件可能版本更旧;必须按状态机、单调版本和账本裁决。
7. 综合口述题
问题:覆盖索引突然退化为回表或全表扫描,如何排查?
- 考点:查询形态、统计信息、访问路径、风险受控的验证与业务正确性。
- 回答思路:先固定同一输入和变化时间线,再比较估算与实际,最后用影子流量和订单不变量验收。
- 详细答案:先以同一 SQL(结构化查询语言)摘要和参数桶复现问题,比较发布前后的投影列、联合索引列序、字段类型、统计信息与执行计划,确认是回表、范围扩大还是改走全表扫描。再比对估算行数与实际扫描行,并在隔离环境验证最小索引或查询改动,避免在高峰期用建宽索引掩盖锁等待和 I/O(输入输出)压力。
- 进阶追问:统计信息正确时,为什么优化器仍可能不选看似最合适的联合索引?
- 进阶回答:优化器比较的是整条路径的成本,排序、回表、连接顺序和缓存假设都可能使该索引并非最低成本。应按参数桶核验实际扫描与返回比例,必要时拆分查询形态而不是永久固定索引提示。
- 事实等级:机制与排查方法为 E2(既有材料映射),示例行数为 E3(演练设计),生产计划和收益为 E0(待核对)。
- 口述答案:我不会看到
Using index消失就直接加索引,先固定 SQL(结构化查询语言)摘要、绑定值分布、租户、实例、开始时间和发布版本,并确认结果错误还是仅延迟上升。第一组假设是查询形态变化,例如新增返回列导致不再覆盖、函数包裹索引列、数值字段传入字符串或排序方向改变;第二组是元数据变化,例如索引被改为不可见、联合索引列序变化、统计信息过旧或直方图不再代表热点参数;第三组是计划没变,但缓冲池命中、并发、锁等待或存储延迟恶化。证据要成套保存发布前后表结构、索引、统计时间、优化器开关、EXPLAIN FORMAT=JSON(JSON 格式执行计划)、按参数桶采样的实际扫描行与返回行、回表和临时表信号。生产高峰不贸然执行大范围EXPLAIN ANALYZE(执行计划实测分析),而是在接近分布的隔离环境或极小影子流量验证。止血优先回退投影或查询版本、限制深分页和异常参数、隔离报表流量,不在锁和复制已紧张时直接建宽索引。根因若是统计失真,就受控刷新并观察代表性参数;若是查询模型问题,就减少投影、统一类型或设计最小有效联合索引,同时评估写放大。验证不能只看索引名,要比较扫描返回比、第 95 百分位、缓冲命中和压力窗口,并核对订单行数、状态摘要、支付与库存结果未变化。复盘记录计划基线、参数分桶、统计更新门禁和回滚条件。所有没有原始监控支持的收益都保持 E0(待核对)。 - 追问 1:能否立刻使用强制索引? 直接回答:只可作为受控短期止血,必须验证各参数桶并设置撤销条件,不能替代根因修复。
- 追问 2:覆盖索引越宽越好吗? 直接回答:不是,宽索引增加空间、写放大和缓存压力,应按收益与写入预算取舍。
- 追问 3:
EXPLAIN(执行计划分析)估算很好为何仍慢? 直接回答:估算不包含全部运行时竞争,还要看实际行数、缓存、锁、磁盘和并发。 - 追问 4:延迟恢复即可宣布完成吗? 直接回答:不行,还要验证结果一致、业务不变量和完整峰值内无反弹。
- 对应唯一正文:B+Tree(多路平衡树)索引与优化器
- MySQL(关系型数据库)官方索引优化说明
问题:同一条 SQL(结构化查询语言)为何在发布后发生执行计划漂移?
- 考点:成本模型、参数倾斜、统计信息、版本差异与计划治理。
- 回答思路:把计划视为输入集合上的成本选择,用时间线和对照实验逐项证伪。
- 详细答案:执行计划由表统计、直方图、索引基数、绑定参数、会话变量和优化器版本共同决定,同一 SQL(结构化查询语言)文本在这些输入变化后会重新计算成本。应保存计划指纹与统计快照,分别用冷热参数验证估算偏差,区分统计失真、参数倾斜和查询形态变化。
- 进阶追问:刷新统计信息后计划反而更慢,应如何处置?
- 进阶回答:先回退或隔离受影响参数桶,确认新统计是否代表热点分布,再决定补直方图、调整索引或拆分查询。不能只因统计更新时间较新就默认其更准确,验证标准仍是代表性请求的实际成本和业务结果。
- 事实等级:优化器机制为 E2(既有材料映射),参数占比为 E3(演练设计),生产漂移原因必须保持 E0(待核对)直到证据闭环。
- 口述答案:同一条 SQL(结构化查询语言)文本不代表优化器看到的输入完全相同,我会先对齐发布前后的数据库版本、表结构、索引可见性、优化器开关、统计更新时间、字符集和会话变量,再按真实绑定值把请求分桶。计划漂移常见有三类:第一,数据分布变化,原来高选择性的状态值变成热点,统计基数或直方图又没有跟上;第二,应用变化使参数类型、条件组合、投影或排序发生改变,文本摘要看似相近但可用索引不同;第三,版本或成本模型变化让候选路径重新排序。取证时同时保存旧计划和新计划,比较访问类型、选择的键、估算行、过滤比例、连接顺序、临时表与排序,并在隔离环境用代表性的冷门、普通、热点参数执行受控
EXPLAIN ANALYZE(执行计划实测分析),重点看估算与实际偏差,而不是只比总耗时。止血可以回退版本、隔离热点参数、限制查询并发,必要时短期使用提示,但要给提示设置版本、范围和退出条件。修复可能是更新统计、建立直方图、重写条件、拆分多峰参数查询或调整索引;任何索引变化都要评估写入成本和复制压力。验证要求多个参数桶在同一数据分布下达标,计划变化可解释,支付查询不漏状态、库存查询不读错仓库。复盘建立 SQL(结构化查询语言)摘要到计划指纹的基线、统计漂移告警和发布前回放,并保存可一键撤销的临时提示清单。演练参数不能冒充生产占比,真实原因无快照时只能标 E0(待核对)。 - 追问 1:统计信息越新越好吗? 直接回答:要代表当前分布且更新过程可控,频繁更新也可能触发计划变化和资源抖动。
- 追问 2:为什么一个提示不能长期保留? 直接回答:数据和版本会变化,固定提示可能把旧局部最优变成新全局劣化。
- 追问 3:如何证明是参数倾斜? 直接回答:按参数桶比较频率、实际行数、计划与耗时,观察热点桶是否系统性偏离估算。
- 追问 4:计划指纹相同就能排除数据库问题吗? 直接回答:不能,锁、缓存、输入输出、并发和副本负载仍可能改变运行时间。
- 对应唯一正文:B+Tree(多路平衡树)索引与优化器
- MySQL(关系型数据库)官方优化器统计信息
问题:长事务如何通过 MVCC(多版本并发控制)拖垮系统?
- 考点:Read View(读视图)、历史版本、清理滞后、连接与业务恢复。
- 回答思路:从最老快照出发,串起更新速率、历史链、空间、查询与恢复成本。
- 详细答案:长事务持有的 Read View(读视图)会阻止 purge(清理)越过其可见性边界,并发更新产生的旧版本因此持续保留在 undo log(回滚日志)版本链中。历史链变长会增加一致性读的版本重建、空间与 I/O(输入输出)成本,严重时还会拖慢检查点推进和故障恢复。
- 进阶追问:把长报表改为分页短事务后,怎样避免读到跨页不一致的数据?
- 进阶回答:以稳定的时间水位或单调主键作为分页边界,并将本次导出绑定到明确快照或版本条件。若业务要求严格全局快照,应转到专用副本或离线链路,而不是无限延长主库事务。
- 事实等级:机制与治理为 E2(既有材料映射),30 分钟和版本量为 E3(演练设计),真实事务年龄与影响为 E0(待核对)。
- 口述答案:长事务的危险不只在持锁。对于一致性读,它可能长期持有 Read View(读视图),并发更新产生的旧版本必须保留在 undo log(回滚日志)链上,最老快照不释放,清理就无法越过这条边界。E3(演练设计)中若每秒更新 2,000 行、报表事务保持 30 分钟,就有 360 万次历史版本机会;真实空间还受行宽、页和索引影响,不能直接照搬。排查时我先固定最老事务的会话、开始时间、隔离级别、当前语句和来源应用,再把历史链、undo log(回滚日志)空间、清理速率、磁盘、缓冲池、查询延迟和复制延迟对齐。还要区分它是正在执行的慢 SQL(结构化查询语言)、开启事务后空闲、连接池未归还,还是备份或报表的设计行为。止血先暂停新报表和大批次、保护在线连接池,确认事务可中断且不会留下外部副作用后再终止异常会话;绝不能手工删除 undo log(回滚日志)文件。修复是把全量报表改为稳定游标分页和短事务,设置事务年龄与空闲事务门禁,确保异常路径回滚并归还连接。终止后历史链不会瞬间消失,要观察清理持续推进、空间和延迟逐步回落。验证用相同更新率与报表输入做故障注入,要求最老事务有界、历史链形成平台、复制不被拖垮,并核对导出快照、库存流水和支付状态没有因拆批而混用版本。复盘记录发现为何滞后、谁拥有连接、告警阈值与自动处置边界;没有生产记录时影响只能是 E0(待核对)。
- 追问 1:只读事务为什么会影响写? 直接回答:它可能保护旧版本,间接增加清理、空间、输入输出和查询成本,当前读还可能加锁。
- 追问 2:杀掉最老事务后为何仍慢? 直接回答:历史版本需后台逐步清理,缓存和复制积压也要时间收敛。
- 追问 3:降低隔离级别能根治吗? 直接回答:不能替代事务生命周期治理,还会改变可见性语义,必须按业务验证。
- 追问 4:怎样防止连接池再次出现? 直接回答:统一事务模板、超时、空闲事务监控、泄漏检测和归还路径测试。
- 对应唯一正文:事务隔离与 MVCC(多版本并发控制)
- MySQL(关系型数据库)官方多版本机制
问题:支付事务提交超时,怎样判断成功、失败还是未知?
- 考点:提交未知态、幂等、三日志、账务分录与对账裁决。
- 回答思路:先承认客户端超时不等于数据库失败,再以原业务键分层查证和恢复。
- 详细答案:客户端超时可能发生在提交前、提交已持久化但响应丢失,或主库切换期间,因此必须以原商户请求号查询支付单、唯一分录、redo log(重做日志)恢复边界和渠道受理结果。只有数据库与渠道证据都明确失败时才能按原键重试,其余情况保留未知态并禁止生成新支付意图。
- 进阶追问:两阶段提交能否让支付超时直接判定为成功?
- 进阶回答:两阶段提交降低 InnoDB(事务存储引擎)提交状态与 binlog(二进制日志)不一致的窗口,却不能证明客户端是否收到结果或渠道是否已受理。支付结论仍须由幂等业务键、账务分录和渠道查单共同裁决。
- 事实等级:事务与幂等方案为 E2(既有材料映射),超时窗口为 E3(演练设计),具体渠道和事故结果为 E0(待核对)。
- 口述答案:支付提交超时后我只接受三种状态:有证据成功、有证据失败、当前未知,绝不把超时直接映射成失败。先固定商户请求号、支付单号、金额、币种、数据库事务时间、实例和渠道请求号,禁止调用方生成新键重试。数据库侧查询支付单、唯一索引和不可变账务分录;应用侧查事务前后的日志与连接错误;渠道侧用同一请求号主动查单。若支付单与借贷分录完整、金额币种一致,即使客户端没收到响应,也返回原成功结果;若事务明确回滚且渠道无受理事实,可以按原键重试;若数据库或渠道任一侧仍无法确认,就保持处理中或未知,进入受控查证,不能先退款或再扣一次。止血阶段限制该通道新请求和自动重试,保护账务写入与查询能力,并保存 redo log(重做日志)、binlog(二进制日志)位置、故障切换时间和实例角色证据。根因可能是提交后响应丢失、主库切换、连接超时配置过短或副本读旧值,修复分别落到幂等返回、写后读路由、切换栅栏和超时预算。历史恢复按支付单、渠道状态、账务分录和订单逐笔对账;任何自动补偿都用新的补偿业务键并关联原单。验证注入提交前断连、提交后丢响应、主库崩溃、副本延迟和重复回调,要求同一支付意图只生效一次、状态不倒退、借贷平衡,并覆盖至少一个渠道对账窗口。复盘将未知态年龄、查单成功率和人工裁决数量纳入告警,明确自动转人工的时间边界;真实发生次数没有记录就标 E0(待核对)。
- 追问 1:为什么不能超时后立即退款? 直接回答:原支付可能尚未确认成功,盲退会创建新的资金动作并扩大未知态。
- 追问 2:唯一索引冲突代表什么? 直接回答:只说明相同键已存在,还要读取原记录状态、金额和币种确认是否同一意图。
- 追问 3:读主库就能完全消除未知吗? 直接回答:不能,渠道和网络仍可能未知,但可避免副本延迟造成的数据库侧误判。
- 追问 4:补偿为什么要新业务键? 直接回答:补偿是独立可审计动作,需关联原单但不能与原扣款共享状态身份。
- 对应唯一正文:三日志与崩溃恢复
- MySQL(关系型数据库)官方 ACID(原子性、一致性、隔离性、持久性)模型
问题:redo log(重做日志)、undo log(回滚日志)和 binlog(二进制日志)如何共同支撑恢复?
- 考点:页级崩溃恢复、事务回滚、复制与时间点恢复、业务裁决。
- 回答思路:先分清三类日志职责,再按崩溃点和恢复目标组织证据与验证。
- 详细答案:redo log(重做日志)用于将已记录的页修改重放到一致状态,undo log(回滚日志)用于撤销未提交修改并提供 MVCC(多版本并发控制)历史版本,binlog(二进制日志)则提供复制和时间点恢复所需的逻辑变更序列。崩溃恢复优先由存储引擎完成页与事务收敛,误删恢复必须从备份加 binlog(二进制日志)在隔离环境重放,再用业务账本验收结果。
- 进阶追问:为什么不能直接拿主库的 binlog(二进制日志)回放到生产修复误删?
- 进阶回答:直接回放可能覆盖故障后已产生的合法写入,且难以准确截断到目标事务边界。应先在隔离实例验证恢复水位与差异,再把经过业务裁决的补偿操作受控写回。
- 事实等级:日志机制为 E2(既有材料映射),故障点为 E3(演练设计),生产刷盘参数和恢复目标为 E0(待核对)。
- 口述答案:我会先澄清三类日志不互相替代。redo log(重做日志)属于 InnoDB(事务存储引擎)的物理或逻辑物理修改记录,用于崩溃后把已记录的页变化重放到一致状态;undo log(回滚日志)保存回滚所需信息并支撑 MVCC(多版本并发控制)的历史版本,恢复时可撤销未提交事务;binlog(二进制日志)是服务器层逻辑变更序列,承担复制、审计和基于备份的时间点恢复。事故时我先定义目标:是单实例崩溃自动恢复、误删回到某时间点,还是主从切换后核对事务边界。单实例重启要看恢复日志、检查点、未完成事务和启动后页错误;误删恢复要从已验证备份启动隔离实例,再按 binlog(二进制日志)重放到目标事务之前,绝不直接在生产边试边恢复;切换则比较 GTID(全局事务标识)、最后提交事务和新旧主库写入栅栏。两阶段提交用于降低 InnoDB(事务存储引擎)提交状态与 binlog(二进制日志)状态不一致的窗口,但不能替代业务幂等和对账。止血是冻结高风险写、保存日志与拓扑、阻止旧主恢复写入;修复还包括校准刷盘策略、备份保留和恢复自动化。验证必须做真实恢复演练:重启后已提交保留、未提交回滚,时间点恢复的订单、支付分录和库存流水与目标水位一致,旧主无法产生分叉。复盘记录恢复点目标、恢复时间目标、日志保留、演练频率和负责人。配置与时长没有命令输出时只能保持 E0(待核对),不能背参数值冒充现场事实。
- 追问 1:有 binlog(二进制日志)为什么还要 redo log(重做日志)? 直接回答:前者面向逻辑复制与重放,后者面向存储引擎页级崩溃恢复,职责和粒度不同。
- 追问 2:undo log(回滚日志)能恢复误删吗? 直接回答:通常不作为业务时间点恢复接口,误删应使用备份加 binlog(二进制日志)隔离恢复。
- 追问 3:重启成功是否证明恢复正确? 直接回答:不够,还要核对提交边界、页错误、复制位置和业务账本。
- 追问 4:两阶段提交是否保证跨系统一致? 直接回答:它协调数据库内部引擎日志与 binlog(二进制日志),不自动覆盖渠道、缓存或消息系统。
- 对应唯一正文:三日志与崩溃恢复
- MySQL(关系型数据库)官方 redo log(重做日志)说明
问题:库存扣减出现锁等待,怎样区分热点竞争与死锁?
- 考点:等待图、锁范围、访问索引、重试反馈和库存不变量。
- 回答思路:先画事务等待关系,再用业务键分布判断普通热点、长持锁还是闭环。
- 详细答案:热点竞争表现为多个事务围绕同一库存记录单向等待,持有者提交后队列可继续推进;死锁则是多个事务持有和请求的记录或间隙形成闭环。排查应从锁等待图回溯到访问索引和业务键,确认是否因范围扫描、反向锁序或事务内慢调用扩大了锁持有时间。
- 进阶追问:为什么给库存表补索引后仍可能有死锁?
- 进阶回答:索引只能缩小扫描与加锁范围,不能消除不同业务路径以相反顺序锁定多条记录的环。还应统一按仓库、SKU(库存单位)等稳定顺序访问,并让重试带退避和总时限。
- 事实等级:锁机制和排查路径为 E2(既有材料映射),到达率与等待数为 E3(演练设计),生产热点和事故为 E0(待核对)。
- 口述答案:我不会把“请求卡住”都叫死锁。先固定仓库、SKU(库存单位)、订单行、事务标识、实例和时间窗,采集活跃事务、锁持有者、等待者、锁对象、索引、SQL(结构化查询语言)摘要和事务开始时间,画出有向等待图。普通热点竞争通常是很多事务单向等待同一个持有者,持有者提交后队列能推进;长事务是持有者长期不变,常见原因是事务内调用下游、批量更新过大或异常路径没提交;死锁则存在稳定闭环,数据库会选择牺牲者回滚。还要检查执行计划,因为缺少选择性索引会让更新扫描并锁住更大范围,Repeatable(可重复注解) Read(可重复读)下的范围访问还可能涉及间隙。E3(演练设计)若一个事务持锁 3 秒、热点到达率 120 个/秒,理论会新增 360 个等待机会,超时重试又会形成反馈。止血先限流或暂停该热点键,关闭无预算的自动重试,把外部调用移出新事务路径;若确认异常长事务,在保存证据并评估副作用后终止。修复根据证据选择缩短事务、统一访问索引和锁顺序、增加精准索引、条件更新或业务键分段,但数据库条件与唯一流水仍是最终裁决。验证要同时注入热点、慢下游和反向顺序,观察最老等待、完成率和重试下降,并复算期初、冻结、扣减、释放与期末库存,确认任何成功扣减都未越过可售量。恢复流量按热点键分档放开,失败即退回上一档。复盘记录等待链、错误重试和缺失告警;真实峰值与收益无原始数据时保持 E0(待核对)。
- 追问 1:锁等待超时是否等于事务全部回滚? 直接回答:要结合语句、事务配置和应用处理确认,业务层不能假设所有副作用自动消失。
- 追问 2:加大锁等待超时能否止血? 直接回答:通常只让请求等待更久并占用更多连接,不能修复持锁根因。
- 追问 3:热点库存能否完全无锁? 直接回答:可减少显式协调,但最终仍需原子条件更新、版本或串行账本裁决。
- 追问 4:怎样证明不是磁盘慢? 直接回答:锁等待图能指出阻塞关系,同时应对照输入输出和无锁查询延迟排除共同资源瓶颈。
- 对应唯一正文:InnoDB(事务存储引擎)锁与死锁
- MySQL(关系型数据库)官方锁模型
问题:线上死锁发生后,如何止血、修复并证明库存没有超卖?
- 考点:死锁证据、牺牲者处理、锁序、幂等重试与库存账本。
- 回答思路:保存等待环后先降反馈,再修事务结构,最后做技术与业务双重验收。
- 详细答案:数据库选择牺牲者回滚只解决当前等待环,止血还需限制热点入口和无界重试,并按原订单行查证事务外消息、库存流水与订单状态。根因修复应统一锁顺序、缩短事务并用精确索引收敛范围,验收则要同时证明条件扣减未越界、唯一流水未重复和库存账本守恒。
- 进阶追问:把死锁重试次数调高,为什么可能让事故更严重?
- 进阶回答:重试会向同一热点重新施压,使锁等待、连接占用和请求超时形成正反馈。应仅对可安全重试的牺牲者使用带随机退避、次数上限和原业务键的重试策略。
- 事实等级:死锁机制为 E2(既有材料映射),100 轮注入为 E3(演练设计),生产影响与恢复时长为 E0(待核对)。
- 口述答案:发生死锁后第一步不是重启,而是保存最近死锁信息、错误日志、参与事务的 SQL(结构化查询语言)、索引、锁对象、事务年龄和应用请求号,并把数据库线程映射回订单、仓库与 SKU(库存单位)。我要确认等待环的每条边:预占路径是否先锁库存再锁订单,取消路径是否相反;是否因范围条件缺索引扩大锁集合;是否有批量更新让多个键排序不稳定。止血时暂停或限流问题入口,关闭立即重试,保护连接池和正常仓库;数据库已经选出的牺牲者只代表当前事务回滚,应用必须按原请求号查询库存流水和订单状态,不能换键再扣。修复通常是所有路径按稳定业务键统一锁顺序、缩短事务、把通知和渠道调用移出事务、为范围条件补准确索引,并给死锁重试增加随机退避、次数和总时限。若业务可以用单条条件更新表达,就让
available >= quantity与版本条件承担原子裁决,同时以唯一订单行流水防重复。历史恢复要按死锁窗口列出已提交、已回滚和未知请求,逐笔核对冻结、扣减和释放归属;发现差异先冻结 SKU(库存单位),通过可审计调整单修复。验证用屏障控制两个事务反向交错至少 100 轮,再叠加慢下游与重复请求,要求等待图不再成环、重试收敛、库存不为负、唯一流水无重复、订单终态单调。复盘写明为什么测试没覆盖锁序、为何重试放大事故及后续门禁。没有事故原始记录时不能声称零超卖,只能标 E0(待核对)。 - 追问 1:数据库自动回滚一个事务为何还要业务查证? 直接回答:事务外可能已有消息、渠道请求或重试,且调用方未必正确识别牺牲者结果。
- 追问 2:统一锁顺序后是否绝无死锁? 直接回答:该等待环可消除,但其他索引范围、外键或新路径仍可能形成新的环。
- 追问 3:重启为什么不是根治? 直接回答:它只释放当前锁,反向锁序和重试模型不变时会再次发生。
- 追问 4:库存总量不负是否足够? 直接回答:不足,还要核对冻结归属、订单行幂等和释放状态,防止明细错配。
- 对应唯一正文:InnoDB(事务存储引擎)锁与死锁
- MySQL(关系型数据库)官方死锁说明
问题:复制延迟导致用户支付后查不到结果,怎样治理?
- 考点:复制阶段、写后读、一致性令牌、幂等与用户体验。
- 回答思路:先定位延迟发生在接收还是应用,再按读一致性分类止血和修复。
- 详细答案:复制延迟要拆分为主库生成 binlog(二进制日志)、副本接收、写入 relay log(中继日志)和应用事务四段,只有目标事务应用完成后副本才能对查询可见。支付等关键写后读应携带提交水位或回主库查询,重复点击始终由原商户请求号返回同一支付意图,不能把副本旧值当作失败。
- 进阶追问:半同步复制为什么仍不能保证支付查询立即读到新值?
- 进阶回答:半同步确认的边界通常是副本收到日志,不等于该副本已完成事务应用并对查询可见。应用仍需按提交令牌选择已追平副本,或在有限窗口回主库读取。
- 事实等级:复制与写后读方案为 E2(既有材料映射),积压量级为 E3(演练设计),真实副本拓扑和延迟为 E0(待核对)。
- 口述答案:我先确认支付写入在主库是否有明确提交事实,再检查结果查询实际路由到哪台副本,避免把读旧值误判成支付失败。复制链要拆成主库生成 binlog(二进制日志)、副本接收、写 relay log(中继日志)、协调与工作线程应用四段,保存主库提交 GTID(全局事务标识)、副本接收和应用水位、线程服务状态、最后错误、大事务与磁盘负载。
Seconds_Behind_Source(落后源库秒数)只作线索;线程停止或单个工作线程卡住时,单值可能不能表达真实可见水位。止血先把落后副本从支付状态和库存可售等关键读中摘除,在有界窗口让写后读回主库或选择达到提交令牌的副本,同时限制用户重复提交和暂停制造积压的大批次,不能通过跳过事件让副本看似追平。修复按根因处理:单个大事务拆成有业务检查点的小批,报表与在线副本隔离,校准并行应用,热点冲突回到事务和索引治理;应用侧让支付写入返回版本或事务令牌,后续查询携带该水位。即使结果页读到处理中,再次点击也沿用原商户请求号,由支付单和唯一分录返回原结果。恢复时要求接收与应用水位持续收敛、工作线程无错,按 10%、30%、60% 分档恢复读流量并覆盖完整峰值;业务侧核对支付状态单调、金额币种一致、重复分录为零。复盘补充副本可见水位、最老未应用事务和关键读误路由告警。所有延迟和收益数字无原始监控时保持 E0(待核对)。 - 追问 1:副本延迟为 0 能否恢复支付读? 直接回答:还要确认线程健康、目标事务已应用并在观察窗口内无反弹。
- 追问 2:半同步复制能否解决所有写后读? 直接回答:确认边界取决于配置和实现,它不等同所有副本已应用,应用仍需一致性合同。
- 追问 3:为什么不能跳过报错事件? 直接回答:会制造静默数据分叉,资金和库存必须先定位并修复事件原因。
- 追问 4:读主库会不会压垮主库? 直接回答:只对关键写后读临时或按令牌路由,并配合限流和容量保护,不是全量永久回主。
- 对应唯一正文:复制、高可用与备份恢复
- MySQL(关系型数据库)官方副本状态说明
问题:主库故障切换后,怎样证明没有丢单或重复记账?
- 考点:切换水位、旧主栅栏、恢复点、幂等和资金对账。
- 回答思路:按故障前最后确认事务、新主已应用事务和切换后新写建立三段时间线。
- 详细答案:应以 GTID(全局事务标识)和副本接收、应用水位划分故障前已确认、新主已覆盖和切换后新写三段事务,再把每笔支付和库存操作映射到稳定业务键。旧主必须通过网络、权限或租约被栅栏为只读,否则恢复后可能接受新写并形成双主分叉,单看新主可写不能证明零丢失。
- 进阶追问:发现旧主有新主未覆盖的已提交事务,应否直接全量回灌?
- 进阶回答:不能直接覆盖,因为该业务键可能已在新主被幂等重试或被补偿推进。应隔离提取缺失事务,结合渠道、账务和单调版本逐笔裁决后,再执行可审计的补写或冲正。
- 事实等级:切换与核对方法为 E2(既有材料映射),窗口和抽样为 E3(演练设计),真实 RPO(恢复点目标)与结果为 E0(待核对)。
- 口述答案:主库切换成功只说明服务重新可写,不证明业务完整。我先冻结拓扑变化,记录故障前旧主最后确认的 GTID(全局事务标识)、各副本接收与应用水位、提升时刻、代理路由版本和新主第一笔写;同时必须用网络、权限或租约把旧主栅栏为只读,防止恢复后形成双主。然后把故障窗口的请求分成三类:客户端明确成功、明确失败和超时未知。对支付按商户请求号查询支付单、渠道状态和不可变账务分录;对库存按订单行、仓库与 SKU(库存单位)核对条件更新和唯一流水。若旧主有已确认但新主未应用的事务,不能盲目复制旧库整表覆盖新主,而要在隔离环境提取缺失事务,按业务键判断是否已在新主重试生效,再做可审计补写或冲正。止血阶段限制资金写和库存高风险操作,暂停自动补偿与批任务,优先保留查询和对账能力。修复包括可靠故障检测、提升门禁、事务水位选择、旧主栅栏、客户端幂等和切换后写后读。验证要注入提交前崩溃、提交后回执丢失、复制延迟、提升中旧主恢复和重复请求,要求同一支付意图只产生一组平衡分录,库存不为负,未知态在限定窗口收敛。恢复流量按 10%、30%、60% 分档,每档核对错误率、复制水位和账本差异,任一反弹就回退;观察覆盖一个完整峰值和对账周期。复盘记录实际恢复点目标、恢复时间目标、人工决策和自动化缺口,并保存旧主重新入群前的数据重建证据;若没有原始切换与账本证据,只能把“零丢失”标为 E0(待核对)。
- 追问 1:选择延迟最小的副本提升就够吗? 直接回答:还要确认事务应用完整、数据一致、可写条件和旧主栅栏,单一延迟值不足。
- 追问 2:旧主恢复后为何不能直接加入? 直接回答:它可能包含分叉事务,需隔离比较并重新建立复制基线。
- 追问 3:怎样处理已确认成功却在新主不存在的支付? 直接回答:结合渠道与分录裁决,按审计补写或冲正,不能伪造原事务已存在。
- 追问 4:业务恢复看什么窗口? 直接回答:至少覆盖原故障峰值、积压清空和一次完整资金或库存对账周期。
- 对应唯一正文:复制、高可用与备份恢复
- MySQL(关系型数据库)官方复制监控表
问题:在线迁移双写出现新旧库分叉,怎样建立证据链?
- 考点:权威源、操作标识、版本、未知态、差异分类与可逆切流。
- 回答思路:先冻结迁移状态,按业务键重建每次操作在两库的完整时间线,再分类修复。
- 详细答案:双写分叉后先冻结路由版本、迁移水位和自动补偿,以业务键、操作标识、单调版本和内容摘要重建源库与目标库的写入时间线。再将差异按一侧失败、回执丢失、重复乱序、删除遗漏和旧实例写入分类,目标侧只接受更高版本的幂等更新,不能依据更新时间反向删除。
- 进阶追问:两库同一业务键的版本相同但内容不同,应以哪一边为准?
- 进阶回答:版本相同内容不同意味着版本生成、双写实现或摘要记录本身存在缺陷,不能仅按库的新旧裁决。应回到当前阶段的单一权威源和迁移账本,再用支付分录或库存流水等业务事实确认并修复。
- 事实等级:迁移状态机为 E2(既有材料映射),差异率和桶数为 E3(演练设计),真实迁移结果为 E0(待核对)。
- 口述答案:双写分叉发生后我先停止继续切流,明确当前阶段的权威源,并冻结路由版本、迁移水位和自动补偿,避免差异边查边变。证据必须以稳定业务键和操作标识为中心,收集旧库版本与提交时间、新库版本与提交时间、迁移账本状态、重试次数、应用实例、路由版本和删除标记,不能只按时间接近就认为是同一次写。随后把差异分成旧库成功新库失败、旧库成功新库未知、新库已成功但回执丢失、事件重复、事件乱序、删除遗漏和切流路由错误。未知态先按原键、版本和摘要查询,不能换键重放或反向删除。止血通常把读写回到当前权威源,暂停影子写和问题租户,摘除旧版本实例;资金和库存链路保留查询与对账能力。修复采用事务内 Outbox(发件箱)或可靠变更流记录源操作,目标侧以业务键加单调版本做幂等条件更新,低版本事件不得覆盖新值。数据校验先按稳定哈希分桶比较行数、版本、金额或数量摘要,再对差异键全量下钻;E3(演练设计)中五千万行即使差异率只有 0.002% 仍有一千条,不能按比例小就忽略。修复后重新跑全量轻量校验和差异键校验,再注入超时、丢回执、乱序、重启和旧实例恢复,要求差异清零、最老未同步年龄收敛、支付分录平衡、库存数量守恒。恢复按租户或虚拟分片分档切流,每档都有明确回退水位。复盘写明为何双写失败不可见、何时允许切读切写和旧库保留期;没有生产证据时切流收益保持 E0(待核对)。
- 追问 1:两库都成功是否代表一致? 直接回答:不代表,还要确认同一业务键、版本、内容摘要和状态语义一致。
- 追问 2:以更新时间较新的库为准可以吗? 直接回答:不可靠,时钟和迟到事件会误导,应按权威方向与单调版本裁决。
- 追问 3:差异修完能否立即删旧库? 直接回答:不能,要覆盖完整峰值、迟到事件和回滚窗口,并验证备份可恢复。
- 追问 4:为什么需要迁移账本? 直接回答:它保存操作身份、尝试、状态与水位,让失败可查证、可重放和可审计。
- 对应唯一正文:分库分表与迁移
- Percona Toolkit(数据库运维工具集)在线变更说明
- 问题:大表在线变更引发抖动,怎样止血与回滚?
- 考点:算法与锁模式、MDL(元数据锁)、磁盘和复制压力、工具边界。
- 回答思路:先确认变更阶段与阻塞链,再按可暂停、可取消和不可逆动作选择止血。
- 详细答案:Online DDL(在线数据定义语言)的
INSTANT(即时)、INPLACE(原地)与COPY(复制)算法在 MDL(元数据锁)、磁盘、redo log(重做日志)和复制压力上的边界不同,必须先确认实际阶段再决定暂停或取消。若长事务阻塞元数据锁,应先保护业务并清理可确认的异常事务;若分块复制或影子表写入压垮资源,则降速或暂停,并保持原表权威直到换表结果被校验。 - 进阶追问:
LOCK=NONE(不阻塞并发数据操作)为什么仍可能造成业务超时? - 进阶回答:它不排除开始或结束阶段的短暂 MDL(元数据锁),也不会消除重建索引、日志写入和复制应用的资源竞争。应以等待链、尾延迟和副本水位设自动暂停门槛,而不是只依据锁模式名称放行。
- 事实等级:Online DDL(在线数据定义语言)机制为 E2(既有材料映射),表量与阈值为 E3(演练设计),生产版本和影响为 E0(待核对)。
- 口述答案:大表 Online DDL(在线数据定义语言)抖动时,我先确认执行的具体语句、MySQL(关系型数据库)版本、算法、锁模式、开始时间和当前阶段,因为
INSTANT(即时)、INPLACE(原地)与COPY(复制)的资源和回滚边界不同,“在线”也不代表没有短暂 MDL(元数据锁)或资源竞争。证据上同时看元数据锁等待链、活跃长事务、线程运行数、磁盘空间与输入输出、redo log(重做日志)增长、临时在线日志、复制延迟和业务尾延迟。如果出现“长事务持有旧元数据—DDL(数据定义语言)等待独占元数据锁—后续普通请求排在其后”的队头阻塞,先停止新变更并处理明确异常长事务;如果是复制和磁盘被复制数据拖慢,则暂停或降低分块速率,保护支付与库存写。不能看到抖动就随意杀会话,要先确认取消是否需要长时间回滚、工具是否留有影子表和触发器。若使用 Percona Toolkit(数据库运维工具集),还要检查触发器冲突、外键、复制过滤、负载与延迟门禁及最终换表风险。止血后根据阶段决定回滚:未切换时保持原表权威并清理影子资产;已切换时先验证新表数据和写入方向,再用预先设计的反向同步或原子换回,不能凭表名猜。修复从变更前检查、磁盘预算、长事务清理、小流量演练和自动暂停阈值入手。验证重放高峰写、长事务、复制延迟和磁盘压力,要求变更可暂停、取消可解释、支付和库存结果无差异。复盘记录工具命令、阶段证据、错误阈值和回滚耗时;真实收益无记录时保持 E0(待核对)。 - 追问 1:
LOCK=NONE(不阻塞并发数据操作)是否绝不阻塞? 直接回答:不是,初始和结束阶段仍可能需要元数据锁,且资源竞争也会拖慢业务。 - 追问 2:为什么要先清理长事务? 直接回答:它可能持有旧元数据版本,让变更和后续请求形成队头阻塞。
- 追问 3:直接终止在线变更安全吗? 直接回答:要确认阶段、回滚成本和工具残留,先保护业务并保存现场。
- 追问 4:影子表行数相同可否换表? 直接回答:不够,还要核对键、版本、摘要、增量和触发器应用结果。
- 对应唯一正文:线上排障、项目话术与综合题库
- MySQL(关系型数据库)官方 Online DDL(在线数据定义语言)操作矩阵
- 问题:分库分表路由错库导致库存错扣,怎样排查和修复?
- 考点:路由输入、混合版本、双片查证、库存账本和可审计修复。
- 回答思路:先阻断继续分叉,再从原始业务键重算路由并以账本裁决权威记录。
- 详细答案:先冻结受影响路由版本,只允许当前权威分片继续写入,并用原始租户、仓库、SKU(库存单位)与归一化结果重算落点,定位配置半发布、字符串处理差异或旧实例恢复。修复时按订单行和库存流水裁决唯一有效动作,将正确记录以独立操作标识幂等补入权威片,对错误片保留可审计失效状态而非直接删行。
- 进阶追问:双片查询发现同一订单行各有一笔扣减记录,怎样避免二次释放库存?
- 进阶回答:先以订单行动作标识、单调版本和库存账本确定哪个动作已被承认,再只对重复的有效业务动作生成一次关联补偿。释放也必须走条件更新和唯一流水,不能按两片记录数量机械相减。
- 事实等级:路由治理方案为 E2(既有材料映射),错片样例为 E3(演练设计),生产影响范围为 E0(待核对)。
- 口述答案:路由错库后我先冻结受影响的租户、仓库和虚拟分片,摘除错误路由版本,禁止补偿任务和消费者继续按旧规则写;读侧可以临时双片查询用于查证,但只允许当前权威分片接受新写。证据从请求原始字段开始,保存租户标识、仓库、SKU(库存单位)、订单行、归一化前后值、哈希结果、路由表版本、逻辑分片、物理库表、实例版本和消息标识。常见根因包括应用把前导零保留为字符串,消费者却转成数字;大小写或字符集归一化不同;负数哈希处理不同;路由配置发布不原子;旧实例在切流后恢复消费。随后按稳定业务键在候选分片查询库存记录、冻结记录和唯一流水,不能简单选择更新时间较新的一条,因为迟到事件可能时间新但版本旧。权威裁决要回到订单状态、单调版本和库存守恒:期初加收入减出库,应等于可售、冻结和已扣的组合,每个订单行只能有一次有效动作。修复数据时生成独立调整或迁移操作标识,把正确记录幂等补到权威片,对错误片做可审计失效标记,不能直接删行掩盖证据;若已有下游消息,还要同步修复派生状态。代码层统一路由组件和规范化契约,所有异步任务携带路由版本,并让旧版本写入被栅栏拒绝。验证覆盖前导零、大小写、空值、负哈希、旧消息、实例恢复和重复重放,要求同键唯一落点、库存不为负、冻结归属正确、跨片汇总守恒。复盘补充路由决策日志、版本分布告警和切流门禁;生产影响无直接证据时保持 E0(待核对)。
- 追问 1:双片查询后把两边数量相加可以吗? 直接回答:不能,可能把同一业务意图重复计算,必须按业务键和版本去重裁决。
- 追问 2:为什么不直接移动错误行? 直接回答:还可能有流水、索引、消息和并发写,需要有状态的幂等迁移与审计。
- 追问 3:配置中心统一路由就够吗? 直接回答:不够,还要统一归一化代码、版本栅栏、消费者契约和回放测试。
- 追问 4:怎样圈定影响范围? 直接回答:按错误版本生效窗、实例、路由输入和候选分片反查业务键,再与流水交叉验证。
- 分库分表与迁移唯一正文
- 问题:扩容迁移期间跨分片重复写,怎样守住支付不变量?
- 考点:路由版本、全局幂等、状态单调、分录唯一和迁移回滚。
- 回答思路:以支付业务身份跨越物理分片,用全局账本和版本拒绝重复生效。
- 详细答案:扩容混合版本期间,物理分片的局部唯一索引无法阻止同一商户请求号分别写入旧片和新片,因此幂等边界必须由全局业务身份、路由版本和权威分片共同定义。支付单和 Outbox(发件箱)事件在本地事务中落库,目标侧以业务键加单调版本应用,迟到事件不得把成功状态覆盖为失败。
- 进阶追问:全局幂等账本不可用时,是否可以让两个分片都拒绝写入?
- 进阶回答:可以作为短期止血来冻结受影响虚拟分片,但不能成为常态,否则会把迁移故障扩大为全量支付不可用。恢复后要按原商户请求号、渠道受理结果和不可变分录裁决,再重放未完成操作。
- 事实等级:支付与迁移方案为 E2(既有材料映射),8 片扩 16 片为 E3(演练设计),真实拓扑和差异为 E0(待核对)。
- 口述答案:扩容时最危险的是把物理分片当成幂等边界。若旧规则
hash(key) % 8与新规则hash(key) % 16同时存在,同一商户请求号可能分别写入旧片和新片;两边局部唯一索引都不冲突,却产生两张支付单或两组账务分录。我的设计先给迁移单元分配明确路由版本和权威分片,应用、回调、消息消费者、查单与补偿都使用同一个路由组件;旧版本实例在分片切换后通过栅栏拒绝写入。支付幂等身份使用商户请求号、金额和币种,放在可全局裁决的幂等账本或可确定定位的业务片,不能由重试重新生成。写入支付单和 Outbox(发件箱)事件在本地事务内完成,目标分片按业务键加版本幂等应用;状态只能从创建向处理中、成功或关闭单调迁移,迟到失败不能覆盖成功。发现重复写时先冻结该虚拟分片和自动退款,按渠道请求号、支付单与不可变分录双片查证。若只有一边产生渠道扣款,就保留其业务事实并迁移索引;若两边都扣款,则这是两次真实资金动作,必须按业务规则冲正其中一笔,不能删数据库记录伪装恢复。数据修复后重新核对每个商户请求号只有一个有效支付意图、金额币种不变、借贷分录平衡、订单只推进一次。验证注入旧实例恢复、重复回调、迁移消费者重放、提交后丢响应和路由配置回退,要求所有重复都命中原结果或进入明确冲正。复盘记录路由发布为何未栅栏、局部唯一为何失效和资金对账门禁。没有生产账单证据时不能声称零资损,只能标 E0(待核对)。 - 追问 1:全局唯一标识能否代替商户请求号? 直接回答:系统生成标识可用于主键,但业务重试仍需稳定商户请求号识别同一意图。
- 追问 2:两片都有成功记录该保留哪条? 直接回答:先以渠道扣款和分录裁决真实资金动作,重复动作需冲正,不能按时间随意选。
- 追问 3:局部唯一索引为何不够? 直接回答:它只在单个物理分片内生效,错路由后两个分片可各自插入成功。
- 追问 4:迁移完成何时撤掉旧路由? 直接回答:迟到消息、旧实例和回滚窗口都清零,并覆盖完整对账周期后再受控撤除。
- 支付资金一致性项目串讲
- 问题:支付与库存跨库更新,怎样设计一致性与补偿?
- 考点:领域不变量、本地事务、Outbox(发件箱)、幂等、状态机和补偿边界。
- 回答思路:不追求模糊的瞬时全局一致,先分别守住资金与库存事实,再用可恢复流程收敛。
- 详细答案:支付库与库存库分别在本地事务内写入权威状态和 Outbox(发件箱)事件,消费方按商户请求号或订单行加动作执行幂等条件更新,状态机只允许单调迁移。支付成功而库存无法承诺时必须通过独立退款或冲正补偿,保留原支付、库存冻结和补偿记录的关联,不能删除原事实伪造一致。
- 进阶追问:消息已经投递但库存服务宕机,何时可以释放冻结?
- 进阶回答:不能由消息超时直接释放,应先按原订单行查询支付状态、库存动作和未消费事件,避免与恢复后的消费者并发执行相反动作。超过未知态预算后进入带幂等键的查证和人工裁决,明确支付失败或补偿完成才释放。
- 事实等级:一致性模式为 E2(既有材料映射),超时和重试为 E3(演练设计),生产采用方式与服务等级为 E0(待核对)。
- 口述答案:支付和库存跨库时我先定义两个领域不变量:支付侧同一商户请求号只能产生一次有效扣款,金额币种不变,账务分录不可变且借贷平衡;库存侧可售不能小于零,同一订单行的冻结、扣减和释放只能生效一次。订单成功不是一个模糊布尔值,而是支付确认、库存承诺和履约受理三个可审计事实的聚合。实现上每个服务只在自己的数据库事务内更新权威状态和 Outbox(发件箱)事件,消息可能重复,因此消费方以订单行加动作或商户请求号做幂等条件更新,状态机拒绝倒退。典型流程可先冻结库存再发起支付:库存冻结成功后支付超时保持未知,按原请求号主动查单;明确失败才释放冻结,支付成功则把冻结转为扣减。若支付成功而库存最终无法承诺,不能删除支付记录,应发起独立退款或冲正,并保留原单关联。止血时暂停新流量与自动补偿,分别查询支付单、渠道、分录、库存冻结和订单状态,防止两个恢复任务对同一未知单做相反动作。修复要有重试预算、最老未知态、死信人工入口和补偿幂等,不能靠分布式锁替代账本。验证注入消息重复、乱序、丢失、支付提交后断连、库存服务重启和补偿重复,要求最终每个订单落入明确终态,支付金额与分录一致,库存守恒,无重复退款,并覆盖一次完整对账周期。复盘记录状态定义、权威事实、人工裁决阈值和对账周期。具体生产流程若没有源码与渠道契约,只能作为 E2(既有材料映射),效果为 E0(待核对)。
- 追问 1:为什么不直接使用跨库 XA(扩展架构事务)? 直接回答:需按参与者、可用性、锁持有和故障模型评估,外部支付渠道通常也不在同一事务资源中。
- 追问 2:先扣款还是先冻结库存? 直接回答:取决于业务损失和补偿成本,但两种顺序都必须定义未知态与逆向动作。
- 追问 3:消息重复会不会重复扣库存? 直接回答:消费按订单行和动作做唯一或条件更新,重复事件返回原结果而不再次生效。
- 追问 4:补偿成功是否可以删原失败记录? 直接回答:不能,原动作和补偿都要保留审计关系,资金系统尤其需要不可变轨迹。
- 支付资金一致性唯一正文
- 问题:请完整设计一次 MySQL(关系型数据库)迁移失败演练与恢复验收。
- 考点:演练目标、故障矩阵、证据合同、止血、修复、业务恢复和复盘。
- 回答思路:以在线迁移状态机为主线,逐阶段注入可控失败,并用技术指标与支付库存不变量共同裁决。
- 详细答案:演练先限定可回滚的租户或虚拟分片,以基线复制、增量追平、双写观察、切读、切写和收口为阶段,并为每阶段定义权威源、GTID(全局事务标识)水位、暂停条件和回退动作。故障注入覆盖一侧超时、回执丢失、重复乱序、复制延迟、旧实例恢复和主库崩溃,恢复后按分桶摘要与差异键核对支付分录平衡、库存守恒和路由唯一性。
- 进阶追问:演练的技术指标全部恢复,为什么仍不能立即结束?
- 进阶回答:迟到消息、补偿任务和渠道账单可能在资源指标恢复后才暴露差异,因此必须覆盖完整峰值和至少一个业务对账周期。还要从备份完成隔离恢复并实际执行一次回退,证明检查点、旧实例栅栏和恢复脚本可用。
- 事实等级:演练方法为 E2(既有材料映射),数据量、阈值和轮次为 E3(演练设计),生产拓扑、恢复点目标与恢复时间目标为 E0(待核对)。
- 口述答案:我会先写演练合同,而不是直接断网络。范围限定一个可回滚租户或虚拟分片,明确旧库为初始权威、新库为影子,冻结数据样本、路由版本、备份水位和成功标准;支付要求同一请求只生效一次、金额币种不变、分录平衡,库存要求可售不为负、冻结扣减释放可对账。迁移按基线复制、增量追平、双写观察、切读、切写和收口六阶段推进,每阶段都有暂停、回退和证据门禁。故障矩阵依次注入旧库成功新库超时、新库成功回执丢失、增量重复与乱序、删除遗漏、复制延迟、迁移消费者重启、旧版本实例恢复、路由配置只发布一半,以及切换窗口主库崩溃。每次只改变一个主变量,再做组合演练。证据包统一保存业务键、操作标识、源目标版本、GTID(全局事务标识)、迁移水位、路由决策、应用实例、差异桶、支付分录和库存流水。止血脚本应能冻结单分片、把读写退回权威源、摘除旧实例、暂停自动补偿并保留查询;未知写按原键查证,禁止换键重试。修复后从检查点幂等重放,按分桶数量、版本、金额和数量摘要筛差异,再对差异键全量核对。恢复按 10%、30%、60%、100% 分档切流,每档要求积压和最老未知态下降、复制水位收敛、错误率不反弹,并覆盖完整峰值与一个对账周期。最后从备份做隔离恢复验证可用性,复盘比较实际恢复点目标、恢复时间目标、错误动作、监控缺口和负责人。所有数字先标 E3(演练设计),只有命令、日志和账本能把结论提升为 E1(直接证据)。
- 追问 1:为什么每次只注入一个主变量? 直接回答:便于建立因果和退出条件,单项稳定后再组合验证耦合故障。
- 追问 2:技术指标恢复后为何还要对账周期? 直接回答:迟到事件、补偿和账单可能晚于资源恢复,业务差异需要时间暴露。
- 追问 3:演练中可以使用生产全量吗? 直接回答:先从隔离、脱敏和小范围开始,满足保护与回滚条件后才逐步扩大。
- 追问 4:怎样判定回滚也可靠? 直接回答:实际执行切回、旧实例栅栏、检查点重放和差异校验,不能只评审脚本。
- 追问 5:什么证据可把结果升为 E1(直接证据)? 直接回答:可定位的命令输出、日志、配置、账本、校验结果和复盘记录共同支持。
- 对应唯一正文:分库分表与迁移
- MySQL(关系型数据库)官方 Online DDL(在线数据定义语言)失败条件
8. 复习清单
- 能把覆盖索引描述为查询与访问路径的关系,而不是索引的永久属性。
- 能用 SQL(结构化查询语言)、参数桶、统计快照和实际行数解释执行计划漂移。
- 能说明 Read View(读视图)、undo log(回滚日志)版本链和长事务清理滞后的关系。
- 能区分 redo log(重做日志)、undo log(回滚日志)与 binlog(二进制日志)的恢复职责。
- 能画出锁等待图,区分普通竞争、长事务、死锁和 MDL(元数据锁)队头阻塞。
- 能在死锁恢复后用唯一流水和数量守恒证明库存没有超卖或错配。
- 能把复制延迟拆成生成、接收、应用和业务可见四段,不迷信单一延迟值。
- 能说明支付提交超时为何必须保持未知态,并按原商户请求号查证。
- 能在主库切换后说明事务水位、旧主栅栏、未知请求和业务对账。
- 能按基线、增量、双写、切读、切写和收口讲清在线迁移状态机。
- 能用业务键、操作标识、单调版本和分桶摘要定位新旧库分叉。
- 能说明在线变更的算法、锁模式、磁盘、复制和回滚边界。
- 能解释直接修改取模基数为何导致大量键重映射,并给出路由版本与虚拟分片方案。
- 能在错路由后双片查证但坚持单一写权威,不用更新时间粗暴覆盖。
- 能分别说清支付金额分录不变量与库存数量状态不变量。
- 能区分止血、根因修复、历史数据修复、恢复验证和复盘五个阶段。
- 能把方法标为 E2(既有材料映射)、演练量级标为 E3(演练设计)、待核对生产事实标为 E0(待核对)。
- 能为每个结论给出可证伪证据和退出条件,不用重启、延迟下降或总量相等代替恢复证明。
