查看: 6|回复: 0

MySQL 底层拆解:为什么付款完成后数据库被 kill‑9 杀掉,重启之后数据还在?

[复制链接]

1972

主题

5

回帖

6011

积分

超级版主

积分
6011
发表于 17 小时前 | 显示全部楼层 |阅读模式

前言

后端开发经常会遇到一个很反直觉的现象:一笔订单付款完成,业务返回 “付款成功”,此时直接用kill‑9强制杀掉 MySQL 进程。去磁盘上直接看数据文件,这笔新增订单记录看不到;但重启 MySQL 之后,刚刚那笔付款记录又完整回来了。

很多人会疑惑:明明磁盘数据文件没有写入,为什么宕机重启数据不会丢?本文结合 MySQL 8.4,从 SQL 执行流程、B + 树索引、缓冲池、redo log、undo log 三大日志讲清背后原理,同时结合实测案例,搞懂索引失效、回表、WAL 预写日志这些高频面试知识点MySQL。

概念释义 kill‑9:操作系统强制终止进程,不给数据库执行优雅关闭、刷脏页的机会。 Buffer Pool 缓冲池:InnoDB 内存缓冲区,缓存数据页与索引页,减少磁盘 IO。 脏页:内存中被修改过,但还没有同步写入磁盘数据文件的数据页。 WAL 预写日志(Write‑Ahead Logging):修改数据前,先把变更记录写入 redo log 持久化磁盘,再异步刷新数据页,保证宕机可恢复。 redo log 重做日志:记录数据修改动作,用于崩溃恢复,保证事务持久性。 undo log 回滚日志:记录修改前数据快照,用于事务回滚、MVCC 多版本控制。 binlog 二进制日志:Server 层日志,记录行变更,用于主从复制、时间点数据恢复。 聚簇索引:InnoDB 主键索引,B + 树叶子节点存放完整行数据;二级索引叶子节点只保存主键,需要回表读取完整行。


一、MySQL 整体架构:Server 层 + InnoDB 存储引擎层

MySQL 整体分为两层:

  1. Server 层:连接器、解析器、优化器、执行器。负责连接管理、SQL 语法解析、生成执行计划、调用存储引擎接口。MySQL8.0 已经彻底移除旧版查询缓存。
  2. 存储引擎层(InnoDB):真正负责数据读写、事务、日志、索引。InnoDB 是 MySQL5.5 之后的默认存储引擎腾讯云。

一条 SQL 完整执行链路:

  1. 连接器:建立 TCP 连接,校验账号密码权限;后端服务一般使用连接池复用连接,避免频繁创建销毁线程。
  2. 解析器:词法、语法解析,校验表名列名、权限,生成抽象语法树。
  3. 优化器:评估多条执行路径成本,选择索引,生成最优执行计划。
  4. 执行器:调用 InnoDB 引擎接口,读取、过滤数据。
  5. InnoDB 层:访问 Buffer Pool 缓冲池;命中内存直接读取,未命中则从磁盘加载数据页;执行读写逻辑,处理 undo、redo 日志。

MySQL 每一条新连接会分配独立工作线程,不是进程,线程开销更小。

二、B + 树索引、回表与索引失效实战案例

InnoDB 数据以16KB 为一页单位存储,所有表都是 B + 树结构。主键是聚簇索引,叶子节点存放完整行;普通二级索引叶子只存主键值,拿到主键后,再到聚簇索引读取完整行,这个过程叫做回表CSDN博...。

以一千万行订单表实测:

  • 全表扫描:要读取约 55500 个数据页,耗时 4 秒。
  • 走 user_id 二级索引:索引树 3 层,先从二级索引拿到订单主键,再回表读取完整记录,一共只访问 16 个页,耗时仅 3 毫秒。

索引不是没有代价:建立索引消耗磁盘空间,写入更新数据,所有相关索引都需要同步维护,会增加写开销。

一个经典索引失效坑

两张查询逻辑返回结果完全一致,性能天差地别:

-- ✅可以走create_time索引,65ms
select * from orders where create_time >= '2026‑03‑05 00:00:00' and create_time < '2026‑03‑06 00:00:00';

-- ❌索引列使用DATE()函数,索引失效,触发全表扫描,耗时3.7秒
select * from orders where DATE(create_time) = '2026‑03‑05';

原因:B + 树索引保存的是字段原始值,在索引列上做函数运算,数据库无法利用索引有序性,只能逐行计算过滤,导致索引失效。MySQL8.0 支持函数索引,但业务上优先改写 where 条件更稳妥。

