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;

结果:

idnameage
5小林coding18

事务 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 + 隐藏列 来实现。

undolog日志

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 是如何工作的?(比喻法)

想象你在改一份文档,而你的老板要过来检查:

  1. 修改(写):你不是在原件上改,而是复印了一份。你在副本上改(Undo Log),并且记下你是谁(TRX_ID)。

  2. 查看(读):老板过来时,他手里拿着一张“清单”(Read View),上面写着“哪些人的修改还没做完”。

  3. 判断规则

    • 如果版本链上的修改者是已经提交的,老板就看那个版本。

    • 如果修改者还在忙(在清单里),老板就顺着“回滚指针”去找上一个更早的、已经提交的版本。

结果:你继续改你的,老板看他的历史版,互不打扰。


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

  1. trx_id < min_trx_id → 已提交,可见

  2. trx_id >= max_trx_id → 未开始,不可见

  3. 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 性能)

都会被占用,无法回收。