mysql事务是如何实现的_mysql事务执行机制解析

来源:这里教程网 时间:2026-02-28 20:46:39 作者:

MySQL 事务的底层实现不依赖“魔法”,而是靠

redo log
、
undo log
和
锁机制
三者协同完成,其中
redo log
保证持久性(crash-safe),
undo log
支持回滚和 MVCC,并发控制则由
行锁
+
间隙锁
等在
InnoDB
引擎内实现。

事务原子性靠 undo log 实现,不是靠内存回滚

很多人误以为事务回滚只是把内存里改过的数据“撤回来”。实际上,InnoDB 在事务修改记录前,会先将原值写入

undo log
。一旦执行
ROLLBACK
,就用这些旧值覆盖当前版本;即使事务已提交但崩溃重启,
undo log
还能用于构建一致性视图(MVCC)。

undo log
存在
rollback segment
中,属于逻辑日志(记录“怎么反向操作”),不是物理备份
长事务会阻止
undo log
回收,导致
ibdata1
文件持续膨胀,这是线上常见磁盘告警根源
INSERT
产生的
undo log
最简(只需记录
DELETE
操作),而
UPDATE
和
DELETE
会产生更复杂的版本链

持久性不靠刷脏页,靠 redo log 的两阶段写入

事务提交时,InnoDB 并不立即将

buffer pool
中的脏页写回磁盘(太慢),而是把修改操作记入
redo log
,并确保该日志落盘(
fsync
)。只要
redo log
完整,崩溃后就能重放日志恢复未刷盘的数据。

innodb_flush_log_at_trx_commit=1
:每次
COMMIT
都
fsync
,最安全但有性能开销;设为
0
或
2
会丢事务(最多丢 1 秒)
redo log
是固定大小的循环文件(如
ib_logfile0/ib_logfile1
),写满会触发
checkpoint
,推动脏页刷新
如果
redo log
写失败(比如磁盘只读),事务会直接报错
ERROR 1030 (HY000): Got error -1 from storage engine

可重复读隔离级别下,快照读不加锁,但幻读仍需间隙锁拦截

MySQL 默认的

REPEATABLE READ
隔离级别下,普通
SELECT
是快照读(基于
read view
读取事务开始时的已提交版本),不加锁;但当前读(如
SELECT ... FOR UPDATE
、
UPDATE
、
DELETE
)会加行锁+间隙锁,防止其他事务插入“幻行”。

间隙锁(
gap lock
)锁定的是索引区间,不是具体记录,所以
WHERE
条件没走索引时,会升级为表级锁(
next-key lock = record lock + gap lock
)
SELECT ... LOCK IN SHARE MODE
和
FOR UPDATE
都是当前读,都会触发加锁,区别只在锁类型(S 锁 vs X 锁)
唯一索引等值查询(
WHERE id = ?
)只加
record lock
,不加
gap lock
;范围查询(
WHERE id > 10
)或非唯一索引一定会加
gap lock

事务提交不是原子动作,XA 分布式事务更要小心两阶段提交残留

单机事务看似一气呵成,实际内部也分“prepare → write redo → write binlog → commit”多个步骤。MySQL 5.7+ 引入

binlog_group_commit
优化,但极端情况下(如写
binlog
成功而引擎层
commit
失败),可能留下
XID
状态为
PREPARE
的悬挂事务。

可通过
SELECT * FROM information_schema.INNODB_TRX WHERE trx_state = 'PREPARED'
查到悬挂事务
这类事务不会自动清理,需人工判断是否
COMMIT
或
ROLLBACK
,否则长期占用
undo log
和锁资源
使用
mysqlbinlog --base64-output=DECODE-ROWS -v
解析
binlog
,可确认对应
XID
是否已写入,再决定恢复策略

真正难的从来不是“开启事务”这个动作,而是理解每个 SQL 在不同隔离级别、不同索引条件下到底加了什么锁、写了哪些日志、何时刷盘——这些细节藏在

INFORMATION_SCHEMA
表、
SHOW ENGINE INNODB STATUS
输出和
binlog
/
redo log
解析结果里,而不是文档第一行。

相关推荐