1.事务的特性是什么?如何实现的?
事务的 ACID 四大特性分别由不同机制实现:原子性依赖 undo log,一致性由 ACID 综合保证,隔离性依靠 MVCC + 锁,持久性由 redo log + WAL 实现。
🔹1. 原子性(Atomicity)
含义: 事务中的操作要么全部成功,要么全部失败,不会执行一半。 MySQL InnoDB 实现: 👉 undo log(回滚日志)
- 更新时先记录旧值
- 事务失败可依赖 undo log 回滚
🔹2. 一致性(Consistency)
含义: 事务开始前与结束后,数据必须保持约束完整、逻辑正确。 实现: 👉 由原子性 + 隔离性 + 持久性共同保证 InnoDB 没有单独的模块专门保证一致性。
🔹3. 隔离性(Isolation)
含义: 多个事务并发执行时,互不干扰。 MySQL 实现方式: 👉 MVCC(多版本并发控制) 👉 锁机制(行锁、间隙锁、next-key 锁) 隔离级别(从低到高):
- 读未提交(可能脏读)
- 读已提交(可能不可重复读)
- 可重复读(MySQL 默认,可能幻读)
- 串行化(最安全,用锁)
🔹4. 持久性(Durability)
含义: 事务一旦提交,就保证数据不会丢。 InnoDB 实现: 👉 redo log(重做日志)
- 先写日志再写磁盘(WAL 技术)
- 崩溃后依靠 redo log 恢复
2.mysql可能出现什么和并发相关问题?
1. 脏读(Dirty Read)(读未提交)
- 一个事务读到了 另一个事务未提交的数据。
- 风险:对方一旦回滚,你读到的数据就是假的。

2. 不可重复读(Non-repeatable Read)(读已提交)
- 一个事务中 前后两次读同一行数据结果不一致。
- 原因:期间有其他事务 修改并提交 了该行。

3. 幻读(Phantom Read)
- 一个事务中 前后两次查询同一条件的记录数不一致。
- 原因:期间有其他事务 插入或删除 了满足条件的记录。

