48. MySQL 事务的四大特性 ACID 说一下?
答:
事务的四大特性通常简称为 ACID,分别是:
- A:Atomicity(原子性)
- C:Consistency(一致性)
- I:Isolation(隔离性)
- D:Durability(持久性)
这是事务最基础、最核心的概念,面试里几乎一定会问。
1)原子性(Atomicity)
原子性的意思是:
一个事务中的所有操作,要么全部成功,要么全部失败回滚。
事务是一个不可再分割的整体。
例子
转账操作:
- A 账户减 100
- B 账户加 100
这两个动作必须看成一个整体。 如果只执行了第一步,第二步失败,就会导致钱“凭空消失”。
原子性要求:
- 要么两步都成功
- 要么两步都不生效
这就是“要么全做,要么全不做”。
2)一致性(Consistency)
一致性的意思是:
事务执行前后,数据库都必须处于一致的、合法的状态。
所谓“一致”,可以理解为业务规则不能被破坏。
例子
还是转账:
- A 有 1000 元
- B 有 500 元
转账 100 元后:
- A 变成 900
- B 变成 600
总金额仍然是 1500,没有变化。 这就是一致性。
如果因为事务中途出错导致:
- A 扣了钱
- B 没加钱
那数据库就处于不一致状态。
注意
一致性是最终目标,原子性、隔离性、持久性都是帮助实现一致性的手段。
3)隔离性(Isolation)
隔离性的意思是:
多个事务并发执行时,一个事务的中间状态不应该被其他事务干扰。
也就是说,事务和事务之间应该彼此隔离。
例子
事务 A 正在修改一条数据,但还没提交; 事务 B 此时不应该随意看到事务 A 尚未提交的中间结果。
如果隔离性做得不好,就会出现:
- 脏读
- 不可重复读
- 幻读
所以 MySQL 才会设计不同的事务隔离级别来平衡性能和一致性。
4)持久性(Durability)
持久性的意思是:
事务一旦提交,它对数据库的修改就应该永久保存,即使数据库宕机也不能丢失。
例子
事务提交成功后,即使此时服务器突然断电、数据库崩溃,重启后数据也应该还是提交后的结果。
MySQL 中主要依靠:
- redo log
- 刷盘机制
来保证持久性。
5)怎么理解 ACID 的关系?
可以这么理解:
- 原子性:事务不能只做一半
- 一致性:事务执行后,数据必须正确
- 隔离性:多个事务之间互不干扰
- 持久性:提交后的结果不能丢
6)一句话总结
ACID 是事务的四大特性,分别保证事务“完整做、做正确、互不干扰、提交不丢”。
49. 那 ACID 靠什么保证的呢?
答:
ACID 是事务对外表现出来的四大特性,但这些特性不是凭空实现的,MySQL/InnoDB 是通过一系列机制共同保证的。
通常可以这样回答:
- 原子性:靠
undo log - 持久性:靠
redo log - 隔离性:靠锁和 MVCC
- 一致性:由原子性、持久性、隔离性共同保证,同时也依赖业务约束
这是面试里比较标准的回答框架。
1)原子性靠什么保证?
原子性主要靠:
undo log(回滚日志)
为什么?
因为事务执行过程中,如果某一步失败,或者用户主动执行了:
ROLLBACK;数据库需要把之前已经执行过的操作撤销掉。
例子
事务中执行了:
UPDATE account SET balance = balance - 100 WHERE id = 1;在真正修改前,InnoDB 会先记录旧值到 undo log。 如果事务后续失败,就可以根据 undo log 把数据恢复回去。
所以说:
undo log 让事务具备“后悔药”,从而保证原子性。
2)持久性靠什么保证?
持久性主要靠:
redo log(重做日志)
为什么?
事务提交时,不一定所有数据页都已经写入磁盘。 如果此时数据库宕机,内存中的修改可能丢失。
为了解决这个问题,InnoDB 会先把修改记录写入 redo log。 即使数据页还没真正刷盘,只要 redo log 在,数据库重启时就能把已提交事务恢复出来。
所以:
redo log 保证了事务提交后即使宕机也不丢。
3)隔离性靠什么保证?
隔离性主要靠两类机制:
- 锁机制
- MVCC(多版本并发控制)
(1)锁机制
用于控制写写、读写冲突,例如:
- 行锁
- 表锁
- Next-Key Lock
- Gap Lock
它保证多个事务不会随意互相破坏数据。
(2)MVCC
主要用于提高一致性读性能。
在读已提交、可重复读隔离级别下,普通 select 不加锁,依靠 MVCC 读取数据的历史版本,从而实现事务隔离。
所以隔离性并不是只靠锁,而是:
- 写操作更多靠锁
- 读操作更多靠 MVCC
4)一致性靠什么保证?
一致性不能简单地说只靠某一种日志,它是最终目标,通常由多方面共同保证:
- 原子性保证事务不会只执行一半
- 持久性保证提交结果不会丢
- 隔离性保证并发下结果不混乱
- 业务约束、主键、唯一约束、外键、检查逻辑等也会参与保证一致性
所以更准确地说:
一致性是 ACID 最终要达到的目标,它由原子性、隔离性、持久性以及业务规则共同保证。
5)总结表格
| ACID 特性 | 主要依赖机制 |
|---|---|
| 原子性 | undo log |
| 持久性 | redo log |
| 隔离性 | 锁 + MVCC |
| 一致性 | 由原子性、隔离性、持久性及业务约束共同保证 |
6)一句话总结
MySQL 中,原子性靠 undo log,持久性靠 redo log,隔离性靠锁和 MVCC,一致性则是这些机制共同作用后的最终结果。
50. 事务的隔离级别有哪些?MySQL 默认隔离级别是什么?
答:
SQL 标准定义了四种事务隔离级别,用来控制多个事务并发执行时的可见性和相互影响程度。
这四种隔离级别分别是:
- Read Uncommitted(读未提交)
- Read Committed(读已提交)
- Repeatable Read(可重复读)
- Serializable(串行化)
MySQL InnoDB 默认的事务隔离级别是:
Repeatable Read(可重复读)
1)Read Uncommitted(读未提交)
这是最低的隔离级别。
在这个级别下:
- 一个事务可以读到另一个事务尚未提交的数据
这就会导致:
脏读
特点
- 隔离性最弱
- 并发性能最高
- 几乎很少在实际业务中使用
2)Read Committed(读已提交)
在这个级别下:
一个事务只能读到其他事务已经提交的数据
也就是说,未提交的数据对当前事务不可见。
它解决了脏读问题,但仍然可能出现:
- 不可重复读
- 幻读
特点
- Oracle 默认隔离级别是它
- 比读未提交更安全
- 并发性能也较好
3)Repeatable Read(可重复读)
在这个级别下:
一个事务中多次读取同一条数据,结果保持一致。
它解决了:
- 脏读
- 不可重复读
在 MySQL InnoDB 中,配合 MVCC 和 Next-Key Lock,通常也能较好避免幻读问题。
特点
- MySQL 默认级别
- 在一致性和性能之间做了较好平衡
4)Serializable(串行化)
这是最高的隔离级别。
它会强制事务串行执行,读写都会加锁,避免:
- 脏读
- 不可重复读
- 幻读
特点
- 最安全
- 并发性能最差
- 一般只在极端强一致性场景使用
5)各隔离级别可能出现的问题
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| Read Uncommitted | 会 | 会 | 会 |
| Read Committed | 不会 | 会 | 会 |
| Repeatable Read | 不会 | 不会 | 理论上可能,InnoDB 中大多可避免 |
| Serializable | 不会 | 不会 | 不会 |
6)MySQL 默认为什么是可重复读?
因为它在以下两者之间取得了比较好的平衡:
- 一致性
- 并发性能
并且 InnoDB 配合:
- MVCC
- Next-Key Lock
可以很好地处理大多数事务并发问题,所以 MySQL 选择它作为默认隔离级别。
7)查看和设置隔离级别
查看当前隔离级别:
SELECT @@transaction_isolation;设置隔离级别:
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;8)一句话总结
事务隔离级别有四种,MySQL InnoDB 默认是 Repeatable Read(可重复读)。
51. 什么是幻读、脏读、不可重复读?
答:
这三个概念都是事务并发时可能出现的典型问题。 它们的区别主要在于:读到了什么样的不一致数据。
1)脏读(Dirty Read)
脏读指的是:
一个事务读到了另一个事务尚未提交的数据。
也就是说,读到的是“脏数据”。
例子
事务 A:
UPDATE account SET balance = 500 WHERE id = 1;-- 还没提交事务 B:
SELECT balance FROM account WHERE id = 1;如果事务 B 此时读到了 500,但事务 A 后面又执行了:
ROLLBACK;那么事务 B 读到的 500 就是不存在的“脏数据”。
本质
脏读的问题是:
读到了别人还没最终确认的数据。
这种数据可能随后被回滚,所以非常危险。
2)不可重复读(Non-repeatable Read)
不可重复读指的是:
同一个事务中,两次读取同一条记录,结果不一样。
这里注意重点:
- 是同一条记录
- 两次查询结果不同
- 通常是因为别的事务修改并提交了这条记录
例子
事务 A 第一次读:
SELECT balance FROM account WHERE id = 1;-- 结果 1000事务 B 执行:
UPDATE account SET balance = 900 WHERE id = 1;COMMIT;事务 A 第二次再读:
SELECT balance FROM account WHERE id = 1;-- 结果 900同一事务内,同一条记录,两次读取结果不同,这就是不可重复读。
本质
不可重复读的问题是:
同一条数据在一个事务里前后读出来不一致。
3)幻读(Phantom Read)
幻读指的是:
同一个事务中,两次按相同条件范围查询,得到的记录条数不一样。
这里重点不是“同一条记录被修改”,而是:
- 某个范围内多了新记录,或者少了记录
- 看起来像“幻觉”一样多出/少出了一行
例子
事务 A 第一次查询:
SELECT * FROM orders WHERE amount > 100;-- 查到 10 条事务 B 插入一条新记录:
INSERT INTO orders(id, amount) VALUES(101, 200);COMMIT;事务 A 再次执行同样查询:
SELECT * FROM orders WHERE amount > 100;-- 查到 11 条同样条件、同一事务、结果集条数变了,这就是幻读。
本质
幻读的问题是:
不是某条记录变了,而是符合条件的“记录集合”变了。
4)三者区别总结
可以这么记:
- 脏读:读到了别人未提交的数据
- 不可重复读:同一条记录,两次读取不一样
- 幻读:同一范围,两次读取出来的记录数量不一样
5)更直观的对比
| 问题 | 关注点 |
|---|---|
| 脏读 | 读到未提交数据 |
| 不可重复读 | 同一条记录的值变了 |
| 幻读 | 同一范围内记录条数变了 |
6)一句话总结
脏读是“读脏了”,不可重复读是“同一行变了”,幻读是“同一批数据多了或少了”。
52. 事务的各个隔离级别都是如何实现的?
答:
这个问题是对“隔离级别原理”的考察。 不同隔离级别,本质上是通过不同的并发控制手段实现的。 在 InnoDB 中,主要依赖两类机制:
- 锁
- MVCC(多版本并发控制)
不同隔离级别使用它们的方式不同。
1)Read Uncommitted(读未提交)怎么实现?
读未提交的特点是:
一个事务可以读到另一个事务尚未提交的数据。
所以它基本可以理解为:
- 读操作几乎不做一致性保护
- 不使用严格的可见性控制
- 隔离性最弱
这种级别一般不依赖复杂 MVCC 可见性控制去屏蔽未提交数据,所以可能发生脏读。
2)Read Committed(读已提交)怎么实现?
读已提交的核心特点是:
每次读取数据时,都生成一个新的 Read View。
这意味着:
- 当前事务每次执行快照读,都只看“当前已提交”的版本
- 别的事务一旦提交,下一次查询就能看到了
所以它能解决:
- 脏读
但不能解决:
- 不可重复读
- 幻读
实现机制
主要依赖:
- MVCC
- 每次读生成新的 Read View
3)Repeatable Read(可重复读)怎么实现?
可重复读的核心特点是:
一个事务中第一次快照读时生成 Read View,后续都复用这个 Read View。
所以在同一个事务里,即使别人提交了新的修改,只要当前事务还是基于旧的快照读,就能保证多次读取结果一致。
这就是为什么它能避免:
- 脏读
- 不可重复读
实现机制
主要依赖:
- MVCC
- 第一次读生成的 Read View
- 后续重复使用
另外,对于当前读(如 update、delete、select ... for update),还会配合:
- 行锁
- Gap Lock
- Next-Key Lock
来避免幻读问题。
4)Serializable(串行化)怎么实现?
串行化的思路最直接:
通过加锁让事务串行执行。
在这个级别下:
- 读通常要加共享锁
- 写要加排他锁
- 事务之间严重互斥
这就意味着后来的事务必须等待前面的事务完成,所以可以避免:
- 脏读
- 不可重复读
- 幻读
实现机制
主要依赖:
- 读写锁
- 强制串行化执行
代价就是并发性能最差。
5)总结成表格
| 隔离级别 | 实现方式 |
|---|---|
| Read Uncommitted | 几乎不做严格可见性控制 |
| Read Committed | MVCC + 每次读生成新 Read View |
| Repeatable Read | MVCC + 事务首次读生成 Read View 并复用 |
| Serializable | 读写加锁,强制串行执行 |
6)补充:什么是快照读和当前读?
要理解隔离级别实现,最好顺带提一下这两个概念。
快照读
普通 select 一般属于快照读。
它通过 MVCC 读取历史版本,不加锁。
当前读
读取最新版本,并可能加锁,例如:
select ... for updateselect ... lock in share modeupdatedelete
可重复读级别下,快照读靠 MVCC,一些幻读问题则通过当前读 + 锁机制解决。
7)一句话总结
事务隔离级别主要通过 MVCC 和锁实现:读已提交、可重复读依赖 MVCC,串行化主要依赖强锁控制。
53. MVCC 了解吗?怎么实现的?
答:
MVCC 是面试中非常高频的事务原理问题。 它的全称是:
Multi-Version Concurrency Control,多版本并发控制
它的核心作用是:
通过维护数据的多个版本,让读操作不加锁也能获得一致性结果,从而提升并发性能。
1)为什么需要 MVCC?
如果没有 MVCC,最简单的并发控制方式就是:
- 读加锁
- 写加锁
这样虽然安全,但并发性能会很差。 因为大量读写会互相阻塞。
MVCC 的目标就是:
让“读”和“写”尽量不冲突。
普通查询读取旧版本数据,写操作修改新版本数据,二者并行执行,这样系统吞吐量更高。
2)MVCC 解决什么问题?
MVCC 主要解决的是:
- 读已提交下的一致性读
- 可重复读下的一致性读
它主要服务于:
- 快照读
而不是所有读。
换句话说,普通 select 往往靠 MVCC;
而 select ... for update 这种当前读还是要靠锁。
3)MVCC 的实现核心有哪些?
InnoDB 中 MVCC 主要依赖以下几个东西:
- 隐藏字段
- undo log
- 版本链
- Read View
- 快照读
4)隐藏字段
InnoDB 每一行记录实际上都会带一些隐藏字段,其中和 MVCC 最相关的是:
DB_TRX_IDDB_ROLL_PTR
DB_TRX_ID
表示最后一次修改这条记录的事务 ID。
DB_ROLL_PTR
回滚指针,指向这条记录对应的 undo log 位置。
通过这两个字段,就能把一条记录的多个历史版本串起来。
5)undo log 和版本链
当一条记录被修改时,InnoDB 不会简单粗暴地把旧数据抹掉,而是会:
- 把修改前的旧值写到 undo log
- 当前记录指向这条 undo log
- 下一次修改时,再产生新的 undo log
这样就形成了一条版本链。
例子
假设一条记录最初是:
id=1, name='Tom'后来:
- 事务 100 把它改成
Jack - 事务 200 又把它改成
Lucy
那么版本链就可能是:
Lucy -> Jack -> Tom最新版本在数据页中,老版本沿着 undo log 一路往回找。
6)Read View 是什么?
Read View 可以理解为:
事务在做快照读时,用来判断“哪些版本对我可见”的规则集合。
它记录了当前事务启动时的一些事务状态,例如:
- 当前活跃事务 ID 列表
- 最小活跃事务 ID
- 下一个待分配事务 ID
- 当前事务自己的事务 ID
有了这些信息,当前事务就能判断某条记录的某个版本是否对自己可见。
7)可见性判断怎么做?
假设当前事务正在读一条记录的某个版本,它会看这个版本上的 DB_TRX_ID,再结合 Read View 来判断是否可见。
大致规则可以这样理解:
情况 1:这个版本是自己改的
如果版本的事务 ID 就是当前事务自己,那肯定可见。
情况 2:这个版本对应的事务在 Read View 生成前已经提交
那也可见。
情况 3:这个版本对应的事务在 Read View 生成时还活跃
那不可见。
情况 4:这个版本对应的事务在 Read View 生成后才开启
那也不可见。
如果当前版本不可见,就顺着 undo log 找前一个旧版本继续判断,直到找到可见版本。
这就是 MVCC 的核心过程。
8)Read Committed 和 Repeatable Read 的区别在哪?
两者都用 MVCC,但关键区别在于 Read View 生成时机不同。
Read Committed
每次执行快照读,都会生成新的 Read View。 所以每次都能看到别人最新已提交的数据。
Repeatable Read
事务第一次快照读时生成 Read View,后续都复用这一个。 所以同一事务里多次读结果保持一致。
9)MVCC 的优点
(1)提高并发性能
读不加锁,减少读写冲突。
(2)保证一致性读
让事务在并发情况下仍然能读到合理版本的数据。
(3)配合隔离级别工作
很好地支撑了:
- Read Committed
- Repeatable Read
10)MVCC 不是万能的
MVCC 主要用于快照读。 对于当前读,例如:
select ... for updateupdatedelete
仍然要靠锁机制保证并发安全。
所以它不是替代锁,而是和锁一起配合使用。
11)一句话总结
MVCC 的本质,是通过 undo log 形成版本链,再结合 Read View 判断可见性,让普通读操作不加锁也能读取一致性数据。