编程进阶网 编程进阶网
首页
  • 在线工具
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 加解密
  • 时间日期
  • 网络工具
  • 颜色设计
  • 二维码
  • 开发实用
  • 计算机的原理
  • 操作系统原理
  • 网络协议原理
  • 数据库的原理
  • 序卷导读
  • 数据本质
  • 运行模型
  • 并发设计
  • 内存真相
  • 交互系统
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 技能之旅
  • 体系建设
  • 代码品质
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 性能优化
  • 数据结构导论
  • 线性结构详解
  • 树哈希结构论
  • 容器设计实战
  • 经典算法思想
  • 工程案例剖析
  • 算法题库精练
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++编程技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript案例
  • JavaScript高级
  • Kotlin精通
  • Android库解读
  • Android专栏
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • 毛选解读
  • 自我精进
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 在线工具
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 加解密
  • 时间日期
  • 网络工具
  • 颜色设计
  • 二维码
  • 开发实用
  • 计算机的原理
  • 操作系统原理
  • 网络协议原理
  • 数据库的原理
  • 序卷导读
  • 数据本质
  • 运行模型
  • 并发设计
  • 内存真相
  • 交互系统
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 技能之旅
  • 体系建设
  • 代码品质
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 性能优化
  • 数据结构导论
  • 线性结构详解
  • 树哈希结构论
  • 容器设计实战
  • 经典算法思想
  • 工程案例剖析
  • 算法题库精练
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++编程技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript案例
  • JavaScript高级
  • Kotlin精通
  • Android库解读
  • Android专栏
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • 毛选解读
  • 自我精进
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • 计算机基础
  • 计算机的原理

  • 网络协议原理

  • 操作系统原理

  • 数据库的原理

    • README
    • 01.数据库整体架构概览
    • 02.数据库索引底层原理
    • 03.数据库事务隔离级别
    • 04.数据库锁的实现原理
    • 05.数据库存储引擎对比
    • 06.数据库查询优化精要
    • 07.数据库日志系统设计
    • 08.数据库主从复制架构
    • 09.数据库分库分表方案
    • 10.NoSQL设计精要
    • 11.数据库的原理总结
      • 0.阅读导航
        • 0.1 按场景读
        • 0.2 按层次读
      • 1.第 01 篇·数据库整体架构概览
        • 1.1 速通卡
        • 1.2 展开:MySQL 的分层智慧
        • 1.3 诊断信号灯
        • 1.4 一句话拎走
      • 2.第 02 篇·数据库索引底层原理
        • 2.1 速通卡
        • 2.2 展开:B+Tree 的四个设计智慧
        • 2.3 诊断信号灯
        • 2.4 一句话拎走
      • 3.第 03 篇·数据库事务隔离级别
        • 3.1 速通卡
        • 3.2 展开:MVCC 的可见性判断
        • 3.3 诊断信号灯
        • 3.4 一句话拎走
      • 4.第 04 篇·数据库锁的实现原理
        • 4.1 速通卡
        • 4.2 展开:InnoDB 的加锁艺术
        • 4.3 诊断信号灯
        • 4.4 一句话拎走
      • 5.第 05 篇·数据库存储引擎对比
        • 5.1 速通卡
        • 5.2 展开:Buffer Pool 的三个秘密
        • 5.3 诊断信号灯
        • 5.4 一句话拎走
      • 6.第 06 篇·数据库查询优化精要
        • 6.1 速通卡
        • 6.2 展开:EXPLAIN 五星级解读
        • 6.3 诊断信号灯
        • 6.4 一句话拎走
      • 7.第 07 篇·数据库日志系统设计
        • 7.1 速通卡
        • 7.2 展开:三种日志的协作
        • 7.3 诊断信号灯
        • 7.4 一句话拎走
      • 8.第 08 篇·数据库主从复制架构
        • 8.1 速通卡
        • 8.2 展开:从异步到半同步的进化
        • 8.3 诊断信号灯
        • 8.4 一句话拎走
      • 9.第 09 篇·数据库分库分表方案
        • 9.1 速通卡
        • 9.2 展开:拆分的五个关键决策
        • 9.3 诊断信号灯
        • 9.4 一句话拎走
      • 10.第 10 篇·NoSQL 设计精要
        • 10.1 速通卡
        • 10.2 展开:Redis 的五个关键设计
        • 10.3 诊断信号灯
        • 10.4 一句话拎走
      • 11.全系列·贯通三句话
        • 11.1 数据走多远决定性能——I/O 是数据库的阿喀琉斯之踵
        • 11.2 有序是索引的一切——B+Tree 的设计哲学
        • 11.3 日志是数据库的生命线——WAL 统一了性能与可靠性
      • 12.全系列数据一页纸
      • 13.全系列症状诊断表
      • 14.学习路径建议
        • 14.1 半天速通
        • 14.2 系统建立数据库直觉(推荐)
        • 14.3 排查问题时
        • 14.4 自测清单
  • 计算机
  • 数据库的原理
杨充
2026-07-12
目录

11.数据库的原理总结

# 数据库的原理总结

10 篇数据库核心原理的精炼。每篇一张速通卡、一段展开、一句话带走。

# 0.阅读导航

# 0.1 按场景读

