跳到主要内容

mysql日志详解

mysql日志详解,error 、slow log、binlog、redo log、undo log

所属层级日志名称主要用途备注
Server 层Binlog (归档日志)数据备份、主从复制最重要,所有引擎共用
Server 层Slow Query Log (慢查询日志)性能调优记录执行慢的 SQL
Server 层Error Log (错误日志)故障排查记录启动和运行异常
Server 层General Query Log (通用日志)审计、排错记录所有收到的 SQL
Server 层Relay Log (中继日志)主从复制仅在从库(Slave)中存在
存储引擎层 (InnoDB)Redo Log (重做日志)崩溃恢复 (Crash-safe)保证事务的持久性
存储引擎层 (InnoDB)Undo Log (回滚日志)事务回滚、MVCC保证事务的原子性

一、InnoDB 存储引擎在收到执行器的请求后,是如何通过 Buffer Pool 和 Redo Log 来保证高性能和数据不丢失的吗?- Redo Log

在传统的思维里,改数据就是改磁盘。但在 InnoDB 里,为了平衡速度(高性能)和安全(数据不丢失),它设计了一套名为 WAL(Write-Ahead Logging,预写日志) 的机制。

1、Buffer Pool:高性能的“内存工作台”

磁盘太慢了,如果每次读写都要转动硬盘,数据库性能会卡死。InnoDB 在内存中开辟了一块巨大的空间,叫 Buffer Pool

  • 读取逻辑:执行器要数据,InnoDB 先看 Buffer Pool 有没有。有就直接给;没有就从磁盘加载一个 16KB 的数据页 存进去。
  • 写入逻辑:这是关键!当你要修改数据时,InnoDB 只修改内存里的 Buffer Pool
    • 被修改过的内存页叫 “脏页”(Dirty Page)
    • 此时,磁盘上的数据还没变,但操作已经算“成功”了。因为在内存里操作,速度极快。

2. Redo Log:数据不丢失的“黑匣子”

只改内存太危险了——万一断电,内存里的“脏页”全没了怎么办?为了解决这个问题,Redo Log(重做日志) 登场了。 当你在 Buffer Pool 修改数据时,InnoDB 会顺手记录一笔:“在某个数据页的某个偏移量处,把值从 A 改成了 B”

为什么有了 Redo Log 就不怕断电了?

  1. WAL 机制:InnoDB 要求在事务提交前,必须先把这笔修改记录(Redo Log)持久化到磁盘上。
  2. 顺序 IO vs 随机 IO
    • 修改数据页是“随机 IO”(数据可能在磁盘任何角落),非常慢。
    • 写 Redo Log 是“顺序追加 IO”(像在笔记本后面续写),速度极快。
  3. Crash Recovery(崩溃恢复):如果服务器突然宕机,内存丢了没关系。重启后,InnoDB 会读取磁盘上的 Redo Log,把还没来得及同步到磁盘的数据重新“重演”一遍,数据就找回来了。

3. 它们是如何协同工作的?(一个事务的旅程)

当执行器发来一条 UPDATE 请求:

  1. 加载数据:将数据页加载到 Buffer Pool(如果不在内存)。
  2. 修改内存:直接在 Buffer Pool 里改掉那行数据。
  3. 写 Redo Log 缓存:将改动记录写入 redo log buffer
  4. 事务提交(Commit)
    • redo log buffer 里的内容刷到磁盘上的 Redo Log 文件
    • 只要 Redo Log 落盘成功,InnoDB 就会告诉执行器:“事务提交成功!”。
  5. 异步刷脏:在后台,InnoDB 有专门的线程会在系统空闲时,悄悄把 Buffer Pool 里的脏页同步回真正的 数据文件(.ibd)。这个过程叫 Checkpoint(检查点)

4. 总结对比

角色存储位置核心作用特点
Buffer Pool内存提高读写速度断电即失,通过“脏页”缓存减少磁盘 IO
Redo Log磁盘保证持久性 (D)物理日志,顺序写入,用于掉电恢复

冷知识:Redo Log 是固定大小的(比如 4 个 1GB 文件),它是循环写的。如果 Redo Log 写满了,MySQL 就会被迫停下手中的活,拼命把内存里的脏页往磁盘刷,腾出空间写日志。这就是为什么有时候 MySQL 会突然“抖动”一下。

二、如果事务执行了一半想回滚(Rollback),是依靠哪个日志实现的? Undo Log

