MYSQL · 进阶 / 高可用与分库分表

MySQL速查

锁机制与临键锁、undo/redo/binlog 日志体系与两阶段提交、主从复制与高可用、分库分表与分布式 ID、SQL 优化实战、备份恢复与延时从库——MySQL 进阶最常追问的 50 个核心考点一张表收齐,随查随用。

50条速查 8大主题 持续更新

📖 速查表

点击展开各小节

🔒 锁机制
名称说明要点 / 示例
全局锁 Flush Tables With Read Lock(FTWRL)让全库只读,用于逻辑备份 InnoDB 场景改用 --single-transaction 借 MVCC 一致性快照备份,不阻塞业务
表锁 lock tables ... read/write 显式加表锁;InnoDB 行锁够用时基本不用 MyISAM 全靠表锁:读读不冲突、读写与写写均冲突,写操作会饿等待
元数据锁 MDL 访问表自动加:增删改查加 MDL 读锁,变更表结构加 MDL 写锁;读写锁互斥 MDL 写锁等待会阻塞后续所有请求 → 长事务期间别执行 DDL 线上事故高发
意向锁 表级 IS/IX,标记「表内有行已加行锁」,使表锁申请无需逐行扫描判断冲突 意向锁之间互相兼容,只与表级读写锁互斥;行锁加锁时自动附带
记录锁 Record Lock 锁单条索引记录;查询条件无索引可用时,行锁退化为「扫到哪锁到哪」,近似全表加锁 行锁锁的是索引项而不是数据行本身 易混淆,所以更新要走索引
间隙锁 Gap Lock 锁索引记录之间的开区间间隙,阻止区间内插入,用于解决幻读 只在 RR(可重复读)隔离级别生效;间隙锁之间不冲突,但会互相阻塞插入
临键锁 Next-Key Lock 记录锁 + 间隙锁(前开后闭区间),RR 隔离级别下行锁的基本加锁单位 等值查询命中唯一索引时退化为记录锁 面试高频 加锁规则分析题核心
共享锁与排他锁 S 锁:select ... for share,兼容 S 阻塞 X;X 锁:for update,读写全阻塞 普通 select 是快照读不加锁(走 MVCC);当前读才加锁
死锁示例与排查 典型:两个事务以相反顺序更新两行互相等待;InnoDB 自动检测并回滚代价小的事务 排查:show engine innodb status 看 LATEST DETECTED DEADLOCK;预防:统一加锁顺序 + 小事务 + 合理索引
📜 日志体系
名称说明要点 / 示例
undo log 回滚日志:保存修改前的值,用于事务回滚与 MVCC 一致性读(ReadView + 版本链) insert undo 事务提交即删;update undo 保留供旧版本读,由 purge 线程清理
redo log 重做日志:InnoDB 层物理日志,记录「某页某处做了什么修改」;WAL 先写日志后写数据页 保证崩溃不丢已提交数据;循环写(ib_logfile),落盘策略由 innodb_flush_log_at_trx_commit 控制 必背
binlog Server 层逻辑日志,记录逻辑变更(SQL 或行变更),追加写不覆盖 用途:主从复制 + 恢复到指定时间点(point-in-time recovery)
redo vs binlog 对比 层次:InnoDB 物理 / Server 逻辑;写入:循环写 / 追加写;用途:崩溃恢复 / 复制与归档 两者由内部 XA(两阶段提交)协调一致 易混淆
两阶段提交 redo prepare → 写 binlog → redo commit;保证两份日志逻辑一致 不两阶段提交的后果:崩溃恢复后 redo 与 binlog 不一致 → 主从数据漂移 必背
binlog 三种格式 statement:记 SQL,量小但 NOW()/UUID() 等不确定函数会主从不一致;row:记行变更,量大但最安全;mixed:普通语句 statement、不确定函数切 row 5.7.7+ 默认 row;涉及主从一致与审计优先 row 面试高频
崩溃恢复 一句话:重启后扫描 redo log,对 prepare 状态的事务核对 binlog——binlog 完整则提交、不完整则回滚,再以 binlog 对齐从库 这就是两阶段提交在恢复期的裁决逻辑,保证主库与从库最终一致
🔁 主从与高可用
名称说明要点 / 示例
复制流程(三线程) 主库 dump 线程发送 binlog → 从库 IO 线程接收并写入 relay log(中继日志)→ 从库 SQL 线程重放 relay log 5.7+ SQL 线程可分裂为协调线程 + 多个 worker 并行回放 必背
异步复制 主库提交即返回,不等从库确认 性能最好;主库宕机可能丢失尚未传到从库的最新事务 默认模式
半同步复制 至少一个从库确认收到 binlog(写入 relay log)后主库才向客户端返回成功 after_sync / after_commit 两种等待点位;等待超时自动降级为异步,需监控恢复
GTID 全局事务标识 server_uuid:seq,复制位点自动定位 主从切换不再手工找 binlog file/pos,gtid_mode=ON 是现代架构标配 推荐
主从延迟原因 从库单线程重放(老版本)、大事务重放慢、从库机器规格低或承载大量读、主库批量写(批量导入/大更新) 延迟本质:主库并行写、从库串行(或半并行)读回放
主从延迟对策 开并行复制(MTS:基于库 / 基于 WRITESET)、拆分大事务、从库瘦身(关不必要 binlog)、关键读强制走主、监控 Seconds_Behind_Master 业务侧兜底:写后短时间内的读路由到主库 实战技能
MHA / MGR MHA:外部工具,故障时基于 binlog 补差并选新主;MGR:组复制,基于 Paxos 多数派协议强一致、自带选主 MGR 是 MySQL 8.0 官方方案;生产也常用 Orchestrator / 云高可用套件编排切换
主从复制链路
主从复制链路:事务写 binlog,IO 线程落到 relay log,SQL 线程重放达到最终一致
🗂️ 分库分表