你遇到了什么 先读哪篇
一条 SQL 突然变慢,不知道从哪查起 → §06 查询优化(EXPLAIN 六步法)→ §02 索引原理(最左前缀)
订单表上千万行后查询越来越慢 → §09 分库分表(水平拆分)→ §02 索引原理(联合索引)
秒杀系统库存超卖 → §03 事务隔离(丢失更新)→ §04 锁的实现(乐观锁 vs 悲观锁)
数据库内存打满、Buffer Pool 命中率跳水 → §05 存储引擎(BP 调优)→ §01 整体架构(Buffer Pool 原理)
主从延迟导致用户看到旧数据 → §08 主从复制(并行复制)→ §07 日志系统(Binlog)
数据库突然死锁,业务线程僵住 → §04 锁的实现(死锁检测)→ §03 事务隔离(隔离级别选择)
机房断电后数据能不能恢复 → §07 日志系统(崩溃恢复)→ §05 存储引擎(Double Write)
想用缓存加速,但不知道缓存怎么配 → §10 NoSQL 精要(多级缓存)→ §05 存储引擎(BP 预热)
三表 JOIN 全表扫描 → §06 查询优化(索引改写)→ §02 索引原理(覆盖索引)
COUNT(*) 百万行要几十秒 → §06 查询优化(COUNT 替代)→ §09 分库分表(拆分思路)
面试被问 MySQL 核心原理 → §01 整体架构 → §02 索引 → §03 事务 → §07 日志 → §08 主从

# 0.2 按层次读

第 1 层·架构全景   → §01 整体架构概览
第 2 层·数据引擎   → §05 存储引擎对比 → §10 NoSQL 精要
第 3 层·查询与索引 → §02 索引底层原理 → §06 查询优化精要
第 4 层·并发控制   → §03 事务隔离级别 → §04 锁的实现原理
第 5 层·可靠与持久 → §07 日志系统设计
第 6 层·分布式扩展 → §08 主从复制架构 → §09 分库分表方案

# 1.第 01 篇·数据库整体架构概览

# 1.1 速通卡

维度 内容
遇到场景 订单列表加载 5 秒——user_id 单列索引扫描 35 万行,加了联合索引反而从 5 秒变 15 秒
暴露问题 不理解 MySQL 的分层架构,加索引凭直觉——范围查询 > 让排序列无法利用索引
核心知识 MySQL 分层(Server 层→引擎层)→ SQL 生命周期(连接器→解析器→优化器→执行器→引擎)→ EXPLAIN type 排序 → ACID 实现(undo/redo/MVCC/锁)
架构主线 Server 层管逻辑、引擎层管存储——一条 SELECT 在 6 个组件间完成接力,每个组件都有性能坑
加深理解 EXPLAIN 的 type 字段从左到右排一遍——ALL < index < range < ref < eq_ref < const,你的 SQL 卡在哪一级?

# 1.2 展开:MySQL 的分层智慧

Server 层五组件接力:连接器(权限快照,FLUSH PRIVILEGES 只对新连接生效)→ 解析器(词法分析→AST 语法树)→ 优化器(CBO 基于成本选索引)→ 执行器(调用引擎 API 取数据)→ 结果返回。

优化器三大常见陷阱:①索引选择不准(Cardinality 统计滞后→ANALYZE TABLE)②JOIN 驱动表选反(小表驱动大表是铁律)③子查询效率差(DEPENDENT SUBQUERY→改 JOIN)。

Buffer Pool 的内部逻辑:16KB 页 × 冷热分离 LRU(3/8 为冷区,5/8 为热区,old_blocks_time=1000ms 防一次性扫描冲刷热数据)。

ACID 一张表对应底层实现:A(undo log 回滚)、I(MVCC+锁)、D(redo log+Double Write)、C(前三者共同保证)。

# 1.3 诊断信号灯

信号 行动
EXPLAIN type=ALL 建索引,最差也要到 range
Extra=Using filesort ORDER BY 列加入索引,范围查询放末尾
EXPLAIN key 为空但建了索引 检查索引失效(函数/隐式转换/左模糊)
连接数飙升 慢查询拖长连接生命周期——先优化最慢的 10 条 SQL

# 1.4 一句话拎走

MySQL 不是黑盒——一条 SQL 穿过 6 个组件,每个组件都可能让你慢百倍。读懂 EXPLAIN,就拿到了慢查询的 X 光片。


# 2.第 02 篇·数据库索引底层原理

# 2.1 速通卡

维度 内容
遇到场景 大促 P99 从 50ms 飙升到 5s,用了 idx_user_id 却扫了 35 万行,Extra 出现 Using filesort
暴露问题 单列索引只覆盖 user_id,status 和 create_time 的过滤全在回表之后——回表一列 = 一次随机 IO
核心知识 B+Tree(多叉矮胖、高度=3 可存 80 亿行)→ 聚簇 vs 二级索引 → 最左前缀原则 → 覆盖索引(Using index)→ 十大失效场景
架构主线 索引的本质是有序数据结构——有序让二分查找成为可能,B+Tree 让磁盘随机 IO 变成顺序 IO
加深理解 EXPLAIN 的 Extra 里有 Using index 吗?——覆盖索引和不覆盖索引能差 500 倍