如果事务执行到一半想要回滚(Rollback),或者是数据库崩溃时需要撤销那些尚未提交的事务,依靠的是 Undo Log(回滚日志)。 如果说 Redo Log 是为了“勇往直前”(保证提交的数据不丢失),那么 Undo Log 就是为了“留好后路”(保证未提交的数据能擦除)。

1. Undo Log 是如何工作的?

与 Redo Log 记录物理层面的“页偏移量修改”不同,Undo Log 记录的是逻辑日志。 你可以把它理解为一份**“反向操作清单”**:

  • 当你 Insert 一条记录时,Undo Log 会记录一条对应的 Delete
  • 当你 Delete 一条记录时,Undo Log 会记录一条对应的 Insert
  • 当你 Update 一条记录时,Undo Log 会记录一份修改前的数据(旧值)

当事务需要回滚时,InnoDB 只需要从 Undo Log 里找到对应的记录,执行这些“反向操作”,就能把数据库恢复到事务开始前的样子。

2. Undo Log 的双重身份

Undo Log 不仅仅是为了回滚,它在 InnoDB 中还扮演着另一个极其重要的角色:MVCC(多版本并发控制)

  • 实现原子性(Atomicity):这是回滚的本职工作。
  • 实现隔离性(Isolation):当一个事务在修改数据时,另一个事务想读取这条数据,为了不让读取操作被阻塞(实现非阻塞读),InnoDB 会让读取操作去读 Undo Log 里的旧版本数据

这就是为什么在“可重复读”隔离级别下,你总是能看到一致的快照数据,即使别人正在修改它。

3. 总结:日志兄弟的协作

特性Redo Log (重做日志)Undo Log (回滚日志)
保证的特性持久性 (D)原子性 (A) & 隔离性 (I)
记录内容物理日志(页的改动)逻辑日志(反向 SQL 或旧值)
落盘时机事务提交时必须落盘修改数据前先写 Undo Log
主要用途崩溃恢复(宕机后重做)事务回滚 & MVCC 多版本读

一个有趣的细节 Undo Log 本身也需要持久化。 这意味着,Undo Log 的产生也会伴随着 Redo Log 的产生(因为 Undo Log 也是一种数据,对它的修改也需要记录在 Redo Log 中)。这听起来有点绕,但正是这种嵌套保证了即便在回滚过程中宕机,数据库依然是安全的。

三、server层的binlog和 Redo Log 的两阶段提交是如何保证数据一致性的?

如果把 Redo Log 比作 InnoDB 引擎的“私人账本”,把 Binlog 比作整个 Server 层的“公共档案”,那么 “两阶段提交”(2PC, Two-Phase Commit) 就是为了确保这两份记录在任何时候都必须一致

1. 为什么要搞得这么复杂?(一致性危机)

想象一下,如果没有两阶段提交,而是简单地先后写两个日志:

  • 先写 Redo Log,再写 Binlog:Redo 写完后系统崩了。重启后,主库通过 Redo 恢复了数据,但 Binlog 没记下这笔。结果:从库同步时会少这一笔数据,主从不一致。
  • 先写 Binlog,再写 Redo Log:Binlog 写完后系统崩了。重启后,主库因为没有 Redo 记录而没能恢复数据,但从库已经通过 Binlog 执行了操作。结果:主库没数据,从库多了一笔数据,主从还是不一致。

为了解决这个“谁先谁后都不行”的问题,两阶段提交应运而生。

2. 两阶段提交的三个步骤

当执行器收到提交(Commit)指令后,流程如下:

  1. Prepare 阶段 (准备阶段)
    • InnoDB 将该事务的改动写入 Redo Log
    • 将该 Redo Log 的状态标记为 PREPARE
    • 此时,事务还没有真正提交。
  2. Binlog 写入 (正式写档)
    • Server 层将该事务的操作记录到 Binlog 中。
    • 将 Binlog 刷入磁盘(sync_binlog=1)。
  3. Commit 阶段 (提交阶段)
    • 执行器调用存储引擎的提交接口。
    • InnoDB 将刚才的 Redo Log 状态从 PREPARE 改为 COMMIT
    • 至此,整个事务才算大功告成。

3. 崩溃恢复:MySQL 如何根据两阶段“断案”?

