InnoDB存储引擎的事务隔离级别以及实现方法
参考文献:https://zhuanlan.zhihu.com/p/117476959
书:MySQL技术内幕:InnoDB存储引擎
1.什么是锁
锁机制用于管理对于共享资源的并发访问。在InnoDB中,分为latch和lock两种:
latch是轻量级的锁,要求的锁定时间比较短。latch可以分为两类,分别是:
- mutex互斥量
- rwlock读写锁
他们是用于保证并发线程操作临界资源(内存数据结构)的正确性,没有死锁检测和处理机制,仅通过应用程序加锁的顺序保证无死锁的情况发生。
lock是事务锁定数据库内容的锁,在粒度上分为行级和表级锁。其中,粒度是指如果将上锁对象看成一棵树,对最下层的对象上锁,也就是对粒度最细的对象进行上锁,那么首先需要对粗粒度的对象进行上锁。InnoDB支持多粒度锁定,可以同时加行级锁和表级锁。
行级锁从锁定范围来划分有三种:
- 记录锁**(Record Lock)**:属于单个行记录上的锁。
- 间隙锁**(Gap Lock)**:锁定一个范围,不包括记录本身。区间里具体的索引记录,不存在的空闲空间(可以是两个索引记录之间,也可能是第一个索引记录之前或最后一个索引记录之后的空间)。
- 临界锁**(Next-Key Lock)**:Record Lock+Gap Lock,锁定一个范围,包含记录本身。
行锁从锁定权限来划分有两种:
- S Lock 共享锁(Share Lock)允许事务读取一行数据,允许多个事务同时获取(锁兼容)。
- X Lock 排他锁(Exclusive Lock)允许事务删除或者更新一行数据,不允许多个事务同时获取。如果一个记录已经被加了排他锁,那其他事务不能再对这条事务加任何类型的锁(锁不兼容)。
表级锁中除了共享锁排他锁外,还有IS Lock 意向共享锁, IX Lock 意向排他锁这两种划分。Intention Lock意向锁:为了在一个事务中揭示下一行将被请求的锁类型。
- IS Lock:事务想要获得一张表中某几行的共享锁,加共享锁之前必须获得表的IS锁。
- IX Lock:事务想要获得一张表中某几行的排他锁,加锁之前必须获得表的IX锁。
在mysql的默认隔离级别,非事务的情况下,select不加任何锁,update隐式加排他锁。
2.什么是MVCC
对一份数据会存储多个版本,通过事务的可见性来保证事务能看到自己应该看到的版本。通常会有一个全局的版本分配器来为每一行数据设置版本号,版本号是唯一的。
3.MySql事务
InnoDB支持事务,数据库事务指的是一组数据操作,把数据库从一种状态转换为另一种状态,事务内的操作要么就是全部成功,要么就是全部失败。
在InnoDB存储引擎中,其事务隔离级别SERIALIZABLE事务完全遵循和满足ACID特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。其他隔离级别为了性能,在acid的某些方面进行了妥协。
事务的ACID特性分别为以下四点:
- 原子性(Atomicity):保证事务中的所有操作要么全部成功,要么全部失败,不会留下中间状态。
- 一致性(Consistency):确保事务的执行结果使数据库从一个一致的状态转移到另一个一致的状态。
- 隔离性(Isolation):确保并发执行的事务之间不会互相干扰,每个事务都像是在一个独立的操作环境中执行。
- 持久性(Durability):一旦事务提交,它对数据库的改变就是永久性的,即使系统故障也不会丢失。
总的来说,对于acid特性,AID是手段,而C是目的。
4.并发事务带来的问题
脏读(dirty read)
一个事务A读取记录并进行了修改,这个修改在未提交时对于其他事务B可见。事务B读取了该修改,事务A进行了回滚,导致修改未提交到数据库,事务B读取到的就是脏数据。
丢失修改(lost to modify)
事务 1 读取某表中的数据 A=20,事务 2 也读取 A=20,事务 1 先修改 A=A-1,事务 2 后来也修改 A=A-1,最终结果 A=19,事务 1 的修改被丢失。
不可重复读(unrepeatable read)
一个事务中对于同一条记录的两次读取结果不同。
幻读(phantom read)
一个事务中多次执行同一条查询语句发现查询的记录数量增加。
5.并发事务控制方式
锁(lock)和多版本并发控制(MVCC,Multiversion concurrency control),分别可以看做悲观模式和乐观模式。
6.隔离级别
隔离级别主要关注隔离性问题。
- READ-UNCOMMITTED(读取未提交) :最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
- READ-COMMITTED(读取已提交) :允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
- REPEATABLE-READ(可重复读) :对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
- SERIALIZABLE(可串行化) :最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
查看mysql的隔离级别
1 | -- 会话级别 |
修改mysql的隔离级别
1 | SET GLOBAL transaction_isolation = '你想要的隔离级别'; |
SERIALIZABLE 隔离级别是通过锁来实现的,READ-COMMITTED 和 REPEATABLE-READ 隔离级别是基于 MVCC 实现的。不过, SERIALIZABLE 之外的其他隔离级别可能也需要用到锁机制,就比如 REPEATABLE-READ 在当前读情况下需要使用加锁读来保证不会出现幻读。
读未提交:不加锁,性能最好
串行化:读的时候加共享锁,也就是其他事务可以并发读,但是不能写。写的时候加排它锁,其他事务不能并发写也不能并发读。
避免脏读
一个事务只能读到其他事务已经提交过的数据,也就是其他事务调用 commit 命令之后的数据。
实现可重复读
为了解决不可重复读,或者为了实现可重复读,MySQL 采用了 MVCC (多版本并发控制) 的方式。
row trx_id :我们在数据库表中看到的一行记录可能实际上有多个版本,每个版本的记录除了有数据本身外,还要有一个表示版本的字段,记为 row trx_id,而这个字段就是使其产生的事务的 id,事务 ID 记为 transaction id,它在事务开始的时候向事务系统申请,按时间先后顺序递增。
快照:一致性视图,这也是可重复读和不可重复读的关键,可重复读是在事务开始的时候生成一个当前事务全局性的快照,而读提交则是每次执行语句的时候都重新生成一次快照。
对于一个快照来说,它能够读到那些版本数据,要遵循以下规则:
- 当前事务内的更新,可以读到;
- 版本未提交,不能读到;
- 版本已提交,但是却在快照创建后提交的,不能读到;
- 版本已提交,且是在快照创建前提交的,可以读到;
解决幻读
mysql系统默认是可重复读级别,会有幻读的可能性(实际上mysql已经在该隔离级别下解决了幻读问题):行锁+间隙锁-》next-key锁
假设表中有两条数据并且有索引,那么一个事务update的时候会在该记录上加上next key锁,导致本身和前后区间上锁,防止其他事务插入新的数据。