# 2.2 展开:B+Tree 的四个设计智慧

为什么不用二叉树:AVL 树 100 万行需要 20 层,每次磁盘 IO ~10ms,一次查询 200ms+。B+Tree 16KB 页装 1000+ 个键,3 层搞定——3 次磁盘 IO = 30ms。

聚簇索引 vs 二级索引:聚簇索引叶子节点存整行数据(数据即索引)。二级索引叶子节点存主键值 → 必须回表查完整行。回表代价:随机 IO 4-6 次 vs 主键查询 2-3 次。

最左前缀原则:联合索引 (a, b, c) 相当于建了 (a)、(a,b)、(a,b,c) 三个索引。断开条件:遇到范围查询(>, <, BETWEEN),后续列无法使用索引。

十大失效速查:函数运算 / 隐式转换 / 左模糊 LIKE '%xx' / OR 含非索引列 / NOT / != / IS NULL 看字段 / 联合索引不满足最左 / 范围列后的列 / 优化器认为全表更快。

# 2.3 诊断信号灯

信号 行动
rows 远大于返回行数 索引选择性差→重排联合索引列顺序(等值在前、范围在后)
Extra 有 Using filesort ORDER BY 不走索引→排序列加入联合索引
key_len 比自己预想的小 最左前缀断了→检查 WHERE 条件顺序
删了一个索引性能反升 优化器之前选错了索引→ANALYZE TABLE 更新统计

# 2.4 一句话拎走

索引 = 有序 + 矮胖 + 少回表。B+Tree 解决"有序"和"矮胖",覆盖索引解决"少回表"——掌握这三个词,你就理解了 90% 的索引优化。


# 3.第 03 篇·数据库事务隔离级别

# 3.1 速通卡

维度 内容
遇到场景 秒杀 999 件商品卖了 1203 件——SELECT stock→判断>0→UPDATE stock-1 两个线程同时读到 999
暴露问题 @Transactional 不是魔法——默认 RC 隔离级别下,读写之间的时间窗口就是超卖的温床
核心知识 ACID → 四种并发问题(脏读/不可重复读/幻读/丢失更新)→ 四种隔离级别 → MVCC(Read View + undo 版本链)→ 长事务灾难
架构主线 隔离性的本质是并发正确性的程度选择——越隔离越安全但也越慢,MVCC 用快照实现了"安全的并发"
加深理解 SELECT ... FOR UPDATE 和 SELECT 在 RR 级别下的区别?——前者当前读(读最新+加锁),后者快照读(读事务开始时的版本)

# 3.2 展开:MVCC 的可见性判断

Read View 的核心字段:m_ids(活跃事务列表)、min_trx_id(最小活跃 ID)、max_trx_id(下一个待分配 ID)、creator_trx_id(自己的事务 ID)。可见性规则:trx_id < min_trx_id → 可见(已提交);trx_id >= max_trx_id → 不可见(未开始);在区间内 → 查 m_ids 列表。

RC vs RR 的 Read View 差异:RC 每次快照读都创建新 Read View(能读到最新提交);RR 只在第一次快照读创建 Read View,事务内一直复用(可重复读)。

长事务三大灾难:①undo log 版本链无限膨胀 ②占用锁不释放 ③从库延迟放大。查出长事务:SELECT * FROM information_schema.INNODB_TRX WHERE TIME_TO_SEC(TIMEDIFF(NOW(), trx_started)) > 60。

秒杀修复四方案:①纯乐观锁 WHERE version=? ②悲观锁 SELECT FOR UPDATE ③Redis 预减库存 + DB 兜底 ④Redis + Lua 原子扣减。方案③是高并发场景的最佳实践。

# 3.3 诊断信号灯

信号 行动
库存超卖 区分快照读→改用当前读或乐观锁
undo 表空间持续增长 查长事务 → KILL 或重启
同一条 SQL 两次结果不一样 确认隔离级别→RR 快照读 vs RC 实时读
事务不报错但数据没变 可能被别的事务回滚覆盖→查 binlog

# 3.4 一句话拎走

事务 = MVCC 快照 + undo 版本链 + 锁 + redo 持久性。Understand Read View, understand everything.


# 4.第 04 篇·数据库锁的实现原理

# 4.1 速通卡

维度 内容
遇到场景 秒杀系统死锁——事务 A UPDATE WHERE id=10 vs 事务 B UPDATE WHERE id BETWEEN 5 AND 15,范围重叠互相等待
暴露问题 InnoDB 不是只有行锁——Record Lock 锁记录、Gap Lock 锁间隙、Next-Key Lock 锁记录+前间隙,RR 下默认 Next-Key Lock
核心知识 锁粒度(表/页/行)→ S/X 兼容矩阵 → 三种行锁 → 意向锁 IS/IX → UPDATE 加锁规则 → 死锁四条件 + 检测与避免
架构主线 锁的本质是在索引记录上加锁——无索引的 WHERE 条件 = 全表锁,这是最大的锁事故根源
加深理解 UPDATE WHERE id=10 和 UPDATE WHERE name='张三'(name 无索引)加的锁有什么不同?——前者 Record Lock 一行,后者锁全表所有行+间隙

# 4.2 展开:InnoDB 的加锁艺术

三种行锁的精确区分:

