题目:MySQL 事务 ACID 是如何实现的?
难度:medium
标签:db/mysql/concepts, mysql/acid, mysql/transaction, difficulty/medium
评分:⭐⭐☆☆(2/5)
首先明确下 MySQL 事务的 ACID 都是什么?原子性、一致性、隔离性、持久性。原子性的话基本上是通过锁来执行的,以及 undo log 重做日志去保证。隔离性的话是通过锁,隔离是通过锁,行锁、表锁、间隙锁这些东西把不同的事务进行隔离。持久性就是 binlog 跟 binlog 日志去实现的持久性,然后把数据存到 binlog 以及磁盘文件实现持久性。最终通过原子、隔离、持久三个性质一致。三个性质来实现一致性。
优点:
不足:
补充:
核心考点定位:掌握 ACID 每个性质在 InnoDB 里的具体实现机制,尤其是 redo log、undo log、MVCC、锁、2PC 的分工。
知识点拆解:
SELECT FOR UPDATE / UPDATE / DELETE 隐式加锁innodb_flush_log_at_trx_commit=1 保证)preparecommit| 日志 | 层级 | 记录内容 | 主要用途 |
|---|---|---|---|
| undo log | InnoDB 引擎层 | 旧值(逻辑) | 回滚 + MVCC |
| redo log | InnoDB 引擎层 | 物理页修改 | 崩溃恢复(WAL) |
| binlog | MySQL Server 层 | 逻辑 SQL / row | 主从复制 + 灾备 |
innodb_flush_log_at_trx_commit:1=每次 commit 刷 redo(最安全),2=写 OS cache 不刷盘(MySQL 崩溃不丢,系统崩溃可能丢),0=每秒刷(丢 1 秒数据)sync_binlog:1=每次刷 binlog,1000=每 1000 个事务刷,0=OS 决定sync_binlog=1 + innodb_flush_log_at_trx_commit=1,最安全但最慢innodb_log_file_size:redo log 文件大小,太小会频繁刷脏页考察方向:
延伸递进面试题:
innodb_flush_log_at_trx_commit=2 什么时候用?面试官您好,MySQL 事务的 ACID 我按每个字母拆开讲。
原子性靠 undo log 实现。undo log 是回滚日志,记录每次修改的旧值。事务回滚时按 undo log 反向重放,把数据恢复到事务开始前的状态。崩溃恢复时也一样,未提交的事务用 undo log 回滚。
一致性其实是数据库 + 业务共同保证的。数据库本身只保证 A、I、D 三个性质,业务一致性需要靠约束(NOT NULL、UNIQUE、外键)加上事务边界内正确的业务代码。比如转账 A 减 100、B 加 100,两条 SQL 必须在同一个事务里,靠原子性保证要么都成功要么都失败。
隔离性主要靠锁和 MVCC 两套机制。写操作和当前读(SELECT FOR UPDATE、UPDATE、DELETE)用悲观锁,包括行锁、间隙锁、next-key lock;快照读(普通 SELECT)用 MVCC,通过 ReadView 判断行的哪个版本对当前事务可见,不需要加锁。两套机制配合解决了脏读、不可重复读和部分幻读。
持久性靠 redo log 和 2PC 实现,跟 binlog 是分开的。redo log 是 InnoDB 引擎层的物理日志,记录哪个页的哪字节改成什么。事务提交时把 redo log 刷盘,用 WAL(Write-Ahead Logging)机制保证——先写日志再改内存页,崩溃后用 redo 重放未落盘的页修改。这里要注意 innodb_flush_log_at_trx_commit=1 才能确保每次都刷,不然可能丢数据。
binlog 是 MySQL Server 层的逻辑日志,主要用于主从复制和灾备恢复,不是持久性的直接保证。redo 和 binlog 通过 2PC(两阶段提交) 联动:InnoDB 先写 redo log 状态 prepare,然后写 binlog,最后把 redo 状态改成 commit。崩溃恢复时根据 redo 和 binlog 的状态判断是提交还是回滚。
所以三个日志的分工是:undo log 负责回滚(原子性)、redo log 负责崩溃恢复(持久性)、binlog 负责复制和灾备。生产环境通常用"双 1 配置"——sync_binlog=1 + innodb_flush_log_at_trx_commit=1,两个日志都每次刷盘,保证不丢事务。