事务的ACID特性在InnoDB中靠什么落地
InnoDB不是靠“声明式支持”来提供事务,而是用一套协同工作的底层机制硬实现的:redo log 保证持久性(D),undo log 保证原子性(A)和一致性(C),而行级锁 + MVCC 一起支撑隔离性(I)。这四者缺一不可,删掉任意一个,
START TRANSACTION就只剩个空壳。
redo log 和 binlog 的分工与协作
很多人误以为
commit成功 = 数据已刷盘。实际是:事务提交时,InnoDB 只确保
redo log写入磁盘(由
innodb_flush_log_at_trx_commit控制),而数据页(
ibd文件)可能还在 buffer pool 中延迟写入。这样既快又安全——崩溃后可用 redo log 重放已提交但未落盘的变更。
binlog是 Server 层日志,不参与崩溃恢复,只用于主从复制和归档。MySQL 用两阶段提交(2PC)协调二者:
prepare阶段写 redo log 并标记为 prepared;
commit阶段先写 binlog,再将 redo log 标记为 commit。任一环节失败,crash recovery 都能靠这个状态判断是否回滚。
undo log 如何支撑回滚和 MVCC
每次
UPDATE或
DELETE,InnoDB 不直接覆盖旧数据,而是把原记录写进
undo log,并用
roll_pointer指向它。这样: 执行
ROLLBACK时,顺着
roll_pointer链一路恢复即可 其他事务做
SELECT时,根据自己的
read view判断该读哪个版本——这就是 MVCC 的核心。注意:
READ COMMITTED每条语句都生成新
read view,而
REPEATABLE READ在事务第一次
SELECT时就固定住
read view
purge线程异步清理已无事务需要的 undo log,否则
ibdata1会持续膨胀
行锁、间隙锁与死锁检测的真实开销
InnoDB 默认加的是
next-key lock(行锁 + 间隙锁),不是单纯的“某一行被锁”。比如
WHERE id = 5在唯一索引上才可能是纯行锁;若查的是非唯一索引或范围条件(如
WHERE age BETWEEN 20 AND 30),就会锁住索引间隙,防止幻读——这也意味着并发插入可能被意外阻塞。
死锁检测由
innodb_deadlock_detect控制,默认开启。一旦触发,InnoDB 会主动回滚代价最小的事务(按
undo log大小估算)。但高并发下频繁检测本身有 CPU 开销;关掉它虽省资源,却要靠
innodb_lock_wait_timeout超时被动退出,响应更不可控。
真正容易被忽略的是:唯一索引等值查询(
=)且记录存在时,InnoDB 才会优化为仅加 record lock;只要记录不存在,哪怕条件唯一,也会退化为 gap lock —— 这点在设计防重逻辑时经常踩坑。