拆分是最后的手段:先榨干索引、缓存与归档的潜力,再垂直拆、确实顶不住了才水平拆——拆分带来的复杂度会伴随系统整个生命周期。

名称说明要点 / 示例
垂直拆分 垂直分库:按业务域把不同表拆到不同库(订单库/商品库/用户库);垂直分表:把大字段、低频字段拆到扩展表 主表瘦身、冷热分离;伴随业务边界清晰化,与微服务拆分思路同源
水平拆分 同一张表按分片键把行分散到多个库表,突破单库数据量与 QPS 瓶颈 拆完的代价长期存在(跨片查询/事务/扩容),不到万不得已不拆 先优化再拆分
分片键选择 选查询中最高频的等值条件(如 user_id / buyer_id),让绝大多数查询命中单片 基因法:订单号嵌入分片基因,同一订单既可按买家也可按订单号定位 面试高频
跨片查询 无分片键的查询要广播到全部分片聚合,性能差 解法:异构索引表、ES 宽表、CQRS 冗余读模型、冗余常用查询维度字段
分布式 ID 分库后自增主键会冲突,需要全局唯一 ID 方案:号段模式(Leaf-segment)、雪花算法(时钟回拨要处理)、UUID(无序、不适合聚簇主键) 面试常考
扩容方案 一句话:双写迁移——新旧库双写、存量灰度迁移、校验一致性、读切流、最后撤旧写 翻倍扩容可预留 2^n 槽位或用一致性哈希减少迁移量
分片中间件 ShardingSphere(JDBC / Proxy 两种形态)、MyCat 等 应用层(JDBC)分片性能好、更可控;Proxy 形态对业务语言透明
🚀 SQL 优化实战
名称说明要点 / 示例
慢查询定位流程 slow_query_log + 设置 long_query_time → explain 分析执行计划 → 优化(索引/SQL 改写)→ 复测对比 线上批量分析用 pt-query-digest 聚合 top SQL 必背
explain 关键列 type(至少 range,避免 ALL 全表扫)、key / key_len(实际使用的索引)、rows(预估扫描行数)、Extra(附加信息) Extra 出现 Using filesort / Using temporary 要警惕;Using index 表示覆盖索引
深分页优化 limit 1000000,20 会扫描并丢弃前 100 万行 游标法:where id > 上次最大值 limit 20(连续翻页);延迟关联:子查询用覆盖索引取主键再回表 面试高频
count(*) 优化 InnoDB 必须遍历(MVCC 决定没有全局计数),count(1)≈count(*),count(字段) 忽略 NULL 更慢 思路:近似值 information_schema.tables、缓存计数(Redis/汇总表)、判断存在用 limit 1 / exists
join 优化 小表驱动大表(驱动表结果集小);Index Nested-Loop 要求被驱动表 join 字段有索引 无索引退化为 BNL(Block Nested-Loop);8.0.18+ hash join 兜底;on 字段类型/字符集一致防隐式转换失效
覆盖索引与索引下推 覆盖索引:查询列全在索引内,免回表;ICP 索引下推:where 条件下推到存储引擎层先过滤再回表 高频查询列表按需建联合索引,用 Extra 验证 Using index
索引失效常见场景 联合索引跳过最左列、对索引列做函数或运算、隐式类型转换(字符串列传数字)、like '%xx' 前缀模糊、or 连接非索引列 另一类「失效」是优化器判断回表代价高于全表扫而主动放弃 易混淆
💾 备份与恢复
名称说明要点 / 示例
mysqldump 常用参数 --single-transaction(InnoDB 一致性快照不锁表)、--master-data=2(注释记录位点便于搭从库)、--set-gtid-purged 配合 --databases / --tables / --where 控制备份范围
xtrabackup 物理备份 一句话:直接拷贝数据文件并应用备份期间的 redo,速度快于逻辑备份,适合 TB 级实例热备 流程:--backup 备份 → --prepare 整理 → --copy-back 恢复 大库首选
备份策略 每周全量 + 每日增量(binlog),保留期分级:7 天快速恢复 / 30 天追溯 / 年级异地归档 全量用 xtrabackup、增量靠 binlog,两者配合才能恢复到任意时间点
延时从库误删恢复 配置一台延迟从库(DELAY 一小时至一天);误删后立即 stop slave 冻结重放 让延时从库停在误操作前的那条 binlog,跳过该事务后重放即得到「误删前」数据 误删兜底
备份验证习惯 定期在测试环境恢复演练:验证恢复成功率与耗时,比对表行数、抽样数据、checksum 没验证过的备份不算备份;恢复 RTO/RPO 指标要写进运维预案
🧪 索引有效性实验