🎯 一句话记忆法
脏读读到未提交,不可重复读前后数据变,幻读前后行数变。
3.哪些场景不能允许脏读?
脏读=读到了“未提交”的数据 → 可能随时被回滚 → 风险极大
因此,以下场景 绝不能允许脏读。
✔ 1. 金融/银行系统
场景:余额读取
-
事务 A:把余额从 1000 改成 0(但还没提交)
-
事务 B:读取余额 → 看到了 0
结果:A 一旦回滚,B 看到的余额就是假的 → 资金错乱
✔ 2. 库存系统(电商、仓储)
场景:库存扣减
-
A:“临时把库存扣成 0”(未提交)
-
B:检查库存 → 以为没货了
后果:拒单、超卖、缺货计算错误
✔ 3. 订单系统
场景:订单状态读取
-
A:把订单改为“已取消”(未提交)
-
B:读取订单状态 → 认为已取消,进行后续业务
但 A 事务一旦回滚 → 订单实际上没取消
✔ 4. 转账系统、支付系统
脏读会导致读取到不真实的”余额、支付记录、流水号” → 影响对账。
✔ 5. 任何需要“强一致性”的业务
例如:
-
积分扣减
-
券码发放
-
库存锁定
-
用户权限修改
只要读错一次未提交数据,就会导致业务逻辑错误
4.**mysql的是怎么解决并发问题的?
MySQL 通过锁机制控制写冲突,通过 MVCC 实现非阻塞读,通过事务隔离级别定义可见性,这三者共同解决并发带来的脏读、不可重复读和幻读问题。
1. 锁机制(Locking)
用于控制并发情况下对同一行/表的访问冲突。
包括:
-
行锁(Record Lock):精确锁一行
-
表锁:锁整个表
-
意向锁:加速冲突检测
-
间隙锁(Gap Lock):阻止插入
-
Next-Key Lock:记录锁+间隙锁,防幻读
-
读写锁(S/X):读共享,写排他
👉 锁的功能:防止并发读写冲突(如更新同一行)
2. MVCC(多版本并发控制)
用于解决读写冲突,让读操作不阻塞写操作。
实现方式:
-
undo log(旧版本数据)
-
Read View(决定哪个版本可见)
👉 作用:
-
读操作不加锁
-
避免阻塞
-
实现“读已提交”和“可重复读”场景
** 3. 事务隔离级别(Isolation Level)
通过不同级别控制可见性,避免脏读、不可重复读、幻读。
| 隔离级别 | 解决问题 | |
|---|---|---|
| 读未提交 | 无 | 指一个事务还没提交时,它做的变更就能被其他事务看到 |
| 读已提交 | 避免脏读 | 指一个事务提交之后,它做的变更才能被其他事务看到; |
| 可重复读(默认) | 避免脏读 + 不可重复读(通过 MVCC) | 指一个事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的 |
| 串行化 | 避免所有并发问题,但性能最低 | 指一个事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的 |
「读已提交」隔离级别是在「每个语句执行前」都会重新生成一个 Read View, 「可重复读」隔离级别是「启动事务时」生成一个 Read View,然后整个事务期间都在用这个 Read View。
5.可重复读隔离级别下,A事务提交的数据,在B事务能看见吗?
✔ 情况 1:B 已经执行过第一次 SELECT(已经生成 ReadView)
👉 不能看到
因为 RR 隔离级别的 ReadView 在第一次读时就固定了可见的版本,整个事务期间都用同一份快照。
结论:如果 B 事务已经查过一次,那么整个事务期间都看不到 A 提交的新数据。
✔ 情况 2:B 还没有执行过第一次 SELECT
👉 能看到
因为此时 ReadView 还没生成,第一次读会直接用最新数据生成快照,所以可以看到 A 提交的数据。
🧠 为什么会这样?(一句话)
RR 隔离级别下,ReadView 在第一次 SELECT 时生成,只要已经生成,就一直读旧版本。
6.举个例子说可重复读下的幻读问题
在可重复读隔离级别下,如果事务 A 先查询一个不存在的记录,事务 B 随后插入该记录并提交,那么事务 A 在执行 update 或再次查询时可能突然看到这条新记录,这种“之前不存在、之后冒出来”的现象就是幻读。
表 t_stu 中当前没有 id = 5 的记录。
① 事务 A:开始并查询 id = 5
BEGIN; SELECT * FROM t_stu WHERE id = 5; -- 结果:空
此时 ReadView 已生成,记录 id=5 在此快照中不存在。
② 事务 B:插入 id = 5,并提交
BEGIN; INSERT INTO t_stu VALUES(5, '小美', 18); COMMIT;
此时数据库真实存在了 id = 5 这条记录。
③ 事务 A:更新 id = 5
UPDATE t_stu SET name='小林coding' WHERE id = 5;
⚠ 奇怪的事情发生了:
-
按 MVCC 的逻辑,A 事务中
id=5在快照中是不存在的 -
但 update 是“当前读”,使用锁,会发现这条记录真实存在
-
A 可以正常更新成功!(虽然它一开始查不到这条记录)
④ 事务 A 再次查询
SELECT * FROM t_stu WHERE id = 5;
结果:
| id | name | age |
|---|---|---|
| 5 | 小林coding | 18 |
事务 A 明明第一次查时 id=5 不存在,现在却查到了新插入的记录,这就是幻读。
7.Mysql 设置了可重读隔离级后,怎么保证不发生幻读?
尽量在开启事务后,马上执行select … for update这类锁定读的语句,因为他会对记录加next-keylock,避免其他食物插入一条新纪录🔒 三、加锁逻辑(lock)
8.串行化隔离级别是通过什么实现的?
通过「读写锁(S/X 锁)+ next-key 锁」实现。
1. 普通查询 SELECT → 会加 S 锁(共享锁)
-
S 锁之间可以并发
-
但会阻塞写(UPDATE / INSERT)
2. 写操作 → 加 X 锁(排他锁)
- X 锁会阻塞一切其他事务对该记录的访问(包括读)
3. 范围查询会加 next-key 锁
-
锁住记录 + 锁住间隙
-
避免出现幻读(别人不能插入新记录)
9.**介绍MVCC实现原理
MVCC = 多版本并发控制
目标:让读和写互不阻塞,提高并发性能
MySQL 通过 undo log + ReadView + 隐藏列 来实现。
MVCC(Multi-Version Concurrency Control,多版本并发控制)。这是一个在面试,尤其是互联网大厂(如字节、阿里)面试中,关于数据库底层原理的“必杀题”。
简单来说,MVCC 是为了解决一个核心矛盾:如何在不加锁的情况下,让“读”和“写”不冲突?
1. 为什么需要 MVCC?(背景)
在没有 MVCC 之前,数据库为了保证数据一致性,通常使用“锁”:
-
读读:没问题,大家一起看。
-
读写/写读:必须排队。一个人改的时候,另一个人不能看;一个人看的时候,另一个人不能改。
这在高并发下性能很差。MVCC 的出现,让“读”和“写”可以同时进行。 它的核心思想是:“当你改数据时,我去看这个数据的历史快照。”
2. MVCC 的“三根支柱”
MVCC 并不是靠单一技术实现的,它是靠三样东西配合完成的:
① 隐藏列(秘密档案)
InnoDB 存储引擎会在每行记录后面偷偷增加两个隐藏列:
-
DB_TRX_ID:记录最后一次修改该行记录的事务 ID。 -
DB_ROLL_PTR:回滚指针。它指向这条记录的上一个版本。
② Undo Log(历史记录链)
每当你修改一条数据,旧版本的数据并不会被立刻删掉,而是会被存入 Undo Log。利用隐藏列里的回滚指针,这些旧版本会像“串葫芦”一样连成一条版本链。
③ Read View(时空快照)
这是 MVCC 的“大脑”。当你开启一个事务执行查询时,系统会生成一个 Read View。它记录了当前系统中有哪些事务正在运行(还没提交)。
它像一张过滤网,决定了版本链中哪个版本对你是可见的。
3. MVCC 是如何工作的?(比喻法)
想象你在改一份文档,而你的老板要过来检查:
-
修改(写):你不是在原件上改,而是复印了一份。你在副本上改(Undo Log),并且记下你是谁(TRX_ID)。
-
查看(读):老板过来时,他手里拿着一张“清单”(Read View),上面写着“哪些人的修改还没做完”。
-
判断规则:
-
如果版本链上的修改者是已经提交的,老板就看那个版本。
-
如果修改者还在忙(在清单里),老板就顺着“回滚指针”去找上一个更早的、已经提交的版本。
-
结果:你继续改你的,老板看他的历史版,互不打扰。
4. RC vs RR:MVCC 在不同隔离级别下的区别
这是面试中最喜欢深挖的细节:
-
RC(Read Committed,读已提交):
-
逻辑:每次执行快照读(SELECT)时都会生成一个新的 Read View。
-
结果:如果别人提交了新数据,你下一秒再查就能看到,所以会产生“不可重复读”。
-
-
RR(Repeatable Read,可重复读):
-
逻辑:整个事务期间,只有第一次查询时生成一个 Read View,之后一直复用。
-
结果:无论别人怎么改、怎么提交,你看到的永远是事务刚开始那一刻的样子。这就是为什么它能实现“可重复读”。
-
5. 总结:MVCC 到底算不算“锁”?
虽然名字里常带着“锁机制”去讨论,但 MVCC 并不是真正的锁。
-
它是一种“无锁”的解决方案(针对读写冲突)。
-
它配合 行锁(处理写写冲突)一起构成了 InnoDB 的并发控制体系。
💡 面试官可能会追问:
“既然 MVCC 这么强,那它能解决‘幻读’吗?”
你可以这样回答:
“在 RR 级别下,MVCC 解决了快照读(普通 SELECT)的幻读。但如果是当前读(比如
SELECT ... FOR UPDATE),则需要配合 间隙锁(Gap Lock) 才能彻底解决幻读。”
你现在对这个“版本链”和“Read View”的逻辑感觉顺畅了吗?我们要不要写一个具体的 TRX_ID 比较算法 的例子,带你走一遍它是怎么判断“哪个版本可见”的?这可是大厂面试官最爱考的逻辑题。
undo log(记录旧版本)
当事务更新一条记录时:
-
把旧值写入 undo log
-
把新值写回记录
-
trx_id 设置为当前事务 ID
-
roll_pointer 指向旧版本
这样就可以沿着 undo log 找到历史版本。
Read View 有四个重要的字段:
(决定哪个版本对当前事务可见)