锁类型 锁范围 冲突对象 存在原因
Record Lock 索引记录 同记录 X 锁 行级互斥
Gap Lock 记录之间的间隙 Gap 之间不冲突 防幻读(INSERT 新行)
Next-Key Lock 记录 + 前间隙 等于 Record+Gap RR 默认锁

Gap Lock 之间不冲突:两个事务可以在同一个间隙上加 Gap Lock——因为 Gap 的目的不是互斥而是"禁止插入",两个 Gap 的意图兼容。这是死锁根因:事务 A Gap Lock,事务 B Gap Lock,然后 A 尝试 INSERT→等待 B 释放 Gap,B 尝试 INSERT→等待 A 释放 Gap→死锁。

UPDATE 加锁规则速记:唯一索引等值且记录存在→Record Lock;唯一索引等值但记录不存在→Gap Lock;普通索引等值→Next-Key Lock + 下一个 Gap;范围查询→Next-Key Lock 一路到头;无索引→全表 Next-Key Lock。

死锁避免六法:①固定加锁顺序 ②缩短事务时间 ③用乐观锁替代悲观锁 ④批量操作拆小批次 ⑤RC 隔离级别减少 Gap Lock ⑥重试机制 + 指数退避。

# 4.3 诊断信号灯

信号 行动
SHOW ENGINE INNODB STATUS 有 LATEST DETECTED DEADLOCK 解读等待图 → 调整加锁顺序
事务等待超时(Lock wait timeout) 查 information_schema.INNODB_TRX → 找阻塞源
简单 UPDATE 极慢 大概率 WHERE 条件无索引 → 正在锁全表
RC 级别下死锁比 RR 少 RC 没有 Gap Lock → 但这意味着可能有幻读

# 4.4 一句话拎走

无索引 = 全表锁,有索引 = 精确锁。WHERE 条件的索引覆盖率决定锁粒度——加锁的第一课是给 WHERE 条件建索引。


# 5.第 05 篇·数据库存储引擎对比

# 5.1 速通卡

维度 内容
遇到场景 大促当晚 Buffer Pool 命中率从 99% 跳水到 75%,P99 从 30ms→800ms,Free buffers 只剩 64 个
暴露问题 缓冲池太小(2GB)被热数据撑爆——Buffer Pool 是 InnoDB 性能的心脏,命中率掉 1% 延迟涨 10 倍
核心知识 InnoDB 五层存储架构 → 16KB 页结构 → Buffer Pool(冷热分离 LRU + Free/LRU/Flush 三链表)→ Change Buffer(写加速 5-10 倍)→ Double Write → AHI
架构主线 InnoDB = B+Tree 的物理实现 + 内存缓冲池的缓存艺术 + 预写日志的崩溃恢复——三者是同一个系统的不同切面
加深理解 SHOW ENGINE INNODB STATUS 里 Buffer pool hit rate 是多少?——低于 95% 就该扩容 Buffer Pool 了

# 5.2 展开:Buffer Pool 的三个秘密

三链表协作:Free 链表管理空闲页(被取走从 Free 移除),LRU 链表存所有使用中的页(冷热分离 3/8 分界),Flush 链表存脏页(修改过未刷盘的页)。淘汰时从 LRU 冷区尾部取→归还给 Free 链表。

Change Buffer 的写加速:更新的二级索引页不在 Buffer Pool 时,不去磁盘读取——修改缓存到 Change Buffer,等页被读入时再合并。唯一索引不能用(需读盘判重)。全局唯一 vs 普通索引的写入性能可差 5-10 倍。

Double Write 防部分写失效:16KB 页刷盘写到一半断电→页损坏。Double Write 先顺序写 1MB 到共享表空间的 Double Write 区→再随机写到数据文件→如果第二步写到一半断电→恢复时从 Double Write 区还原完整页。

InnoDB vs MyISAM 决定性差异:InnoDB(行锁/事务/CRASH SAFE/B+Tree/聚簇索引),MyISAM(表锁/无事务/崩溃易损坏/B+Tree 叶子存指针/非聚簇)。选 InnoDB 不需要理由,选 MyISAM 需要理由。

# 5.3 诊断信号灯

信号 行动
BP 命中率 <95% 增大 innodb_buffer_pool_size
Free buffers < 总量的 5% 同上 + 检查是否有全表扫描冲热数据
脏页比例 >75% 调大 innodb_io_capacity 或 innodb_max_dirty_pages_pct
写入 TPS 上不去 检查 Change Buffer 是否被唯一索引禁用

# 5.4 一句话拎走

InnoDB = 16KB 页的 B+Tree + Buffer Pool 冷热 LRU + Change Buffer 写加速 + Double Write 防损坏。调优四步:看命中率→看空闲页→看脏页→调 io_capacity。


# 6.第 06 篇·数据库查询优化精要

# 6.1 速通卡