Buffer Pool 缓冲池带来的速度差异

同样一条索引查询,第一次执行 3ms,第二次只需要 0.2ms。 原因:第一次需要把索引、数据页从磁盘加载进 Buffer Pool;第二次全部命中内存,不需要磁盘 IO。生产环境数据库服务器,通常会把物理内存的 60%‑80% 分配给 Buffer Pool。

三、核心问题:kill‑9 强杀 MySQL,付款记录为什么重启又回来了?

业务收到 “付款成功” 响应,立刻kill‑9强制杀死 MySQL 进程。此时内存里修改的数据页(脏页)来不及刷新到磁盘上的 ibd 数据文件,直接看磁盘文件看不到这条新订单。但是redo log 已经提前持久化到磁盘。

这就是 InnoDB 核心机制 WAL 预写日志(Write‑Ahead Logging):

事务提交的顺序:先写 redo log 落盘,脏页数据页后续后台异步刷盘,不需要等待数据页写入磁盘。

完整流程:

  1. 执行 update /insert 付款记录;
  2. 写入 undo log,保存修改前数据,用于事务回滚;
  3. 修改 Buffer Pool 内存里的数据页,变成脏页;
  4. 把本次变更操作记录写入 redo log buffer;
  5. 事务提交,redo log 持久化 fsync 到磁盘,此时给业务返回 “付款成功”;
  6. 脏页不会立刻写磁盘,后台 checkpoint 线程慢慢把脏页刷新到 ibd 数据文件。

当kill‑9宕机:内存全部丢失,但 redo log 文件已经保存在磁盘。重启 MySQL,InnoDB 执行崩溃恢复 Crash Recovery,扫描 redo log,把已经提交、但脏页没有刷盘的变更重新重演一遍;未提交的事务依靠 undo log 回滚。于是那条付款记录就回来了MySQL。

关键点:

  1. 业务收到成功,代表 redo log 已经落盘,数据具备持久性;脏页刷盘是后台异步动作。
  2. undo log 负责回滚未提交事务;redo log 负责崩溃之后重做已提交的变更;binlog 是 Server 层日志,用于主从复制、时间点恢复,三者各司其职。

四、三大日志简单区分

表格

日志归属核心作用
redo log 重做日志InnoDB 存储引擎WAL 崩溃恢复,保证已提交事务宕机不丢失
undo log 回滚日志InnoDB 存储引擎事务回滚、MVCC 多版本并发控制
binlog 二进制日志MySQL Server 层主从同步、基于时间点数据恢复

五、常见认知误区澄清

  1. 误区:事务提交,数据就立刻写入磁盘 ibd 文件

真相:提交保证 redo log 落盘;脏页是后台异步刷入数据文件,两者不同步。kill‑9 杀掉进程,ibd 文件可以看不到新数据,重启靠 redo log 恢复。

  1. 误区:建立索引,where 条件就一定能用上索引

真相:索引列做函数运算、隐式类型转换、like 前缀通配符,都会造成索引失效,触发全表扫描。

  1. 误区:Buffer Pool 越大,磁盘读写就完全消失

真相:冷库、大量新数据,依然会产生磁盘 IO;缓冲池只是缓存热点页。

  1. 误区:binlog、redo log 是同一个东西

真相:binlog 属于 Server 层记录逻辑行变更;redo log 是 InnoDB 物理层面页变更日志,用途完全不一样。

  1. 误区:回表就是 bug,应该尽量全部避免

真相:回表是 InnoDB 二级索引正常机制;可以通过覆盖索引消除回表,但覆盖索引会增加索引存储开销。

结语

很多后端开发者天天写 SQL,却不了解 MySQL 内部发生了什么。 一条 SQL 从客户端发出,经历连接器、解析器、优化器、执行器,再进入 InnoDB,依靠 B + 树索引、Buffer Pool 缓冲池提升查询性能;依靠 WAL 预写日志机制,redo log 保证宕机数据不丢,undo log 保证可以回滚,binlog 负责复制与数据恢复。

kill‑9杀掉 MySQL,磁盘数据文件看不到刚刚提交的数据,重启之后记录重现,正是 WAL 预写日志的典型体现。同时也要注意索引失效、回表等性能坑,写 SQL 的时候,多使用explain查看执行计划,确认索引是否真正生效。

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|天翼网

相关侵权、举报、投诉及建议等,请发 E-mail:2026@typc.net

Powered by Discuz! X5.0 © 2001-2026 Discuz! Team.|晋ICP备2026008270号-1|晋公网安备14010602111293号

QQ客服返回顶部