第四部分:数据库架构(第 41~50 题)
进入「第三阶段架构师能力」。聚焦 MySQL 性能、分库分表、读写分离、在线迁移、连接池、容灾。每题需给出:诊断方法→方案→容量→兜底→成本。
第 41 题:MySQL 慢 SQL 全链路排查 性能
SELECT ... WHERE status=? AND create_time BETWEEN ? AND ? ORDER BY id DESC 扫描 800 万行。要求:快速定位根因并根治。- 1. 排查套路?2. EXPLAIN 关键字段?3. 为什么没走索引?4. 临时止血?5. 根治+防复发?
第一步:分析。慢 SQL 是性能第一杀手,先定位(慢日志/APM)→ 诊断(EXPLAIN)→ 止血(限流/加索引/改写法)→ 根治。
第二步:挑战。全表扫、索引失效、锁等待、临时表。
第三步:架构。①开 slow log + 监控告警;②EXPLAIN 看 type/rows/key/Extra;③发现该 SQL 因 status 区分度低+范围条件致联合索引失效,建 (create_time, status, id) 覆盖索引;④止血:限流+加索引 online(gh-ost);⑤防复发:SQL 审核平台+压测门禁。
第四步:选型。慢日志/Performance Schema + EXPLAIN + gh-ost(在线加索引)+ APM(定位慢调用)。
第五步:一致性。在线加索引不锁表,业务无感;覆盖索引保证一致读。
第六步:高可用。慢 SQL 限流防拖垮;索引 online 不影响写入。
第七步:优化。覆盖索引免回表;避免 SELECT *;分页改用游标。
EXPLAIN 看什么:type(ALL=全表扫差)、rows(扫描行数)、key(用的索引)、Extra(Using filesort/temporary 危险)。索引失效:范围条件后的列不进索引、函数包裹列、隐式转换。为什么覆盖索引:索引含查询全部字段,免回表,rows 暴降。
// 覆盖索引: (create_time, status, id) 避免回表+filesort // 错误: WHERE status=? AND create_time BETWEEN ? AND ? ORDER BY id → 范围后列失效 // 正确: 索引顺序 create_time(范围)放前, status, id 供排序 EXPLAIN SELECT id,status FROM orders WHERE create_time BETWEEN '2026-01-01' AND '2026-02-01' AND status=1 ORDER BY id DESC LIMIT 20; // 在线加索引(gh-ost,不锁表) // gh-ost --table=orders --alter="ADD INDEX idx_ct_st_id(create_time,status,id)"
追问 1:加索引为什么锁表?原生 ALTER 会锁,用 gh-ost/pt-osc 影子表增量同步,online。
追问 2:索引有了还慢?看是否真用上(EXPLAIN)、统计信息是否过期(ANALYZE)、是否回表多。
追问 3:锁等待慢?查 information_schema.innodb_trx 找长事务/死锁;缩短事务。
追问 4:临时表爆?排序/分组字段加索引避免 filesort;调 tmp_table_size。
追问 5:防复发机制?SQL 审核(Yearning)+ CI 慢 SQL 扫描 + 压测 P99 门禁。
- ❌ 直接 kill 慢查询——复发。正确:定位根因+索引/改写。
- ❌ 主库 ALTER 加索引——锁表。正确:gh-ost online。
- ❌ 只看平均 RT——漏长尾慢 SQL。正确:看 P99+slow log。
第 42 题:索引设计与最左前缀陷阱 索引
(user_id, status, create_time),但「按 status 查全部待支付」全表扫;「按 user_id+create_time」也偶发慢。要求:设计一套既满足多查询模式又不冗余爆炸的索引策略。- 1. 最左前缀是什么?2. 联合索引列顺序怎么定?3. 索引越多越好吗?4. 区分度低的列能进索引吗?5. 覆盖索引怎么用?
第一步:分析。索引是「以空间换查询时间」,列顺序决定能否命中(最左前缀)。
第二步:挑战。顺序错致失效、冗余、写放大、区分度。
第三步:架构。①列顺序=高频等值(高区分度)在前,范围在后:如 (user_id, status, create_time) 支持 user_id 等值、user_id+status、user_id+范围;②低区分度列(status)单独查需额外索引或位图/过滤;③控制索引数(写多则少建);④覆盖索引减少回表。
第四步:选型。联合索引 + 覆盖索引 + 必要时函数索引/虚拟列。
第五步:一致性。索引不改变语义;覆盖索引需 INCLUDE 全部查询列。
第六步:高可用。索引 online 维护;避免大表锁。
第七步:优化。区分度评估( Cardinality);冗余索引清理。
最左前缀:索引 (a,b,c) 能加速 a、a+b、a+b+c,不能加速 b 或 c 单独。顺序原则:等值条件列(高区分度)放最左,范围列放最后,否则范围后的列用不上。冗余代价:每个索引拖慢写入、占空间;写多读少表应精简。
// 查询模式驱动索引设计 // Q1: WHERE user_id=? AND status=? AND create_time>? → idx(uid,status,ct) ✔ // Q2: WHERE status=? 单查 → 需 idx(status) 或函数索引(低频) // Q3: 只取 id,status → 覆盖索引 INCLUDE // MySQL 8 函数索引示例(低频特殊查询) // CREATE INDEX idx_status ON orders ((status));
追问 1:status 区分度低要不要索引?单独查低频可建;高频用「过滤+其他高区分列」或 ES 替代。
追问 2:ORDER BY 怎么走索引?ORDER BY 列须在索引中且顺序一致、无范围穿插,否则 filesort。
追问 3:索引选错?统计信息过期→ANALYZE TABLE;或 FORCE INDEX(谨慎)。
追问 4:联合索引 vs 多个单列?联合索引支持前缀组合更省;单列适合独立查询。
追问 5:写入太慢?索引过多,删冗余;批量写;写多读少表精简索引。
- ❌ 范围列放索引中段——后续列失效。正确:范围放最后。
- ❌ 索引越多越好——写放大。正确:按需精简。
- ❌ 低区分列带队——选择性差。正确:高区分在前。
第 43 题:分库分表设计与扩容 分片
- 1. 分片键怎么选?2. 分多少库表?3. 扩容怎么不停机?4. 跨分片查询怎么办?5. 热点分片(大商户)?
第一步:分析。分库分表=水平拆分降单实例压力,核心在分片键与扩容平滑。
第二步:挑战。分片键选择、跨分片、扩容停写、热点。
第三步:架构。①分片键:选高基且查询必带的列(user_id/order_id);②基因法:order_id 内嵌 user_id 基因,使按订单查也能路由到用户分片;③扩容:一致性 Hash 或「双倍扩容」——新 32 库,仅迁移一半数据(按取模翻倍),用 CDC 双写增量同步;④跨分片:ES/聚合层;⑤热点:基因扰动或独立表。
第四步:选型。ShardingSphere(分片+扩容)+ 一致性 Hash + CDC(增量同步)。
第五步:一致性。双写期以老库为准,校验一致后切读;扩容不丢数据。
第六步:高可用。每分片主从;扩容可回滚。
第七步:优化。分片数留余量(2 的幂利于翻倍);避免跨分片事务。
分片键=生命线:选错则大量跨分片查询。常用 user_id(用户维度查询多)。基因法:order_id 低位存 user_id%16,下单与查单同分片,免跨片。扩容:取模翻倍法最简单——uid%16→uid%32,仅一半数据需迁移;配合 CDC 双写保证不停机。
// ShardingSphere 配置(按 user_id 分片) spring.shardingsphere.rules.sharding.tables.orders.actual-data-nodes=ds$->{0..15}.orders_$->{0..63} spring.shardingsphere.rules.sharding.tables.orders.database-strategy.standard.sharding-column=user_id spring.shardingsphere.rules.sharding.tables.orders.database-strategy.standard.sharding-algorithm-name=mod16 // 基因法:生成 order_id 时低位嵌入 user_id 取模结果 long orderId = snowflake() * 16 + (userId % 16);
追问 1:跨分片分页/聚合?ShardingSphere 归并结果但效率低;重查询走 ES 宽表。
追问 2:分布式事务跨分片?尽量避免;必须则用 Seata/TCC,或最终一致(事务消息)。
追问 3:扩容期间双写不一致?CDC 追平增量;切读前校验行数/校验和;灰度切。
追问 4:大商户单分片热点?商户维度独立分片/打散;或单独大客户表。
追问 5:分片数怎么定?按 2 年数据量/单表 500 万行上限估算;取 2 的幂便于翻倍。
- ❌ 分片键选错——跨片查询爆炸。正确:高基+查询必带。
- ❌ 扩容停写迁移——业务中断。正确:CDC 双写在线。
- ❌ 单表无限大——DDL/备份崩。正确:提前分片。
第 44 题:读写分离与主从延迟 复制
- 1. 读写分离怎么路由?2. 主从延迟怎么治?3. 刚写就读走哪?4. 从库挂了?5. 延迟监控?
第一步:分析。读写分离用从库扛读,但异步复制有延迟,强一致读必须回主。
第二步:挑战。主从延迟、读不到刚写、从库故障、路由。
第三步:架构。①路由:写走主,普通读走从;②延迟敏感读(刚下单查单)强制走主(Hint/注解);③降低延迟:并行复制、半同步、从库提升规格;④从库挂:自动切主或摘除;⑤监控 Seconds_Behind_Master 告警。
第四步:选型。ShardingSphere(读写分离路由)+ MySQL 半同步 + 并行复制。
第五步:一致性。强一致读回主;最终一致读走从;业务标注读重要性。
第六步:高可用。从库故障转移;延迟超阈值降级读主。
第七步:优化。读多写少场景多从库分压;缓存扛热点读。
为什么读不到?异步复制下从库落后,刚写主库的数据还在路上。解法:「写后强制读主」窗口(如订单详情)或「写时打标,短时间读主」。半同步:主提交等至少一个从 ACK,降延迟但增写 RT,折中。
// ShardingSphere 读写分离 + 强制主库 Hint // 普通读自动走从;刚下单查单强制主 HintManager hm = HintManager.getInstance(); hm.setWriteRouteOnly(); // 本次读走主库 Order o = orderMapper.selectById(orderId); hm.close(); // 半同步开启(主库 my.cnf) // rpl_semi_sync_master_enabled=1
追问 1:延迟还是高?并行复制(按库/事务)+ 提升从库 IO;或写少场景用半同步。
追问 2:从库数据错乱?不,复制最终一致;仅延迟问题。防脑裂用 GTID + 半同步。
追问 3:所有读都走主?只强一致读走主(如余额、刚下单),普通列表走从。
追问 4:一主多从读不均?读负载均衡 + 权重;热点读走缓存而非从库。
追问 5:延迟监控怎么告警?监控 Seconds_Behind_Master + 复制心跳;超阈值告警+降级。
- ❌ 所有读走从——刚写读不到。正确:强一致读回主。
- ❌ 从库挂全崩——无降级。正确:降级主/摘除。
- ❌ 无延迟监控——客诉才发现。正确:实时告警。
第 45 题:在线数据迁移与双写校验 迁移
- 1. 不停机迁移步骤?2. 增量怎么追?3. 双写一致性?4. 怎么校验不丢?5. 怎么回滚?
第一步:分析。在线迁移=「全量+增量双写+校验+灰度切+可回滚」经典四步。
第二步:挑战。增量丢失、双写不一、校验、回滚。
第三步:架构。①全量:分批迁移历史数据(限速防压主);②增量:CDC(Canal)订阅 binlog 双写新库;③双写:应用同时写新旧(或 CDC 补偿);④校验:全量比对(行数/校验和)+ 增量对账;⑤灰度切读新库,异常回旧;⑥回滚:旧库一直保留到确认无问题。
第四步:选型。Canal/Debezium(CDC)+ 自研迁移 Job + 校验脚本 + 开关切读。
第五步:一致性。双写+校验保证新旧一致;切读前全量+增量均通过。
第六步:高可用。迁移限速保护源库;回滚随时可切。
第七步:优化。分批迁移+限速;校验用抽样+全量结合。
为什么 CDC?停写迁移会丢增量;CDC 订阅 binlog 实时追平,零停机。双写兜底:应用双写 + CDC 补偿,防止 CDC 中断丢增量。校验:不止行数,还要校验和/关键字段,发现不一致重投。
// 应用双写(兜底 CDC) public void createUser(User u) { oldRepo.save(u); // 旧库 newRepo.save(convert(u)); // 新库(分片) // 任一失败: 记入补偿表, 定时重试 } // Canal 订阅 binlog 增量同步(防 CDC 中断) // 校验: SELECT COUNT(*), CHECKSUM 分批比对新老库
追问 1:双写性能降?新库写异步/最终一致;或仅 CDC 同步,应用不双写(需 CDC 高可靠)。
追问 2:校验发现不一致?定位差异主键,重投增量,重校验;必要时人工修复。
追问 3:切读后新库慢?新库预热/索引就绪后再切;灰度 1%→100%。
追问 4:回滚丢增量?旧库持续收双写直到确认;回滚即停新库写,旧库完整。
追问 5:大表全量迁移慢?分批(按 id 段)+ 多并发 + 限速;夜间低峰提速。
- ❌ 停写迁移——丢增量。正确:CDC 在线。
- ❌ 只比对行数——内容差漏。正确:校验和+抽样。
- ❌ 切完即删旧库——无法回滚。正确:保留观察期。
第 46 题:深度分页与游标优化 查询
LIMIT 100000,20 扫 10 万行再丢弃,接口 4s 超时,DB 压力飙升。要求:优化深翻页且支持千万级数据导出。- 1. 为什么 LIMIT 深翻慢?2. 游标分页怎么做?3. 跳页(任意页)怎么办?4. 大数据导出?5. 一致性(翻页期间有新数据)?
第一步:分析。深翻页 LIMIT offset,N 要扫 offset+N 行再丢弃,offset 越大越慢。改用游标(基于有序键)。
第二步:挑战。扫全丢弃、跳页、导出、游标稳定性。
第三步:架构。①游标:WHERE id < lastId ORDER BY id DESC LIMIT 20(索引定位,免扫);②跳页:前端禁任意跳,或「估算页+游标」;③导出:游标分批流式导出(避免一次性);④一致性:游标基于稳定有序键,新增数据不影响已读。
第四步:选型。覆盖索引(id 有序)+ 游标 API + 流式导出。
第五步:一致性。游标基于单调键(id/时间),翻页期间插入不重复不漏。
第六步:高可用。游标查询走索引,DB 压力恒定。
第七步:优化。禁用深 offset;导出异步任务化。
游标本质:用上一页最后一条的有序键值作为下一页起点,索引直接定位,O(页大小) 而非 O(offset)。跳页:真实业务极少需要任意跳页,限制即可;非要则估算+游标近似。导出:游标分批,避免 SELECT 全量撑爆内存。
// 游标分页:基于上一页最大 id public List<Order> page(Long lastId, int size) { return orderMapper.select( "WHERE id < ? ORDER BY id DESC LIMIT ?", lastId, size); } // 导出:游标流式分批写文件 try (Writer w = ...) { Long last = Long.MAX_VALUE; while ((list = page(last, 1000)).size() > 0) { last = list.get(list.size()-1).id(); w.write(list); // 分批, 不撑内存 } }
追问 1:按时间排序游标冲突(同秒)?用 (create_time, id) 复合游标,id 兜底保证唯一有序。
追问 2:用户要跳到第 8000 页?限制最大页或「估算+不准跳」;ES 支持 from/size 但深翻也慢,仍建议游标。
追问 3:翻页期间数据插入重复?基于单调 id 倒序,新数据 id 更大在列表前,已读不重复;正序用 > lastId。
追问 4:导出超大数据?异步任务+游标分批+生成文件下载;或推 MQ 分片导出。
追问 5:复合排序游标?游标键 = 排序字段组合(如 score,id),下一页 WHERE (score,id) < (?,?) 字典序。
- ❌ 继续 LIMIT 100000,20——慢且崩。正确:游标。
- ❌ 一次性导出全量——内存爆。正确:游标流式。
- ❌ 任意深跳页——无必要且慢。正确:限制/估算。
第 47 题:数据库连接池调优 连接
- 1. 连接池设多大?2. 实例数×池 > DB 上限怎么办?3. 获取连接等待超时?4. 连接泄漏?5. 慢 SQL 与连接池关系?
第一步:分析。连接池不是越大越好。公式:池大小 ≈ (核心数×2) / 事务阻塞比;总量受 DB 连接上限硬约束。
第二步:挑战。总量超限、等待超时、泄漏、慢 SQL 占连接。
第三步:架构。①容量:单实例池 = min(DB上限/实例数, 经验值 10~50);②DB 侧:max_connections 留余量(含运维);③超时:connectionTimeout 短(如 300ms)快速失败而非堆积;④泄漏:leakDetection(如 5s 未还报警);⑤慢 SQL:占连接久→池耗尽,需限流+优化。
第四步:选型。HikariCP(默认最优)+ 配置按容量模型 + 监控。
第五步:一致性。连接池是资源约束,不影响数据一致;但耗尽致请求失败需降级。
第六步:高可用。池满快速失败+限流;DB 连接留余量防 OOM。
第七步:优化。缩短事务/SQL;连接复用;读走从库减主连接。
为什么不大?连接是 DB 资源,过多→上下文切换+内存+锁竞争反而降吞吐;且实例数×池必须 < DB 上限。容量模型:池大小 ≈ QPS×平均占用时长;或 CPU 核×2(IO 等待型)。等待超时:不设=线程无限等=雪崩;设短=快速失败+降级。
// HikariCP 容量模型配置 HikariConfig c = new HikariConfig(); c.setMaximumPoolSize(12); // ≈ DB上限/实例数 c.setConnectionTimeout(300); // 拿不到连接 300ms 快速失败 c.setLeakDetectionThreshold(5000); // 5s 未还连接报警(泄漏) c.setIdleTimeout(600000); c.setMaxLifetime(1800000); // 防长连接老化
追问 1:实例扩到 200 个?每实例池再调小(800/200≈4),或引入 ProxySQL/连接复用层聚合。
追问 2:池小了吞吐不够?瓶颈在 DB 本身,应优化 SQL/加从库,而非堆连接。
追问 3:连接泄漏定位?leakDetection 打栈;查未关的 Connection(用 try-with-resources)。
追问 4:maxLifetime 设错?须 < DB wait_timeout,防拿到已断连接报错。
追问 5:慢 SQL 占满池?慢 SQL 占连接久→池耗尽;限流+超时+优化慢 SQL 是根本。
- ❌ 池设 200 不乘实例数——超 DB 上限。正确:总量约束。
- ❌ 无 connectionTimeout——线程堆积雪崩。正确:快速失败。
- ❌ 不查泄漏——连接渐耗尽。正确:leakDetection。
第 48 题:MySQL vs PostgreSQL 选型 选型
- 1. 两者核心差异?2. 何时选 PG?3. 何时选 MySQL?4. JSON/地理/分析谁强?5. 混合使用?
第一步:分析。选型看数据模型与查询特征:MySQL 主打简单 OLTP 高并发,PG 主打复杂类型/分析/扩展。
第二步:挑战。场景匹配、团队能力、生态。
第三步:架构。①选 PG:JSONB 复杂查询、PostGIS 地理、窗口函数/CTE 复杂分析、需要扩展(时序/图);②选 MySQL:纯 OLTP 高并发写、团队熟练、云托管成熟;③混合:OLTP 用 MySQL,分析/地理用 PG 或 ClickHouse;④本场景 JSON+地理+分析 → 倾向 PG。
第四步:选型。PG(JSONB/PostGIS/分析)+ MySQL(高并发 OLTP)+ ClickHouse(海量分析)。
第五步:一致性。两者均 ACID;PG 更强约束(如 CHECK/窗口);按场景。
第六步:高可用。两者主从/集群;PG 用 Patroni,MySQL 用 MGR。
第七步:优化。避免单一库包打天下;按域选最优。
PG 强项:JSONB 索引查询、PostGIS、CTE/窗口函数、MVCC 更优(无 undo 膨胀)、扩展生态。MySQL 强项:极简 OLTP、高并发写成熟、云厂商支持好。本场景:JSON+地理+分析三需求 PG 全覆盖,且分析免额外 ETL,选 PG 更省。
// PG JSONB 查询 + GIN 索引 // CREATE INDEX idx_cfg ON t USING gin(cfg jsonb_path_ops); // SELECT * FROM t WHERE cfg @> '{"role":"admin"}'; // PG 地理: SELECT * FROM shop ORDER BY geom <-> ST_MakePoint(116,39) LIMIT 10; // Java: 用 JDBC/MyBatis 直接映射, 类型用 String(JSON)或 PGobject
追问 1:PG 性能不如 MySQL?纯简单 OLTP 写 MySQL 略优;复杂查询/类型 PG 更强。看场景。
追问 2:团队不会 PG?成本权衡:短期 MySQL+ES 补搜索/分析;长期培养 PG 或招人。
追问 3:事务/一致性?两者均 ACID;PG 约束更严,MySQL 默认 RR 需注意。
追问 4:混合运维成本?双栈增运维;按域重要性选,核心 OLTP 仍 MySQL。
追问 5:云托管?两家主流云均托管;PG 扩展(如 Timescale)看云支持度。
- ❌ 一律 MySQL——复杂查询硬扛。正确:按场景选 PG。
- ❌ 一律 PG——高并发简单写无优势。正确:OLTP 用 MySQL。
- ❌ 靠习惯选型——无依据。正确:数据模型驱动。
第 49 题:亿级数据归档与冷热分离 归档
- 1. 冷热怎么分?2. 冷数据存哪?3. 归档不停机?4. 冷数据查询?5. 成本怎么降?
第一步:分析。冷热分离=把低频数据迁出主库,主库只留热数据,性能↑成本↓。
第二步:挑战。归档不停机、冷存选型、查询、成本。
第三步:架构。①分:按时间(>1 年为冷)或访问频率;②冷存:低成本存储(对象存储/历史库/TiDB/列存);③归档:定时任务迁冷数据(CDC/分批),主库 DELETE(或分区 DROP);④查询:冷数据走独立查询服务/ES;⑤成本:冷存用廉价介质+压缩。
第四步:选型。分区表(按时间 DROP 旧分区)+ 对象存储/历史库 + ES 冷查。
第五步:一致性。归档后主库删,冷存保留;查询路由按时间。
第六步:高可用。分区 DROP 瞬时释放;归档限速保护。
第七步:优化。分区表免 DELETE 锁;冷存压缩降本。
分区表优势:归档旧数据用 DROP PARTITION 瞬时释放,比 DELETE 不锁表。冷存选型:对象存储(S3/OSS)最廉,配合 Parquet+压缩;或历史库降配实例。成本:冷数据占 90% 存储却 1% 查询,迁出后主库缩容,月费大降。
// 分区表按月份: orders_202601, orders_202602... // 归档: 每月 DROP 最旧分区(瞬时) jdbcTemplate.execute("ALTER TABLE orders DROP PARTITION p_202401"); // 冷数据迁对象存储(Parquet 压缩) archiveJob.exportToOSS("orders_202401"); // 分批限速 // 冷查: 路由到历史库/ES
追问 1:归档丢数据?先迁后删,校验一致;冷存多副本;删除可恢复窗口。
追问 2:冷数据要联表查?冷数据入湖(对象存储)+ 计算引擎(Spark/Presto)离线查;或 ES 索引。
追问 3:归档影响在线?分批限速 + 低峰执行;DROP 分区瞬时不锁。
追问 4:成本具体降多少?主库缩容(热数据小)+ 冷存廉价介质,典型省 60%~80% 存储费。
追问 5:热数据变冷?定时重判(访问频率),动态迁移;或按时间自动滚动。
- ❌ 全量留主库——备份/DDL 崩。正确:冷热分离。
- ❌ DELETE 旧数据——锁表慢。正确:DROP 分区。
- ❌ 冷数据也用高配——浪费。正确:廉价介质。
第 50 题:金融级高可用与容灾 容灾
- 1. RPO/RTO 是什么?2. 同城双活怎么实现?3. RPO=0 怎么保证?4. 异地灾备数据延迟?5. 容灾演练?
第一步:分析。容灾=「数据不丢(RPO)+ 快速恢复(RTO)」。RPO=0 需同步复制。
第二步:挑战。同步延迟、脑裂、演练、成本。
第三步:架构。①同城双活:主从半同步/同步复制(RPO≈0),应用双活、流量可切;②异地灾备:异步复制(RPO 秒级),灾备可起;③防脑裂: fencing/多数派(MGR/raft);④演练:定期切换演练(混沌);⑤成本:灾备平时降配或承载只读。
第四步:选型。MySQL MGR(多主/单主,多数派)/ 半同步 + 异地异步 + 容灾管控。
第五步:一致性。同步复制保证提交即多副本;异步灾备允许秒级 RPO。
第六步:高可用。自动故障转移;fencing 防双写;演练验证。
第七步:优化。灾备承载只读/报表降本;同步复制限同城(延迟低)。
RPO vs RTO:RPO=丢失数据量,RTO=恢复时长。RPO=0:必须同步复制(提交等从库 ACK),但增写延迟、依赖网络;跨城同步延迟高,故同城同步、异地异步。脑裂:网络分区时两主都写=数据冲突,用多数派+fencing 杀掉少数派。
// MySQL 半同步(同城 RPO≈0) // rpl_semi_sync_master_enabled=1; rpl_semi_sync_master_timeout=1000 // MGR 单主模式(多数派防脑裂) // group_replication_single_primary_mode=ON // 应用: 写主, 同城从同步; 异地异步灾备 // 容灾切换: 管控平台检测主宕 → 选新主(fencing 旧主) → 流量切
追问 1:同步复制拖慢写?同城 RTT 低(<1ms)影响小;超时退化异步保可用。
追问 2:异地 RPO 秒级能接受?支付核心异地仅灾备用,真实切换极少;RPO 秒级监管可接受。
追问 3:脑裂怎么彻底防?多数派(>半数)才能选主 + fencing(隔离旧主 IO)。
追问 4:灾备成本?灾备降配/承载只读报表/对账,平时不浪费。
追问 5:从不演练?不演练=容灾是假的;定期混沌演练+切换 SOP。
- ❌ 异步复制当 RPO=0——丢数据。正确:同城同步。
- ❌ 不防脑裂——双写冲突。正确:多数派+fencing。
- ❌ 建了不演练——切换必失败。正确:定期演练。