维度 内容
遇到场景 电商首页三表 JOIN 加载 5 秒——全部 type=ALL,rows=289 万行返回仅 20 行
暴露问题 不知道 EXPLAIN 该看哪几个字段——type 和 Extra 是唯二必看的,rows 是第三位
核心知识 EXPLAIN 字段解读(type/Extra/rows/key/key_len)→ 慢查询日志 + pt-query-digest → SQL 改写(LIMIT 深分页/COUNT 优化/子查询改 JOIN)→ Optimizer Trace
架构主线 SQL 优化的本质是让查询尽可能用上索引 + 不回表 = 减少扫描行数 = 减少磁盘 IO
加深理解 EXPLAIN FORMAT=JSON 比 EXPLAIN 多了什么?——多了具体的代价估算(cost_info)和使用的索引覆盖度(used_columns/attached_condition)

# 6.2 展开:EXPLAIN 五星级解读

type 从最优到最差:system > const(PK 等值) > eq_ref(JOIN PK) > ref(索引等值) > range(索引范围) > index(全索引扫描) > ALL(全表扫描)。只要 type 是 ALL 或 index,rows 超过返回行数 100 倍,必须优化。

Extra 关键标志位:Using index(覆盖索引,最优!)> Using index condition(ICP 下推,良好)> Using where(过滤,一般)> Using filesort(排序未用索引,差!)> Using temporary(临时表,最差!)。出现最后两项必须修正。

SQL 改写三招:①LIMIT 深分页(OFFSET 1000000→∞)→ 改用基于主键的"上一页最后 ID→下一页"②COUNT(*) 上亿大表→改用 Redis 计数器或估算值 ③子查询 IN (SELECT ...) → 改 JOIN(或 MySQL 5.7+ 自动优化为 semi-join,但不可靠)。

六步优化法:EXPLAIN→看 type 和 Extra→建/改索引→改 SQL→验证 EXPLAIN→压测确认。效果对比:rows 289 万→50,5s→8ms(625 倍提升)。

# 6.3 诊断信号灯

信号 行动
type=ALL, rows 百万级 建联合索引,覆盖 WHERE+ORDER BY
Extra=Using temporary 检查 GROUP BY 和 ORDER BY 是否不同列
Optimizer Trace 显示选择了错误索引 FORCE INDEX 临时救急 → ANALYZE TABLE 更新统计
慢查询日志里同一类 SQL 反复出现 用 pt-query-digest 聚合→优先优化执行次数最多的

# 6.4 一句话拎走

EXPLAIN 两张脸决定一切:type 告诉你怎么找数据,Extra 告诉你找到之后还要做什么。出现 filesort 或 temporary,一定有优化空间。


# 7.第 07 篇·数据库日志系统设计

# 7.1 速通卡

维度 内容
遇到场景 机房断电后 MySQL 重启——不仅没丢数据,连崩溃前最后几秒提交的事务都在
暴露问题 不知道崩溃恢复的原理——redo 让已提交的不丢,undo 让未提交的不留,binlog 让主从同步不断
核心知识 WAL 机制(先写日志再写盘,快 50-100 倍)→ Redo Log(循环写/LSN/Checkpoint)→ Binlog(三种格式/两阶段提交)→ Undo Log(回滚+MVCC)→ 崩溃恢复七步
架构主线 三种日志是一个完整的可靠性三角——redo 管持久、undo 管原子、binlog 管同步
加深理解 innodb_flush_log_at_trx_commit=0/1/2 的区别?1=每次提交刷盘(最安全最慢),0=每秒刷(丢 1 秒数据),2=写入 OS 缓存(OS 崩溃丢数据)

# 7.2 展开:三种日志的协作

WAL(Write-Ahead Logging) 的数学根基:随机写盘寻道 5-10ms,顺序写 redo 日志追加 ~0.1ms——快 50-100 倍。脏页可以延迟刷盘,只要 redo 里有记录,崩溃恢复就能重放。

Redo Log 的 Checkpoint 机制:LSN(Log Sequence Number)是全局物理指针。LSN_checkpoint 之前的日志已安全刷盘可以覆盖。Checkpoint 的作用是告诉恢复系统"从这里开始重放即可"。

Binlog 两阶段提交:①Prepare(写 redo,状态=prepare)②写 binlog ③Commit(redo 状态=commit)。任何一步崩溃后,恢复时检查 binlog 是否完整——binlog 完整则根据 redo commit,binlog 不完整则 redo rollback。这是分布式事务二阶段提交在单机上的经典应用。

UPDATE 一条记录触发三种日志:①查 Buffer Pool,没有则磁盘读入 ②写 undo log(旧值,用于回滚和 MVCC)③修改 Buffer Pool 中的页(变脏页)④写 redo log(新值,prepare 状态)⑤写 binlog ⑥redo log 变 commit 状态→脏页异步刷盘。

# 7.3 诊断信号灯

信号 行动
SHOW ENGINE INNODB STATUS 中 Log sequence 和 Log flushed 差很大 redo 写压力大,检查 innodb_log_buffer_size
Binlog 磁盘使用暴涨 可能有未提交的大事务→SHOW BINLOG EVENTS 看大小
崩溃恢复特别慢 redo log 太大或 Checkpoint 间隔太长
从库延迟 主库 binlog 量大→开启 ROW 格式+并行复制

# 7.4 一句话拎走

Redo: 已提交不丢,Undo: 未提交不留,Binlog: 主从准时。WAL 是整个可靠性设计的基石——先写日志、后写数据,崩溃了从日志重建。


# 8.第 08 篇·数据库主从复制架构

# 8.1 速通卡

