事务四大特性原理以及log原理
前言
Github:https://github.com/HealerJean
1. 重做日志(redo log)`
2. 回滚日志(undo log)`
3. 二进制日志(binlog)`
4. 错误日志(errorlog)`
5. 慢查询日志(slow query log)`
6. 一般查询日志(general log)
binlog文件记录了mysql所有的增、删、改语句,只要有了这个 binlog,我们就拥有了mysql的完整备份了。
一、三大日志
日志划分:
redo log是物理日志,undo log和binlog是逻辑日志⬤ 逻辑日志:可以简单理解为记录的就是
sql语句。⬤ 物理日志:因为
mysql数据最终是保存在数据页中的,物理日志记录的就是数据页变更。协同机制:事务提交时
redolog和binlog的写入通过prepare/commit状态保证一致性

1、redo log(重做日志)
物理日志:记录的是”在某个数据页上做了什么修改”
核心作用:保证事务的持久性(
ACID中的D),崩溃恢复时重放未刷盘的脏页
1)基本概念与组成
redo log包括两部分:一个是内存中的日志缓冲 (redo log buffer),另一个是磁盘上的日志文件(redo log file)。
mysql每执行一条DML语句,先将记录写入redo log buffer,后续某个时间点再一次性将多个操作记录写到redo log file。这种先写日志,再写磁盘的技术就是MySQL里经常说到的WAL(Write-Ahead Logging) 技术。
-
内存部分:
redo log buffer,日志缓存,事务执行过程中,修改操作会先写入内存中的redo log buffer,每个线程共享同一缓冲区(不同于binlog的线程独立缓存) -
磁盘部分:
redo log file:日志文件,默认由ib_logfile0和ib_logfile1组成,循环写入固定大小的文件,写满后触发检查点(checkpoint)清理可覆盖的日志 -
写入流程:通过
innodb_flush_log_at_trx_commit参数控制刷盘策略redo log buffer → write → OS page cache → fsync → disk
2)刷盘策略与未提交事务的持久化
innodb_flush_log_at_trx_commit参数
| 设置值 | 提交时行为 | 刷盘频率 | 崩溃丢失风险 | 场景 | 说明 |
|---|---|---|---|---|---|
0 |
无任何操作 | 每秒 write + fsync |
MySQL 崩溃 or 系统崩溃 → 最多丢 1 秒 |
极限性能测试 / 临时库 / 日志型应用 | 不推荐用于核心业务 |
1 |
write + fsync | 每次提交 | 几乎不丢(除非磁盘故障) | 通用生产环境(尤其涉及交易、用户数据) | 安全第一,默认值 |
2 |
write(到 OS buffer) | 每秒 fsync |
系统崩溃/断电 → 最多丢 1 秒;仅 MySQL 崩溃 → 不丢 |
高吞吐写入、可容忍秒级数据丢失 | 性能较好,风险可控(需配合 UPS、高可用架构) |
-
0(延迟写):事务提交时不会将redologbuffer中日志写入到osbuffer,而是每秒写入osbuffer并调用fsync()写入到redologfile中。也就是说设置为0时是(大约)每秒刷新写入到磁盘中的,当系统崩溃,会丢失 1 秒钟的数据。 -
1(实时写,实时刷):事务每次提交都会将redo log buffer中的日志写入os buffer并调用fsync()刷到redo log file中。这种方式即使系统崩溃也不会丢失任何数据,但是因为每次提交都写入磁盘,IO的性能较差。 -
2(实时写,延迟刷):每次提交都将redo log buffer中的日志仅写入到os buffer,然后是每秒调用fsync()将os buffer中的日志写入到redo log file。
3)生命周期
- 事务开始后:就开始产生
redo log日志了,在事务执行的过程中,redo log开始逐步落盘 - 脏页刷盘后:当对应事务的脏页写入到磁盘之后,
redo log的使命就完成了,它所占用的空间也就可以被覆盖了。 。 - 循环写入:
redo log文件组大小固定(如 2×1GB),写满后从头覆盖,通过write pos和checkpoint指针协调
4)常见问题
a、0 和 2 区别是啥?
- 参数设为 0
- 行为:系统每秒批量写入日志到磁盘,事务提交时无磁盘
I/O操作。 - 风险:若
MySQL进程突然崩溃,这时redologbuffer就丢了,最后1秒的日志分析结果丢失 - 优势:吞吐量提升 30%+(实测
RAID环境下性能差异约 10%)
- 行为:系统每秒批量写入日志到磁盘,事务提交时无磁盘
- 参数设为
2-
行为:订单支付成功后,日志立即写入
page cache,但磁盘刷盘由操作系统控制。 -
风险:服务器断电时,若
page cache,未刷盘,最近 1 秒的订单状态更新丢失 -
优势:相比设为 1,并发写入性能提升
20%,且比设为0更安全(仅断电有风险)
-
b、OS Buffer 作用是啥?
在计算机操作系统中,用户空间 (user space) 下的缓冲区数据一般情况下是无法直接写入磁盘的,中间必须经过先写入操作系统缓冲区 (OS Buffer)。因此,redo log buffer 写入 redo log file 实际上是先写入OS Buffer,然后再通过系统调用 fsync()将其刷到redo log file中
c、为什么会出现 redo log?
- 解决「随机
I/O性能瓶颈」:- 问题背景:
InnoDB以数据页(16KB)为单位进行磁盘 I/O,但事务可能仅修改页内少量字节(如更新某行的一个字段)。若每次修改都刷整个页到磁盘,会产生大量无效I/O(如仅修改100B却需写入16KB) redolog的优化:仅记录物理修改(如“页号+偏移量+新值”),日志体积小且顺序写入磁盘,避免随机I/O。例如,一个事务修改10个不同页的数据,redo log只需顺序写入10条记录,而非随机写入 10 个分散的页
- 问题背景:
- 保证事务的持久性(
ACID中的D):记录事务提交后数据的物理修改(如“页号X的偏移量Y处值从A改为B”),用于崩溃恢复时重放已提交但未刷盘的修改,确保持久性(D)
d、未提交事务的 redo log 可能持久化的场景?
- 后台线程定时刷盘:若参数设为
0,每 1 秒触发一次write+fsync,可能将未提交事务的日志持久化到磁盘 - 并行事务提交触发:若参数设为
1,事务B提交时会连带事务A的未提交日志一起刷盘- 假设一个事务
A执行到一半,已经写了一些redo log到buffer中,这时候有另外一个线程的事务B提交,如果innodb_flush_log_at_trx_commit设置的是 1 ,那么按照这个参数的逻辑,事务B要把redo log buffer里的日志全部持久化到磁盘。这时候,就会带上事务A在redo log buffer里的日志一起持久化到磁盘。
- 假设一个事务
redo log buffer空间不足:当占用超过innodb_log_buffer_size(默认8MB)一半时,后台线程会主动write到pagecache(不调用fsync)
2、undo log(回滚日志)
逻辑日志,记录的是逆向
SQL(如INSERT对应DELETE)作用:
实现事务回滚(
ACID中的A)实现
MVCC的关键:存储事务版本链
1)核心功能
a、事务回滚(原子性保障)
为保证事务的原子性(要么全执行,要么全不执行),MySQL 在执行变更前会记录回滚日志(Undo Log),用于事务失败时回滚。
InnoDB 引擎使用 undo log(回滚日志)来保证原子性操作,你对数据库的每一条数据的改动(INSERT、DELETE、UPDATE)都会被记录到 undo log 中,比如以下这些操作,当事务执行失败或者调用了 rollback 方法时,就会触发回滚事件,利用 undo log 中记录将数据回滚到修改之前的样子。
-
你插入一条记录时,至少要把这条记录的主键值记下来,之后回滚的时候只需要把这个主键值对应的记录删掉就好了。
-
你删除了一条记录,至少要把这条记录中的内容都记下来,这样之后回滚时再把由这些内容组成的记录插入到表中就好了。
-
你修改了一条记录,至少要把修改这条记录前的旧值都记录下来,这样之后回滚时再把这条记录更新为旧值就好了。
b、多版本并发控制(MVCC,隔离性保障)
作用:为并发事务提供数据的历史版本,实现非锁定读(快照读):
-
可重复读(
REPEATABLEREAD):事务读取的是事务开始时的数据快照,通过UndoLog的版本链实现 -
读已提交(
READCOMMITTED):每次读取最新已提交版本,通过UndoLog避免脏读
2)工作原理
a、写入流程
-
事务开始:分配一个回滚段(
RollbackSegment) -
修改数据前:将原始数据备份到
UndoLog中,并更新DB_ROLL_PTR指向该记录 - 事务提交/回滚:
-
提交:
UndoLog放入链表,供后续Purge线程清理 - 回滚:根据
UndoLog恢复数据到修改前的状态
b、版本链与 MVCC
- 事务
A更新name='Tom'为name='Jerry',UndoLog记录旧值Tom并链接到版本链。 - 事务
B查询时,若事务A未提交,则通过版本链读取旧值Tom
3)常见问题
a、与 Redo Log 的关系
-
持久化依赖:
UndoLog的修改会记录到RedoLog,确保UndoLog本身的持久性 -
崩溃恢复:崩溃时,先通过
RedoLog恢复数据页和Undo页,再通过UndoLog回滚未提交事务
3、binlog(归档日志)
binlog用于记录数据库执行的写入性操作(不包括查询)信息,以二进制的形式保存在磁盘中。binlog是mysql的逻辑日志,并且由Server层进行记录,使用任何存储引擎的mysql数据库都会记录binlog日志。特点:
binlog是通过追加的方式进行写入的,可以通过max_binlog_size参数设置每 个binlog文件的大小,当文件大小达到给定值之后,会生成新的文件来保存日志。
1)核心作用
-
主从复制:主库(
Master)将Binlog发送给从库(Slave),从库重放日志实现数据同步,是 MySQL 高可用架构的基础 -
数据恢复:记录所有数据修改操作(
DDL/DML),结合全量备份可实现时间点恢复(PITR)。例如,通过mysqlbinlog工具重放日志到指定时间点 -
增量备份与审计:记录增量变更,减少全量备份频率;通过解析日志追踪数据变更历史
2)Binlog 的三种格式
binlog日志有三种格式,分别为STATMENT、ROW和MIXED。在
MySQL 5.7.7之前,默认的格式是STATEMENT,MySQL 5.7.7之后,默认值是ROW。日志格式通过binlog-format指定。
| 格式 | 记录内容 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
STATEMENT |
SQL 语句 |
日志量小,写入高效 | 函数/变量操作可能导致主从不一致 | 旧版本兼容性需求 |
ROW |
修改前后的值 | 数据绝对一致,支持复杂操作 | 日志体积大(如批量更新) | 生产环境首选(推荐) |
MIXED |
STATEMENT 或 ROW |
平衡性能与一致性 | 仍有少量不一致风险 | 过渡方案或混合负载 |
a、STATMENT
基于
SQL语句的复制 (statement-based replication, SBR), 每一条修改数据的sql都会记录到master的binlog中,slave在复制的时候,sql进程会解析成和原来在master端执行时的相同的sql再执行。
-
优点:在
statement模式下首先就是解决了row模式的缺点,不需要记录每一行数据的变化,从而减少了binlog的日志量,,节约了IO, 从而提高了性能; -
缺点:
- 在某些情况下会导致主从数据不一致,比如执行
sysdate()、slepp()等。 在statement模式下,由于它是记录的执行语句,所以,为了让这些语句在slave端也能正确执行,那么它还必须记录每条语句在执行的时候的一些相关信息,即上下文信息,以保证所有语句在slave端和在master端执行结果相同。 - 另外就是,由于
MySQL现在发展比较快,很多新功能不断的加入,使MySQL的复制遇到了不小的挑战,自然复制的时候涉及到越复杂的内容,bug也就越容易出现。在statement中,目前已经发现不少情况会造成MySQL的复制出现问题,主要是在修改数据的时候使用了某些特定的函数或者功能才会出现,比如:sleep()函数在有些版本中就不能被正确复制,在存储过程中使用了last_insert_id()函数,可能会使slave和master上得到不一致的id等等。由于row模式是基于每一行来记录变化的,所以不会出现类似的问题。
- 在某些情况下会导致主从数据不一致,比如执行
b、ROW
基于行的复制,不记录每条
sql语句的上下文信息,仅需记录哪条数据被修改了。然后在slave端再对相同的数据进行修改。row模式只记录要修改的数据,只有value,不会有sql多表关联的情况。
⬤ 优点:在 row 模式下,binlog 中可以不记录执行的 sql 语句的上下文相关的信息,仅仅只需要记录哪一条记录被修改了,修改成什么样了,所以 row 的日志内容会非常清楚的记录下每一行数据的修改细节,非常容易理,而且不会出现某些特定情况下的存储过程和 function,以及 trigger的调用和触发无法被正确复制问题。;
⬤ 缺点:会产生大量的日志,尤其是alter table的时候会让日志暴涨
c、MIXED
基于
STATMENT和ROW两种模式的混合复制(mixed-based replication, MBR),一般的复制使用STATEMENT模式保存binlog,对于STATEMENT模式无法复制的操作使用ROW模式保存binlog
MySQL会根据执行的每一条具体的SQL语句来区分对待记录的日志形式,也就是在statement和row之间选择一种。新版本中的statment还是和以前一样,仅仅记录执行的语句。而新版本的MySQL也对row模式做了优化,并不是所有的修改都会以row模式来记录,比如遇到表结构变更的时候就会以statement模式来记录,如果SQL语句确实就是update或者delete等修改数据的语句,那么还是会记录所有行的变更。
3)工作原理
- 写入流程:
- 事务提交时:操作记录为事件(
Event)写入内存缓存(BinlogCache) - 刷盘策略:根据
sync_binlog参数(推荐设为1)决定何时同步到磁盘 - 两阶段提交(
InnoDB):Prepare阶段:RedoLog标记为准备状态Commit阶段:Binlog刷盘后,RedoLog提交,确保崩溃恢复一致性
- 事务提交时:操作记录为事件(
- 文件结构
- 索引文件:
mysql-bin.index记录所有Binlog文件列表 - 日志文件:
mysql-bin.000001(按序号递增)
- 索引文件:
4)binlog 写入机制
其实,
binlog的写入逻辑比较简单:事务执行过程中,先把日志写到binlog cache,事务提交的时候,再把binlog cache写到binlog文件中。 一个事务的binlog是不能被拆开的,因此不论这个事务多大,也要确保一次性写入。这就涉及到了binlog cache的保存问题。
a、Binlog Cache 的内存管理
-
线程私有性:每个线程拥有独立的
binlog cache,由参数binlog_cache_size控制其内存大小(默认32KB)。若事务产生的日志超过该限制,超出的部分会暂存到磁盘临时文件,避免内存溢出 -
事务完整性:无论事务大小,
binlog cache必须完整保存事务的所有日志事件,确保提交时一次性写入binlog文件。例如,一个事务修改100万行数据,其日志可能远超binlog_cache_size,此时会触发磁盘暂存,但提交时仍会合并写
b、写入流程的两阶段:write 与 fsync
-
write阶段:将binlog cache中的日志写入文件系统的page cache(内存缓冲区),此过程由操作系统异步管理,速度极快且不直接占用磁盘I/O -
fsync阶段:调用fsync()强制将page cache中的数据刷入磁盘,此时才会真正占用磁盘 I/OPS。该操作由参数sync_binlog控制触发时机
| 参数值 | 行为 | 优点 | 风险 | 适用场景 |
|---|---|---|---|---|
sync_binlog=0 |
仅 write 到 page cache,依赖系统自动刷盘 |
性能最优 | 崩溃时可能丢失全部未刷盘的 binlog |
非关键日志业务或测试环境 |
sync_binlog=1默认) |
每次提交事务均执行 fsync |
数据最安全(最多丢1事务) | 频繁 I/O 导致性能下降(TPS 可能降低5倍) | 金融、支付等高一致性场景 |
sync_binlog=N |
每 N 次提交执行一次 fsync |
平衡性能与安全性 | 崩溃时丢失最近 N 个事务的日志 |
一般业务(推荐 N=100~1000) |
5)二阶段提交的核心流程
关键:
两阶段提交通过 原子性绑定
redo log和binlog,确保两者要么都成功,要么都失败若
binlog写入失败,事务会回滚,避免主从数据不一致
a、准备阶段(Prepare Phase)
InnoDB准备
事务的修改先写入 redo log,并标记为 PREPARE 状态(此时事务未提交,但已持久化到磁盘)
redo log 刷盘由参数 innodb_flush_log_at_trx_commit=1 控制(默认每次提交刷盘)
- 目的:确保事务的修改可恢复,即使后续崩溃也能通过
redo log重做或回滚
b、提交阶段(Commit Phase)
-
Binlog写入:MySQLServer层将事务的逻辑操作(如SQL语句或行变更)写入binlog文件,并调用fsync刷盘(由sync_binlog参数控制) -
InnoDB提交:binlog写入成功后,redo log状态从PREPARE更新为COMMIT,事务正式提交
6)常见问题
a、崩溃恢复与数据一致性
-
风险场景:若
sync_binlog=N且主机崩溃,最近N个已提交事务的binlog可能丢失,但redo log已持久化(若innodb_flush_log_at_trx_commit=1),导致主从数据不一致。此时MySQL会报错"binary log is shorter than expected",需人工介入从备份恢复 -
两阶段提交(
2PC)保障:binlog与redo log通过2PC机制协同-
Prepare阶段:redo log标记为PREPARE状态 -
Commit阶段:binlog持久化后,redo log标记为COMMIT。- 确保两者状态一致,崩溃后可根据
binlog完整性决定提交或回滚
- 确保两者状态一致,崩溃后可根据
-
b、为什么需要 redo log与 binlog 两者协同
Binlog的局限性:- 无
Crash-Safe能力:InnoDB就可以保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力称为crash-safe,Binlog仅在事务提交阶段(COMMIT)写入,若崩溃发生在提交前(如PREPARE阶段),Binlog中不会有该事务的记录,但此时InnoDB可能已修改内存数据页(通过redo log持久化) - 逻辑日志的恢复效率低:通过
binlog恢复需重放所有SQL,而redolog直接恢复物理数据页,速度更快
- 无
RedoLog的局限性:- 引擎特异性:仅
InnoDB支持redolog,其他引擎(如MyISAM)无法依赖它实现崩溃恢复 - 日志覆盖风险:
redolog空间固定,写满后需覆盖旧日志,若未及时刷盘则历史操作可能丢失
- 引擎特异性:仅
- 协同价值
- 互补性:
redolog保证 实时持久性,binlog提供 长期可追溯性,二者结合覆盖了从崩溃恢复到数据同步的全场景需求 - 两阶段提交(
2PC):通过prepare-commit状态同步,确保redolog和binlog逻辑一致,避免主从数据分歧
- 互补性:
c、Crash-Safe 的实现原理
-
扫描
redolog:找到所有PREPARE状态的事务(未完整提交) - 检查
binlog:- 若
binlog中存在对应事务的完整记录 → 提交事务(重做) - 若
binlog中无记录 → 回滚事务(撤销)
- 若
- 数据页修复:通过
redolog恢复已提交但未刷盘的物理数据页
二、ACID
InnoDB通过UndoLog(原子性)、锁与MVCC(隔离性)、RedoLog(持久性) 的协同,最终实现 一致性,构成完整的ACID保障体系
Binlog 并不直接参与持久性保障,但两者通过协同机制(如两阶段提交)间接关联
| 特性 | 依赖机制 | 与其他特性的关联 |
|---|---|---|
| 原子性 | Undo Log |
为一致性提供回滚基础,隔离性保障回滚不受干扰 |
| 一致性 | 三大特性协同 | 原子性+隔离性+持久性的最终目标 |
| 隔离性 | 锁+MVCC |
避免并发事务破坏原子性和一致性 |
| 持久性 | Redo Log |
确保已提交事务的结果永久生效,支撑一致性 |
1、原子性(Atomicity)
定义:事务中的操作要么全部成功,要么全部失败回滚,不存在部分成功的情况。
实现机制:
Undo Log(回滚日志)-
每条数据变更(INSERT/UPDATE/DELETE)前,InnoDB 会先在 Undo Log 中记录修改前的数据状态(如原始值、行标识等)
-
若事务回滚,InnoDB 根据 Undo Log 逆向执行操作(如 INSERT 的逆向是 DELETE)恢复数据
-
- 关键设计
UndoLog的修改会记录到RedoLog,确保UndoLog本身的持久性(即使崩溃也能恢复回滚所需信息)
示例:转账事务中,若扣款成功但存款失败,Undo Log 会撤销扣款操作,保证账户总额不变
2、 一致性(Consistency)
定义:事务执行前后,数据库必须满足所有完整性约束(如主键、外键)和业务逻辑一致性(如账户总额不变)。
实现机制:要保证数据库的数据一致性,要在以下两个方面做努力:
- 数据库的特性:
- 约束校验:事务提交时检查外键、唯一键等约束,违反则回滚(如转账后账户余额为负会触发回滚)
- 比如声明某个列为
NOT NULL来拒绝NULL值得插入等。
- 绝大部分还是需要我们程序员保证
- 原子性、隔离性、持久性的协同:
- 原子性确保操作全或无,隔离性避免并发干扰,持久性保证提交后数据永久生效,三者共同支撑一致性
- 原子性、隔离性、持久性的协同:
示例:用户A和B共有5000元,无论转账多少次,事务结束后总额仍为5000元
3、隔离性(Isolation)
定义:多个事务并发执行的时候,事务内部的操作与其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
- 隔离性可能会引入脏读(
dirtyread)、不可重复读(non-repeatableread)、幻读(phantomread)等问题,为了解决这些问题就引入了“隔离级别”的概念。
实现机制:
-
锁机制
-
行级锁:写操作加排他锁(
X锁),阻塞其他事务修改同一行 -
间隙锁(
GapLock):在可重复读隔离级别下锁定范围,防止幻读(如阻止其他事务在查询范围内插入新记录)
-
-
MVCC(多版本并发控制):每个事务启动时生成一个ReadView,基于事务ID和版本链判断数据可见性-
读已提交:每次查询生成新
ReadView,看到其他事务已提交的修改 -
可重复读(默认):事务内复用同一
ReadView,保证多次读取结果一致
-
示例:事务A读取数据时,MVCC 会返回事务开始前的快照版本,避免读到事务 B 未提交的修改(脏读)
⬤ 读未提交(read uncommitted):一个事务还没提交时,它做的变更就能被别的事务看到。
⬤ 读已提交(read committed):一个事务提交之后,它做的变更才会被其他事务看到。
⬤ 可重复读(repeatable read) 一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。当然在可重复读隔离级别下,未提交变更对其他事务也是不可见的。
⬤ 和串行化(serializable): 顾名思义是对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。
SQL 标准中规定,针对不同的隔离级别,并发事务可以发生不同严重程度的问题,具体情况如下:
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
读未提交 |
可能 | 可能 | 可能 |
读提交 |
不可能 | 可能 | 可能 |
可重复读 |
不可能 | 不可能 | 可能 |
串行化 |
不可能 | 不可能 | 不可能 |


