03.事务隔离级别
事务:
- 事务:就是要保证一组数据库操作,要么全部成功,要么全部失败
- 事务的特性:ACID,原子性、一致性、隔离性、持久性
- mysql事务是由引擎提供的支持,MyISAM 引擎就不支持事务
- 多事务同时执行可能会出现的问题:脏写、脏读、不可重复读、幻读
- 脏读:在一个事务内读到了另一个未提交事务修改过的数据
- 不可重复读:在一个事务内多次读取同一个
数据
,由于其他事务的提交,导致出现前后两次读到的数据
不一样的情况 - 幻读:在一个事务内多次查询某个符合查询条件的
记录数量
,由于其他事务的提交,出现前后两次查询到的记录数量
不一样的情况
事务的状态机:

事务的隔离级别:
- 读未提交(read uncommitted):一个事务还没提交时,它做的变更能被别的事务看到
- 读提交(read committed):一个事务提交之后,它做的变更才会被其他事务看到
- 可重复读(repeatable read):一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。在可重复读隔离级别下,未提交的变更对其他事务是不可见的。
- 串行化(serializable):对于同一行记录“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行
- InnoDB引擎的默认隔离级别是可重复读,但无法
完全避免
幻读 针对幻读的的避免措施- 快照读:通过MVCC机制,创建一致性视图,确保其他事务不影响当前事务的记录数量
- 当前读:通过next-key lock(记录锁+间隙锁),阻塞插入操作
关于隔离级别的例子:
1.
若隔离级别是“读未提交”,V1=2,V2=V3=2。事务B虽然还没有提交,但是结果已经被事务A看到了。
2. 若隔离级别是“读提交”,V1=1,V2=V3=2。事务B的更新在提交后才能被A看到。
3. 若隔离级别是“可重复读”,V1=V2=1,V3=2。之所以 V2 还是
1,遵循的就是这个要求:事务在执行期间看到的数据前后必须是一致的。 4.
若隔离级别是“串行化”,则在事务 B 执行“将 1 改成
2”的时候,会被锁住(A先获得锁)。直到事务A提交后,事务B才可以继续执行。所以从A的角度看,
V1、V2=1,V3=2
Undo log:
举个例子: 1.
实际上每条记录在更新的时候都会同时记录一条回滚操作。记录上的最新值,通过回滚操作,都可以得到前一个状态的值.
2. 在视图 A、B、C 里面,这一个记录的值分别是 1、2、4。 3.
视图A下,视图C将值修改为4,此时视图A通过视图C添加的回滚操作,逐个回滚即可得到试图A下对应的值1
事务的启动方式:
- 显式启动事务语句:
- 启动事务:
begin
、start transaction
- 提交语句:
commit
; - 回滚语句:
rollback
> [!TIP]begin
、start transaction
命令并不是一个事务的起点,在执行到它们之后的第一个select
语句,事务才真正启动。
- 启动事务:
- 关闭自动提交:set autocommit = 0
- 作用:关闭当前线程的自动提交功能
- 影响:开启后,select 语句需要手动提交/回退
- 周期:该事务持续存在直到主动执行
commit
、rollback
,或者断开连接
一致性视图:
- 作用:定义事务执行期间用来定义“我能看到什么数据”
- 生效范围:
- 对
SELECT
语句查询结果产生影响 - RU 没有视图,
SELECT
直接返回记录最新值 - RC 在每个
SELECT
语句开始执行的时候创建的 - RR
在事务启动时随后的第一个
SELECT
语句时创建,整个事务存在期间都用这个视图 - Serial 没有视图,通过锁实现数据访问
- 对
- 一致性视图创建的两个时机:
begin
/start transaction
开启的事务:- RC下,每个
select
都会创建 - RR下,执行第一个快照读语句
select
时创建
- RC下,每个
start transaction with consistent snapshot
创建一致性快照:- 执行该事务语句后立即创建
- 忽视当前隔离级别的限制
mysql是如何实现的多版本的一致性读MVCC:
概念:
- MVCC:通过「版本链」来控制并发事务访问同一个记录时的行为就叫 MVCC(多版本并发控制)
transaction id
: InnoDB 里面每个事务有一个唯一的事务 ID。在事务开始的时候(实际是在发生增删改时才申请),向InnoDB的事务系统申请的,按申请顺序严格递增的。row trx_id
:每次事务更新数据
会生成一个新的数据版本,并且把事务的transaction id
记录到这个数据版本row的事务ID列,记为row trx_id
- 多版本:数据表中的一行记录,其实可能有多个版本且每个版本有自己的
row trx_id
,多个版本的记录通过roll_pointer连接,形成链式结构,通过undo log可以访问 - 在一个事务中,如果事务本身没有修改数据结果,那么不论在什么时候查询,看到这行数据的结果都是一致的。
如何查找当前事务的可读版本:
思路:一致性视图 + row不同版本之前的顺序关系(undo log),通过undo log,获取row的trx_id,判断对应的row是否可读,不可用则继续执行undo,直到找到当前的可读row trx_id
具体规则:
- InnoDB
为每个事务构造了一个数组m_ids,用来保存这个事务启动瞬间,当前
活跃状态
的所有事务ID。(“活跃”是指:启动了但还没ids - 活跃事务数组里面事务ID的最小值记为低水位min_trx_id
- 当前系统里面已经创建过的事务ID的最大值加1记为高水位max_trx_id
- 视图数组和高水位,就组成了当前事务的一致性视图(read-view)如下:
- InnoDB
为每个事务构造了一个数组m_ids,用来保存这个事务启动瞬间,当前
结合undo log 如下:
通过一致性视图找可读版本的解读:
- 根据版本链/undo log,从当前row的最大的trx_id逐个往前找
- 如果trx_id >
max_trx_id,则在
未提交事务区域
,则不可见;根据版本链找到下一个trx_id - 如果 min_trx_id < trx_id <
max_trx_id,再额外判断trx_id是否在m_ids内
- 如果在,则当前找到的trx_id,对应的事务是活跃事务还未提交,不可见。根据版本链找到下一个trx_id
- 如果不在,则当前事务的trx_id已经提交,可见
- 如果 min_trx_id >
trx_id,则在
已提交事务区域
,可见
可重复读下(RR),事务可见版本结论:
- 对于未提交的版本,不可见;
- 对于已提交的版本,如果是在视图创建后提交的,不可见;是在视图创建前提交的可见。
- 对于自己的更新的版本总是可见;
- 可见版本是找到上述版本中最大的trx_id的那个
读提交下(RC), 事务可见版本结论:
- 每一个语句执行前都会重新算出一个新的视图。所有读都是
当前读
(即读取当前已提交的最新版本)
- 每一个语句执行前都会重新算出一个新的视图。所有读都是
对于更新逻辑:
- 一致性读不适用更新逻辑(
update语句
)。更新逻辑都是先读后写的,读当前的值,称为当前读
(current read)。 - 在同一个事务内的记录
UPDATE
更新,使用当前读
,会被后续的SELECT查到 - 强制
select 语句
使用当前读的两种方式:
(S锁 共享锁): select * from t where id=1 lock in share mode (X锁 排他锁): select * from t where id=1 for update - 一致性读不适用更新逻辑(
当前读
: 读取当前最新的记录。update、insert、delete语句、加锁的select语句快照读
: 读取可见的快照的记录。仅普通select语句一致性读
:当一个事务进行一致性读时,它实际上是在读取这个事务的一致性视图next-key lock
:间隙锁+记录锁,解决RR隔离级别
下,使用当前读
时的幻读问题- select * from x where id > 2 for update;
- 事务A会加上一个(2,+∞]的next-key lock,阻塞其他事务向此区间写入数据
- 事务B会申请一个插入意向锁,等待A提交后再执行
插入意向锁:
无法避免的幻读:
- 事务A先使用快照读,事务B插入/删除记录,事务A使用当前读,则会发生幻读
- 从业务上避免的措施: 尽量在开启事务之后,马上执行 select … for update 这类当前读的语句,对记录加 next-key lock,从而避免其他事务插入一条新记录
- 事务A先使用快照读,事务B插入/删除记录,事务A使用当前读,则会发生幻读
本文作者:navyum
本文链接:https://www.cnblogs.com/navyum/p/18509415
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步