维度 内容
遇到场景 双11支付回调写主库、订单查询走从库——用户看到"支付超时"实际支付成功,主从延迟惹的祸
暴露问题 主从延迟 = 主库写 binlog + 网络传输 + IO 线程写 relay log + SQL 线程重放——四个环节任何一个都可能成为瓶颈
核心知识 三种复制方式(异步/半同步/全同步)→ GTID(自动定位 binlog 位置)→ 并行复制(LOGICAL_CLOCK/WRITESET)→ 读写分离延迟 5 策略 → 高可用架构(MHA/Orchestrator/MGR)
架构主线 主从复制的本质是在主库上执行的写操作,在从库上再执行一遍——Binlog 是两者之间的"操作日志"
加深理解 SHOW SLAVE STATUS 里 Seconds_Behind_Master 为 0 代表什么?——只代表 SQL 线程没有堆积的 relay log,不代表主从数据一致(可能正在复制但没追上)

# 8.2 展开:从异步到半同步的进化

异步复制的三个致命问题:①主库 binlog 可能还没传到从库就宕机→数据丢失 ②SQL 线程单线程回放→延迟累积 ③GTID 无记录→主从切换需要手动找位点。

半同步复制:主库提交事务时至少等待一个从库确认收到 binlog。AFTER_SYNC(Master 写 binlog→等从库 ACK→提交引擎层,推荐)vs AFTER_COMMIT(先提交再等 ACK,有幻读风险)。从库超时→自动降级为异步→从库恢复后自动升回半同步。

GTID 的三个好处:①自动定位——CHANGE MASTER 不需要 MASTER_LOG_FILE/MASTER_LOG_POS,MySQL 自己知道从哪开始 ②主从切换友好——Orchestrator 故障转移零手动 ③自动跳过已执行事务——RETRIEVED_GTID_SET vs EXECUTED_GTID_SET 查差异。

并行复制:DATABASE(按库,有跨库局限)→ LOGICAL_CLOCK(组提交内的事务可并行,要求 same last_committed)→ WRITESET(行级冲突检测,并行粒度最细,MySQL 8.0 默认)。

读写分离延迟五策略:①读主库(关键业务)②前端规避(同一用户写后读强制走主库)③延迟阈值(seconds_behind_master > 2 不读该从库)④半同步复制 ⑤缓存补偿(把刚写的数据同时写 Redis,读优先走缓存)。

# 8.3 诊断信号灯

信号 行动
Seconds_Behind_Master 持续增长 SQL 线程单线程瓶颈→开并行复制+拆分大事务
IO 线程报连接错误 网络/防火墙→CHANGE MASTER 重新指定
GTID 集合不一致 检查是否有人在从库上直接写了数据(严重禁忌)
从库 CPU 高但主库正常 SQL 线程回放压力→检查是否有大量 UPDATE/DELETE 大事务

# 8.4 一句话拎走

主从复制 = Binlog 传输 + Relay Log 重放 + GTID 自动定位 + 并行回放。延迟是主从架构的永恒问题——读写分离时永远记得"刚写的一定读主库"。


# 9.第 09 篇·数据库分库分表方案

# 9.1 速通卡

维度 内容
遇到场景 订单表从 500 万涨到 2 亿行,单表查询 200ms,COUNT(*) 30 秒——B+Tree 维护成本指数级增长
暴露问题 单表 2000 万行后,即使有索引,页分裂和 Buffer Pool 命中率下降让性能崩塌——水平分表是唯一的破局方式
核心知识 垂直拆分 vs 水平拆分 → 分片键选择三铁律 → 分片算法(取模/范围/一致性哈希)→ 雪花算法分布式 ID → 双写零停机迁移 → 中间件(ShardingSphere)
架构主线 分库分表 = 分片键 + 分片算法 + 分布式 ID + 路由中间件 + 灰度迁移——五件少任何一件都是灾难
加深理解 WHERE user_id = 123 能精准路由到 1 张分表,WHERE create_time > '2024-01-01' 却要广播 32 张表——分片键决定了哪些查询能精准路由

# 9.2 展开:拆分的五个关键决策

分片键三铁律:①绝大多数查询必须带这个字段(不带=全分片广播)②数据分布尽量均匀(热点分片=木桶的短板)③不可变(一旦变了=数据迁移)。用户系统的分片键首选 user_id,订单系统的分片键首选 user_id 或 order_id。

雪花算法 64bit 结构:1bit 保留 + 41bit 毫秒时间戳(可用 69 年)+ 10bit 机器 ID(1024 台)+ 12bit 序列号(每毫秒 4096 个)。单机理论 TPS=409.6 万/秒。时钟回拨问题:等时钟恢复 + 用历史最大序列号 + 备用机器 ID。

双写零停机迁移四阶段:①存量数据全量迁移(不停止旧服务)②增量双写(新数据同时写新旧两套分片,中间件记录差异日志)③对比校验(定时对账,补偿差异)④灰度切读(先 1%→10%→50%→100%),切读确认无误后停止双写旧库。

跨分片查询三大思路:①精准路由(WHERE 带分片键→只查 1 张表,最快)②全分片广播(无分片键→查所有表→合并排序,代价大)③冗余索引表(另建一张 order_id→user_id 映射表,通过它找到分片键再精准查询)。

