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
解析结果里,而不是文档第一行。

相关推荐