「加了索引为什么还慢」别靠猜,用 EXPLAIN 做一次前后对比实验:盯住 type、key、rows 三个字段的变化,索引是否生效一目了然。

-- 实验:orders 表约 300 万行,查询「某用户某状态最近的订单」很慢 -- 表结构:orders(id, user_id, status, amount, created_at) -- ① 建索引前:看这三个字段—— -- type = ALL 全表扫描,最差等级 -- rows = 3000000 预估要扫 300 万行 -- key = NULL possible_keys 有值但 key 为空 = 没用上索引 EXPLAIN SELECT * FROM orders WHERE user_id = 42 AND status = 1 ORDER BY created_at DESC; -- ② 建联合索引:等值列在前(user_id, status),排序列在后(created_at) -- 顺序依据:最左前缀原则 + 让 ORDER BY 免去 filesort CREATE INDEX idx_user_status_created ON orders (user_id, status, created_at); -- ③ 建索引后再跑同一句 EXPLAIN,对比变化—— -- type = ref 普通索引等值匹配,可接受等级 -- rows = 42 预估扫描几十行,从百万级掉到两位数 -- key = idx_user_status_created 索引真的被用上了 EXPLAIN SELECT * FROM orders WHERE user_id = 42 AND status = 1 ORDER BY created_at DESC; -- ④ 收尾:大量写入后统计信息可能失真,ANALYZE TABLE orders 刷新; -- 若优化器仍不选新索引,可用 FORCE INDEX 验证是统计问题还是写法问题
📑 表设计军规

建表时的 7 条铁律,条条都是踩坑换来的:字段类型选错、注释缺失,后期修复的代价远超建表时多花的十分钟。

军规原因说明 / 反例
金额用 DECIMAL FLOAT / DOUBLE 是二进制浮点,必然精度丢失 对账差一分钱查半天;用 DECIMAL(12,2),或 BIGINT 以「分」为单位存储 血泪教训
状态用 TINYINT + 字典表 省空间、可扩展,含义集中在字典表维护 0 待支付 / 1 已支付 / 2 已取消…;ENUM 加值要 DDL,魔法数字必须配字典表或枚举类
时间用 DATETIME(3) 毫秒精度排障有用,范围到 9999 年 TIMESTAMP 只到 2038 年且受时区影响;跨时区业务更要统一 DATETIME + UTC 约定
字符集统一 utf8mb4 MySQL 的 utf8 是 3 字节残缺实现,存不了 emoji 与生僻字 建库建表建连接三处统一;排序规则用 8.0 默认 utf8mb4_0900_ai_ci,混用会索引失效
必备 created_at / updated_at 排障与对账的生命线,缺了只能翻 binlog DEFAULT CURRENT_TIMESTAMP(3) + ON UPDATE 自动维护,不依赖业务代码
软删除用 deleted 标记 数据可恢复、审计有据,物理删除无法挽回 注意与唯一索引冲突:把标记列并入唯一键如 (email, deleted),或用 deleted_at 时间戳 易踩坑
每表每字段写 COMMENT 半年后没人记得字段含义,注释即文档 表注释写业务域与写入方,字段注释带单位与取值范围;配合 information_schema 可自动生成数据字典