# 9.3 诊断信号灯

信号 行动
单表 >2000 万行 开始规划水平拆分
某些分片数据是其他的 5 倍 分片键数据倾斜→换分片算法或分片键
跨分片 JOIN 卡死 拆成多次查 + 应用层组装
迁移后旧数据没有被读到 灰度比例验证 → 缓存 100% 旧数据+对比

# 9.4 一句话拎走

分库分表不是银弹——它把"垂直能力"的瓶颈变成了"选对分片键+扛住迁移复杂性"的能力。决策树:能不分→只拆分→必须水平→双写迁移→灰度切换。


# 10.第 10 篇·NoSQL 设计精要

# 10.1 速通卡

维度 内容
遇到场景 双11首页秒杀商品列表 QPS 飙到 5 万,MySQL 从库 CPU 100%——每秒 2 万次执行同一条 SELECT,读写分离没解决重复计算
暴露问题 2 万次查询从库仍执行了 2 万次磁盘 IO——缓存层缺失让数据库反复算同样的结果
核心知识 CAP 定理 → Redis 五大数据结构(底层编码自动切换)→ 持久化(RDB/AOF/混合)→ 淘汰策略 8 种 → 高可用(Sentinel/Cluster 16384 槽)→ 多级缓存架构
架构主线 NoSQL 不是替代 MySQL——SQL 负责强一致+复杂查询,NoSQL 负责高性能+灵活模型,两者互补
加深理解 Redis 单线程为什么快?——所有数据在内存(纳秒级)+ 单线程无锁竞争 + epoll 多路复用(非阻塞)——快的本质是不等磁盘、不等锁

# 10.2 展开:Redis 的五个关键设计

底层编码自动切换:小数据用紧凑结构(ziplist/intset/listpack),大数据切换到复杂结构(skiplist/hashtable/quicklist)。比如 ZSET 在元素 <128 且每个元素 <64 字节时用 ziplist,否则转 skiplist+hashtable。这就是"小数据零内存浪费,大数据高性能查询"的自适应设计。

RDB vs AOF:RDB(快照,全量,适合备份恢复)vs AOF(命令日志,增量,丢失少但文件大恢复慢)。混合持久化(Redis 4.0+):RDB 存基准快照 + AOF 存增量命令,兼具恢复速度和数据完整性。

8 种淘汰策略速记:noeviction(满了直接报错)→ allkeys-lru(最常用,全局淘汰最少使用)→ volatile-lru(只淘汰设了 TTL 的)→ allkeys-lfu(全局淘汰最不频繁使用)→ volatile-lfu → allkeys-random → volatile-random → volatile-ttl(淘汰即将过期的)。生产环境首选 allkeys-lru。

COW(Copy-On-Write)持久化原理:fork() 创建子进程做 RDB,父子共享相同的物理内存页(页表复制,不复制数据)。父进程修改某页时,OS 才为该页创建副本——fork 瞬间快,但修改量大时 COW 内存翻倍是 fork 崩溃的主因。

缓存三大问题及解法:

问题 现象 解决方案
缓存穿透 查不存在的数据,缓存没命中,每次都打 DB 布隆过滤器 + 缓存空值
缓存击穿 某个热点 Key 过期瞬间大量请求打 DB 互斥锁 + 逻辑过期(不等重建,异步刷新)
缓存雪崩 大量 Key 同时过期,DB 瞬间被打垮 过期时间加随机值 + 多级缓存 + 限流熔断

# 10.3 诊断信号灯

信号 行动
RDB fork 时内存翻倍 OOM 预留 maxmemory 的 50% 空闲内存
AOF 文件比 RDB 大 3 倍+ 开启 AOF 重写(BGREWRITEAOF)
缓存大量 miss 且 DB 没挂 检查 key 是否被误 expire→TTL key
主从断连重连后全量同步 调整 repl-backlog-size 避免全量同步

# 10.4 一句话拎走

Redis 快不是因为它是内存数据库,而是因为单线程 epoll + 精心设计的数据结构让每次操作都在纳秒级完成。缓存设计四原则:越近越好、越热越缓存、设好 TTL、兜底永不忘。


# 11.全系列·贯通三句话

# 11.1 数据走多远决定性能——I/O 是数据库的阿喀琉斯之踵

内存访问(100ns) → Buffer Pool命中(不用磁盘) → Change Buffer 写加速
磁盘顺序读(0.1ms) → Redo顺序写 → Binlog顺序写
磁盘随机读(5-10ms) → 回表 → 全表扫描 → 索引失效

所有数据库优化的底层逻辑:让磁盘 I/O 尽可能少、尽可能顺序。

# 11.2 有序是索引的一切——B+Tree 的设计哲学

问题 B+Tree 的答案
磁盘 I/O 太慢 多叉→3 层装下 80 亿行,3 次 I/O 完成查询
范围查询要扫多条记录 叶子节点双向链表→一次定位、顺序扫描
数据变动需要快速更新 页分裂/页合并→局部调整,不重构整棵树
回表太慢 覆盖索引→索引叶子直接包含查询列,零回表

理解 B+Tree 的三个关键数字:16KB 页大小、3 层高度 = 80 亿行、一次磁盘 I/O = 10ms。

