在数据库系统中,为了确保数据的正确性与一致性,引入了事务机制。一个事务代表着数据库执行的最小逻辑单元。
一、事务的四大特性(ACID)与实现原理
| 特性 | 描述 | MySQL 依赖机制 |
|---|---|---|
| 原子性 Atomicity | 事务中的操作要么全部执行成功,要么全部回滚 | Undo Log(回滚日志) |
| 一致性(目的) Consistency | 事务前后数据需保持一致 | 依赖原子性+隔离性+持久性共同保证 |
| 隔离性 Isolation | 并发事务之间互不影响 | 锁机制、MVCC |
| 持久性 Durability | 提交后的数据永久保存,即使系统宕机也不丢失 | Redo Log(重做日志) |
✔一致性是目的,其余三个是手段。
二、并发事务可能出现的问题
| 问题 | 含义 | 举例说明 |
|---|---|---|
| 脏读 Dirty Read | 读取了其他事务未提交的数据 | 余额被读到未提交的修改 |
| 不可重复读 Non-repeatable Read | 同一事务内两次读取同一行却得到不同结果 | 第二次读取时数据已被他人修改并提交 |
| 幻读 Phantom Read | 同条查询条件返回的记录数不一致 | 第一次无结果 → 第二次新增了符合条件的数据 |
脏读问题展示:读到了脏数据,事务1修改了以后没提交就被事务2读了,这时候事务1回滚了。

不可重复读问题展示(一个事务中两次读取):事务1查询得到1000,这时候事务1还没结束,但是事务2修改了balance,事务1又读了一次发现是800。

幻读问题展示:事务1去汇总整个表的用户余额得到3000,这时候还没结束结束,事务二给一个用户加了1000余额,事务1又查了一次发现变成了4000。

幻读问题展示(场景2):当事务1去查询某个表发现没这个记录,他就想插入记录,但是插入的时候发现这个记录又存在了(就好像出现了幻觉),原因就是中途事务2刚好插入了这条数据。
三、事务隔离级别与解决效果
首先明白一点:隔离级别就是对应就是解决并发事务出现的问题。

名词理解:
读未提交:一个事务还没提交时,它做的变更就能被别的事务看到。
读已提交:一个事务提交之后,它做的变更才会被其他事务看到。
可重复读:在一个事务中前后两次重复读取同一条数据,数据内容一致。
串行化:通过加锁的方式让整个过程串行起来。对于同一行记录,"写"会加"写锁","读"会加"读锁"。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。
表格总览------SQL 标准定义四种隔离级别:
| 隔离级别 | 简称 | 脏读 | 不可重复读 | 幻读 | 实现方式 | InnoDB 默认 |
|---|---|---|---|---|---|---|
| 读未提交 | RU | ❌ | ❌ | ❌ | 无 | 否 |
| 读已提交 | RC | ✔️ | ❌ | ❌ | MVCC | 否 |
| 可重复读 | RR | ✔️ | ✔️ | ❓标准存在,MySQL无 | MVCC + 间隙锁 | ✔ |
| 串行化 | Serializable | ✔️ | ✔️ | ✔️ | 加锁 | 否 |
MySQL InnoDB 在可重复读(RR)级别下,可通过 间隙锁(Gap Lock) 避免幻读。
四、什么是 MVCC(多版本并发控制)
MVCC 的核心目标是:
允许多事务并发读写,并尽量避免加锁阻塞,从而提高性能
其本质是在数据上维护多个版本,通过快照读来实现非阻塞读取。

简单来说就是:它是mysql在innodb存储引擎下实现rr和rc的一种手段,它通过维护行记录的多个不同的版本,通过链的形式连接起来,然后比如在select的时候通过MVCC机制去判断到底用哪一个版本。
具体实现主要分为三大部分:两个主要的隐藏字段,undo版本链,ReadView
MVCC 依赖的关键结构
| 名称 | 作用 |
|---|---|
行隐藏字段 trx_id |
表示版本由哪个事务创建 |
行隐藏字段 roll_pointer |
指向 undo log 中的上一个版本 |
| Undo Log(版本链) | 保存历史版本,实现回滚和多版本访问 |
| ReadView(读视图) | 决定事务能看到哪些版本 |
undo版本链示例(越下越旧)
cpp
最新版本 (trx_id = 20)
↓ roll_pointer
上一个版本 (trx_id = 18)
↓ roll_pointer
更旧版本 ...
五、ReadView(快照)可见性规则
事务读取数据时,根据版本的 trx_id 判断可见性:
| 判断条件 | 能否访问 | 说明 |
|---|---|---|
| trx_id == 当前事务ID | ✔ | 自己的版本可读 |
| trx_id < min_trx_id | ✔ | 版本创建事务已提交 |
| trx_id ≥ max_trx_id | ❌ | 版本在快照之后才开始创建 |
| min_trx_id ≤ trx_id < max_trx_id 且在 m_ids 中 | ❌ | 创建事务未提交 |
| min_trx_id ≤ trx_id < max_trx_id 且不在 m_ids 中 | ✔ | 创建事务已提交 |
不可见时则通过 roll_pointer 找到可读的历史版本。
最后:要么查不到(都不符合可见性条件),要么查到的就是该事务能看到的"最新合法版本"
六、RC 与 RR 的根本区别:ReadView 生成时机不同
| 隔离级别 | 生成策略 | 导致现象 |
|---|---|---|
| 读已提交(RC) | 每次查询都会生成新 ReadView | 数据可见版本会变化 → 会出现不可重复读 |
| 可重复读(RR) | 仅第一次查询创建 ReadView 并复用 | 快照固定 → 保证可重复读 |
一句话理解:
RR 固定快照,RC 不固定快照 → RC 更实时,RR 更一致性能
七、总结
| 控制手段 | 主要作用 | 局限性 |
|---|---|---|
| MVCC | 避免读写阻塞,性能好 | 单独无法完全杜绝幻读 |
| 间隙锁 | 解决幻读问题 | 降低并发性能 |
| 串行化 | 强一致性 | 性能最差 |
总结一句话
InnoDB 通过 MVCC + 锁机制 实现高并发下的事务隔离,RR 默认隔离级别结合间隙锁避免幻读,保证数据一致性。