如果在执行过程中突然断电,MySQL 重启后会扫描 Redo Log,根据其状态进行判断

  • 场景 A:Redo Log 状态是 COMMIT
    • 判断:说明 Binlog 肯定也写成功了。
    • 处理:直接恢复数据。
  • 场景 B:Redo Log 状态是 PREPARE,但 Binlog 没记录
    • 判断:说明在写 Binlog 之前系统就崩了。
    • 处理:回滚事务。
  • 场景 C:Redo Log 状态是 PREPARE,且 Binlog 中有对应记录
    • 判断:说明 Binlog 已经写完,但在最后一步改 COMMIT 状态时崩了。
    • 处理:既然 Binlog 已经发出去了(从库可能已经同步),为了保证一致性,主库也要通过 Redo Log 继续提交该事务。

4. 它们是怎么“认出”彼此的? (XID)

你可能会问:Redo Log 和 Binlog 是两个独立的文件,它们怎么知道哪条对应哪条?

答案是 XID(Transaction ID)。 在 Prepare 阶段,MySQL 会生成一个全局唯一的事务 ID(XID)。这个 XID 会同时写入 Redo Log 和 Binlog。崩溃恢复时,MySQL 只要拿着 Redo Log 里的 XID 去 Binlog 找,能找到就提交,找不到就回滚。

5. 总结

阶段日志状态核心动作容错逻辑
PrepareRedo(PREPARE)记录物理改动崩了就回滚
Write BinlogBinlog 落盘记录逻辑 SQL崩了看 Binlog 是否完整
CommitRedo(COMMIT)标记事务完成此时崩了也不怕,数据已安全

“两阶段提交”实际上是把 Binlog 的写入作为了事务最终生效的“决策点”。


四、mysql 如何做到可重复读的

MySQL 的 InnoDB 存储引擎主要通过 MVCC(多版本并发控制)Read View(读视图) 机制来实现“可重复读”(Repeatable Read)隔离级别。

简单来说,它的核心逻辑不是“锁定”数据不让别人改,而是给每一行数据维护多个版本,让不同的事务看到属于它们自己的“快照”。

1. 核心支柱:Undo Log(回滚日志)

当你修改一行数据时,MySQL 不会直接覆盖旧值,而是会将旧版本的数据记录在 Undo Log 中。

  • 每行记录都有两个隐藏列:
    • DB_TRX_ID:最后一次修改该记录的事务 ID
    • DB_ROLL_PTR回滚指针,指向该记录在上一个版本(Undo Log 中)的位置。
  • 这样就形成了一个版本链,通过这个链条可以追溯到数据以前的样子。

2. 关键工具:Read View(读视图)

当一个事务开始执行 SELECT 时,系统会为该事务创建一个 Read View。这个视图决定了当前事务能看到哪些版本。Read View 包含四个核心字段:

  1. m_ids: 当前系统正活跃(未提交)的事务 ID 列表。
  2. min_trx_id: m_ids 中的最小值。
  3. max_trx_id: 下一个将被分配的事务 ID(即当前最大 ID + 1)。
  4. creator_trx_id: 生成这个 Read View 的事务 ID。

3. 可重复读的实现逻辑

在“可重复读”级别下事务只在第一次执行查询时生成一个 Read View,之后的查询都复用这同一个视图。

查询时,MySQL 会根据版本链中的事务 ID (trx_id) 进行比对:

  • 情况 Atrx_id < min_trx_id。说明这个版本在 Read View 创建前就已经提交了,可见
  • 情况 Btrx_id >= max_trx_id。说明这个版本是在 Read View 创建后才开启的事务生成的,不可见
  • 情况 Cmin_trx_id <= trx_id < max_trx_id
    • 如果 trx_idm_ids 列表中,说明版本对应的事务还没提交,不可见(除非是自己改的)。
    • 如果不在列表中,说明事务已提交,可见

这就是关键: 因为 Read View 始终没变,即使其他事务后来提交了新数据,当前事务依然会通过版本链和旧的 Read View 找到那个“老版本”的数据。

4. 解决幻读(Next-Key Locks)

MVCC 解决了“快照读”(普通查询)的重复读问题。但对于“当前读”(如 UPDATESELECT ... FOR UPDATE),MySQL 还会配合 Next-Key Locks

  • 记录锁(Record Lock):锁定索引行。
  • 间隙锁(Gap Lock):锁定索引之间的间隙,防止其他事务插入新数据。

通过这两者的结合,MySQL 在“可重复读”级别下也基本解决了幻读问题。