判断逻辑(面试重点)
一个版本的 trx_id:
-
trx_id < min_trx_id → 已提交,可见
-
trx_id >= max_trx_id → 未开始,不可见
-
min_trx_id ⇐ trx_id < max_trx_id:
- 在 m_ids 中 → 活跃事务 → 不可见
- 不在 m_ids 中 → 已提交 → 可见
如果不可见 → 根据 roll_pointer 找上一版本继续判断。
ReadView 创建时机(RC 与 RR 的区别)
[[5.事务#3-事务隔离级别isolation-level|3. 事务隔离级别(Isolation Level)]]
| 隔离级别 | 创建 ReadView 的时机 |
|---|---|
| 读已提交(RC) | 每条 SELECT 前都会重新生成 |
| 可重复读(RR) | 事务第一次 SELECT 时生成,之后保持不变 |
这就是:
-
RC 能看到别人提交的最新数据
-
RR 保证“快照一致性”不会变
数据行中的两个隐藏列(核心)

每条 InnoDB 记录都包含两个隐藏字段:
| 字段 | 作用 |
|---|---|
| trx_id | 最近一次修改该行的事务 ID |
| roll_pointer | 指向 undo log 中旧版本记录的指针 |
由此形成一个“版本链”:
最新记录 → 上一版本 → 再上一版本 → ...
10.一条update是不是原子性的?为什么?
是的,UPDATE 是原子性的。因为执行前 InnoDB 记录 undo log,用行锁保证独占修改,失败可回滚;事务提交后 redo log 保证宕机恢复。
11.滥用事务,或者一个事务里有特别多sql的弊端?
大事务的缺点是:锁时间长、阻塞严重、undo log 爆炸、回滚成本高、主从延迟、占用大量系统资源,是生产环境需要重点规避的。
1. 锁持有时间太长 → 造成严重阻塞 & 死锁
-
事务不提交,锁就不释放
-
锁住大量行或表,其他事务只能等待
-
容易出现 锁竞争、锁超时、死锁
**场景:**更新几十万行还不提交 → 全库卡死
2. undo log 变大 → 占用大量存储,回滚也很慢
每条 SQL 都会写 undo log,事务越大:
-
undo log 越多
-
回滚时需要“反向执行”所有操作 → 非常慢
-
占用大量磁盘和内存空间
3. 回滚风险大 → 一旦失败损失巨大
事务越大,失败概率越高:
-
只要中间一条 SQL 出错,整个事务都要回滚
-
大事务回滚要恢复上千上万行,非常耗时
4. 导致主从同步延迟(Binlog 复制延迟)
主库要等整个事务全部执行完,才写 binlog:
- 执行 10 分钟的事务
→ 10 分钟后才写入 binlog
→ 从库至少延迟 10 分钟
这是大事务最常见的生产事故来源。
5. 占用大量内存 / 资源(事务上下文长时间不释放)
事务没提交前:
-
锁资源
-
undo log
-
快照(在 RR 下 ReadView 全程保留)
-
行版本链变长(影响 MVCC 性能)
都会被占用,无法回收。