# 11.3 日志是数据库的生命线——WAL 统一了性能与可靠性

日志 写什么 写入方式 解决了什么问题
Redo Log 物理页修改 顺序、循环写 崩溃后恢复已提交事务的数据
Undo Log 数据旧版本 追加写 事务回滚 + MVCC 多版本快照
Binlog 逻辑 SQL/行变更 顺序、追加写 主从复制 + 数据恢复

三者的协作:一次 UPDATE 触发 Redo(崩溃恢复) + Undo(快照读) + Binlog(主从同步),两阶段提交保证三者一致。


# 12.全系列数据一页纸

篇 核心数据 含义
01 6 组件 + 5 层 MySQL 分层架构(Server×5 + 引擎×1)
02 3 层 80 亿 B+Tree 高度 3 可装 80 亿行;覆盖索引快回表 500 倍
03 4 种问题 + 4 级 并发问题矩阵 × 隔离级别选择
04 3 种行锁 + 4 维矩阵 Record/Gap/Next-Key × IS/IX/S/X 兼容性
05 16KB + 3 链表 页大小 + Free/LRU/Flush 三链表协作
06 type 7 级 + Extra 6 种 EXPLAIN 字段优先级(type > Extra > rows)
07 3 种日志 + 2 阶段 redo/binlog/undo × 两阶段提交
08 3 种复制 + GTID 异步/半同步/全同步 × 自动定位
09 2^32 个 ID 雪花算法 64bit=41 时间+10 机器+12 序列
10 8 种策略 + 3 大问题 淘汰策略 × 缓存穿透/击穿/雪崩

# 13.全系列症状诊断表

对照症状,直接跳到对应篇章。

你看到的症状 第一反应读这篇 第二篇
SQL 变慢,EXPLAIN type=ALL §06 查询优化 §02 索引原理
ORDER BY 慢,Extra=filesort §06 EXPLAIN Extra §02 联合索引最左前缀
库存超卖、对账不平 §03 事务隔离 §04 锁的实现
数据库死锁日志 §04 死锁检测 §03 隔离级别改为 RC
BP 命中率 <95%,P99 飙升 §05 Buffer Pool 调优 §01 整体架构
主从延迟,读从库拿到旧数据 §08 复制延迟 §07 日志系统
表超过 2000 万行,COUNT 要几十秒 §09 分库分表 §02 索引覆盖
慢查询日志爆炸 §06 pt-query-digest §02 索引失效
崩溃恢复后数据丢了 §07 Redo/Binlog §05 Double Write
缓存加了 QPS 没降 §10 多级缓存架构 §05 Buffer Pool 替代缓存层
大事务执行一半被 KILL §03 长事务 §07 Undo 膨胀
写入 TPS 低 §05 Change Buffer §02 唯一索引限制
UPDATE 几行数据特别慢 §04 索引与加锁 §02 二级索引加锁规则

# 14.学习路径建议

# 14.1 半天速通

读 §0 导航 → 每篇只看 速通卡 + 一句话拎走(每篇 2 分钟,10 篇共 20 分钟)。遇到具体场景再展开。

# 14.2 系统建立数据库直觉(推荐)

§01 整体架构(SQL 生命周期) → §02 索引原理(B+Tree+覆盖)
→ §06 查询优化(EXPLAIN 六步法)
→ §03 事务隔离(MVCC+四种级别) → §04 锁的实现(三种行锁+死锁)
→ §05 存储引擎(Buffer Pool+InnoDB 架构)
→ §07 日志系统(三种日志+崩溃恢复)
→ §08 主从复制(并行复制+延迟处理)
→ §09 分库分表(水平拆分+双写迁移)
→ §10 NoSQL(Redis+多级缓存)

# 14.3 排查问题时

看 §13 诊断表 定位篇章 → 看速通卡确认方向 → 看信号灯验证假设 → 看展开加深理解。

# 14.4 自测清单

读完 10 篇后闭卷回答:

  1. MySQL 一条 SELECT 经过了哪些组件?每个组件的核心职责是什么?
  2. B+Tree 为什么用多叉而不是二叉树?3 层能装多少行数据?
  3. 联合索引的最左前缀原则是什么?范围查询为什么让后续列失效?
  4. MVCC 的 Read View 怎么判断一行数据是否可见?RC 和 RR 的 Read View 创建时机有何不同?
  5. Record Lock / Gap Lock / Next-Key Lock 三者各自锁什么?无索引 WHERE 条件加什么锁?
  6. Buffer Pool 的三个链表分别管什么?冷热分离 LRU 的分界线在哪?
  7. Redo Log / Undo Log / Binlog 各自解决什么问题?两阶段提交确保了什么?
  8. 主从延迟的四个来源是什么?读写分离怎么应对延迟?
  9. 分片键选择的三个铁律是什么?双写迁移的四个阶段是什么?
  10. 缓存的三大问题(穿透/击穿/雪崩)分别怎么解决?
上次更新: 2026/07/12, 16:35:53
10.NoSQL设计精要

← 10.NoSQL设计精要

最近更新
01
11.操作系统原理总结
07-10
02
资深程序员软能力
07-10
03
做事闭环执行能力
07-10
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 鄂ICP备2024073355号-1 | 鄂ICP备2024073355号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式