持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第13天,点击查看活动详情
SQL92 标准
www.contrib.andrew.cmu.edu/~shadow/sql…
第一个隔离级别叫做:Read Uncommitted(未提交读),一个事务可以读取到其他事务未提交的数据,会出现脏读,所以叫做RU,它没有解决任何的问题。
第二个隔离级别叫做:Read Committed(已提交读),也就是一个事务只能读取到其他事务已提交的数据,不能读取到其他事务未提交的数据,它解决了脏读的问题,但是会出现不可重复读的问题。
第三个隔离级别叫做:Repeatable Read (可重复读),它解决了不可重复读的问题,也就是在同一个事务里面多次读取同样的数据结果是一样的,但是在这个级别下,没有定义解决幻读的问题。
最后一个就是:Serializable(串行化),在这个隔离级别里面,所有的事务都是串 行执行的,也就是对数据的操作需要排队,已经不存在事务的并发操作了,所以它解决了所有的问题
MySQL InnoDB 对隔离级别的支持
在MySQL InnoDB里面,不需要使用串行化的隔离级别去解决所有问题。那我们来看一下 MySQL InnoDB里面对数据库事务隔离级别的支持程度是什么样的。
| 事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| 未提交读(Read Uncommitted) | 可能 | 可能 | 可能 |
| 已提交读(Read Committed) | 可能 | 可能 | |
| 可重复读(Repeatable Read) | |||
| 串行化(Serializable) |
两大实现方案
LBCC
第一种,读取数据的时候,锁定我要操作的数据,不允许其他的事务修改就行了。这种方案我们叫做基于锁的并发控制Lock Based Concurrency Control(LBCC)。
MVCC
在修改数据的时候给它建立一个备份或者叫快照,后面再来读取这个快照就行了。这种方案我们叫做多版本的并发控制Multi Version Concurrency Control(MVCC)。MVCC的核心思想是:我可以查到在我这个事务开始之前已经存在的已提交的数据,即使它在后面被修改或者删除了。在我这个事务之后新增的数据,我是查不到的。
InnoDB 为每行记录都实现了两个隐藏字段(还加上一个ROWID):
-
DB_TRX_ID,6字节:
插入或更新行的最后一个事务的事务ID,事务编号是自动递增的(我们把它理解为创建版本号,在数据新增或者修改为新数据的时候,记录当前事务 ID)。 -
DB_ROLL_PTR,7字节:回滚指针(我们把它理解为删除版本号,数据被删除或记录为旧数据的时候,记录当前事务ID)。
我们把这两个事务ID理解为版本号。
下面我们用一种简化的模型来理解一下 MVCC。
第一个事务,初始化数据(检查初始数据)
| Transaction 1 |
|---|
| begin; |
| insert into mvcctest values(NULL,'qingshan') ; |
| insert into mvcctest values(NULL,'jack') ; |
| commit; |
此时的数据,创建版本是当前事务ID,删除版本为空:
| id | name | 创建版本 | 删除版本 |
|---|---|---|---|
| 1 | xiaoming | 1 | undefined |
| 2 | xiaofang | 1 | undefined |
第二个事务,执行第1次查询,读取到两条原始数据,这个时候事务ID是2:
| Transaction 2 |
|---|
| begin; |
| select * from mvcctest ; -- (1) 第一次查询 |
第三个事务,插入数据:
| Transaction 3 |
|---|
| begin; |
| insert into mvcctest values(NULL,'xiaoli') ; |
| commit; |
此时的数据,多了一条xiaoli,它的创建版本号是当前事务编号,3:
| id | name | 创建版本 | 删除版本 |
|---|---|---|---|
| 1 | xiaoming | 1 | undefined |
| 2 | xiaofang | 1 | undefined |
| 3 | xiaoli | undefined |
第二个事务,执行第 2次查询:
| Transaction 2 |
|---|
| select * from mvcctest ; (2) 第二次查询 |
MVCC 的查找规则:只能查找创建时间小于等于当前事务ID的数据,和删除时间大于当前事务ID的行(或未删除)。
xiaoli 的创建ID大于2,所以还是只能查到两条数据。
第四个事务,删除数据,删除了 id=2 xiaofang 这条记录:
| Transaction 4 |
|---|
| begin; |
| delete from mvcctest where id=2; |
| commit; |
此时的数据,xiaofang的删除版本被记录为当前事务ID,4,其他数据不变:
| id | name | 创建版本 | 删除版本 |
|---|---|---|---|
| 1 | qingshan | 1 | undefined |
| 2 | jack | 1 | |
| 3 | tom | 3 | undefined |
在第二个事务中,执行第 3次查询:
| Transaction 2 |
|---|
| select * from mvcctest ; (3) 第三次查询 |
查找规则:只能查找创建时间小于等于当前事务ID的数据,和删除时间大于当前事务 ID的行(或未删除)。
也就是,在我事务开始之后删除的数据,所以 xiaofang 依然可以查出来。所以还是这两条数据。
第五个事务,执行更新操作,这个事务事务ID是 5:
| Transaction 4 |
|---|
| begin; |
| update mvcctest set name ='yidashi' where id=1; |
| commit; |
此时的数据,更新数据的时候,旧数据的删除版本被记录为当前事务ID 5(undo),产生了一条新数据,创建ID为当前事务ID 5:
| id | name | 创建版本 | 删除版本 |
|---|---|---|---|
| 1 | xiaoming | 1 | |
| 2 | xiaofang | 1 | 4 |
| 3 | xiaoli | 3 | undefined |
| 1 | yidashi | undefined |
第二个事务,执行第 4次查询:
| Transaction 2 |
|---|
| select * from mvcctest ; (4) 第三次查询 |
查找规则:只能查找创建时间小于等于当前事务ID的数据,和删除时间大于当前事务ID的行(或未删除)。
因为更新后的数据 yidashi 创建版本大于2,代表是在事务之后增加的,查不出来。而旧数据xiaoming 的删除版本大于2,代表是在事务之后删除的,可以查出来。
通过以上演示我们能看到,通过版本号的控制,无论其他事务是插入、修改、删除,第一个事务查询到的数据都没有变化。