4858 字
24 分钟
100 道常见 MySQL 数据库题目事务篇
2026-04-16 16:18:56
无标签

48. MySQL 事务的四大特性 ACID 说一下?#

答:

事务的四大特性通常简称为 ACID,分别是:

  • A:Atomicity(原子性)
  • C:Consistency(一致性)
  • I:Isolation(隔离性)
  • D:Durability(持久性)

这是事务最基础、最核心的概念,面试里几乎一定会问。


1)原子性(Atomicity)#

原子性的意思是:

一个事务中的所有操作,要么全部成功,要么全部失败回滚。

事务是一个不可再分割的整体。

例子

转账操作:

  1. A 账户减 100
  2. 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)隔离性靠什么保证?#

隔离性主要靠两类机制:

  1. 锁机制
  2. 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 标准定义了四种事务隔离级别,用来控制多个事务并发执行时的可见性和相互影响程度。

这四种隔离级别分别是:

  1. Read Uncommitted(读未提交)
  2. Read Committed(读已提交)
  3. Repeatable Read(可重复读)
  4. 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 中,主要依赖两类机制:

  1. MVCC(多版本并发控制)

不同隔离级别使用它们的方式不同。


1)Read Uncommitted(读未提交)怎么实现?#

读未提交的特点是:

一个事务可以读到另一个事务尚未提交的数据。

所以它基本可以理解为:

  • 读操作几乎不做一致性保护
  • 不使用严格的可见性控制
  • 隔离性最弱

这种级别一般不依赖复杂 MVCC 可见性控制去屏蔽未提交数据,所以可能发生脏读。


2)Read Committed(读已提交)怎么实现?#

读已提交的核心特点是:

每次读取数据时,都生成一个新的 Read View。

这意味着:

  • 当前事务每次执行快照读,都只看“当前已提交”的版本
  • 别的事务一旦提交,下一次查询就能看到了

所以它能解决:

  • 脏读

但不能解决:

  • 不可重复读
  • 幻读

实现机制

主要依赖:

  • MVCC
  • 每次读生成新的 Read View

3)Repeatable Read(可重复读)怎么实现?#

可重复读的核心特点是:

一个事务中第一次快照读时生成 Read View,后续都复用这个 Read View。

所以在同一个事务里,即使别人提交了新的修改,只要当前事务还是基于旧的快照读,就能保证多次读取结果一致。

这就是为什么它能避免:

  • 脏读
  • 不可重复读

实现机制

主要依赖:

  • MVCC
  • 第一次读生成的 Read View
  • 后续重复使用

另外,对于当前读(如 updatedeleteselect ... for update),还会配合:

  • 行锁
  • Gap Lock
  • Next-Key Lock

来避免幻读问题。


4)Serializable(串行化)怎么实现?#

串行化的思路最直接:

通过加锁让事务串行执行。

在这个级别下:

  • 读通常要加共享锁
  • 写要加排他锁
  • 事务之间严重互斥

这就意味着后来的事务必须等待前面的事务完成,所以可以避免:

  • 脏读
  • 不可重复读
  • 幻读

实现机制

主要依赖:

  • 读写锁
  • 强制串行化执行

代价就是并发性能最差。


5)总结成表格#

隔离级别实现方式
Read Uncommitted几乎不做严格可见性控制
Read CommittedMVCC + 每次读生成新 Read View
Repeatable ReadMVCC + 事务首次读生成 Read View 并复用
Serializable读写加锁,强制串行执行

6)补充:什么是快照读和当前读?#

要理解隔离级别实现,最好顺带提一下这两个概念。

快照读#

普通 select 一般属于快照读。 它通过 MVCC 读取历史版本,不加锁。

当前读#

读取最新版本,并可能加锁,例如:

  • select ... for update
  • select ... lock in share mode
  • update
  • delete

可重复读级别下,快照读靠 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 主要依赖以下几个东西:

  1. 隐藏字段
  2. undo log
  3. 版本链
  4. Read View
  5. 快照读

4)隐藏字段#

InnoDB 每一行记录实际上都会带一些隐藏字段,其中和 MVCC 最相关的是:

  • DB_TRX_ID
  • DB_ROLL_PTR

DB_TRX_ID#

表示最后一次修改这条记录的事务 ID。

DB_ROLL_PTR#

回滚指针,指向这条记录对应的 undo log 位置。

通过这两个字段,就能把一条记录的多个历史版本串起来。


5)undo log 和版本链#

当一条记录被修改时,InnoDB 不会简单粗暴地把旧数据抹掉,而是会:

  1. 把修改前的旧值写到 undo log
  2. 当前记录指向这条 undo log
  3. 下一次修改时,再产生新的 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 update
  • update
  • delete

仍然要靠锁机制保证并发安全。

所以它不是替代锁,而是和锁一起配合使用。


11)一句话总结#

MVCC 的本质,是通过 undo log 形成版本链,再结合 Read View 判断可见性,让普通读操作不加锁也能读取一致性数据。

Comment seems to stuck. Try to refresh?✨