MySQL 技术内幕:事务隔离级别和MVCC
2015-05-16
几点:
- 事务是什么?
- 事务的几个特性/要求:ACID?
- 隔离级别?
- MySQL中怎么支持事务的隔离级别的?
- MySQL中如何修改事务隔离级别?
事务是什么?
事务(Transaction),一组数据库操作,作为单个逻辑单元。
几个特性
事务具有4个属性:ACID,原子性、一致性、隔离性、持久性。
- 原子性(Atomicity):不可分割的逻辑单元,事务中的操作要么都做、要么都不做;
- 一致性(Consistency):事务必须使数据库从一个一致性状态变到另一个一致性状态。例如,一次转账的事务操作,要求A账户转出的钱,等于B账户转入的钱,实际上,一致性,是具体业务场景的约束;
- 隔离性(Isolation):事务相互独立,互不干扰;
- 持久性(Durability):事务一旦提交,数据库的改变就是永久的,断电、故障都不会有影响;
隔离级别
事务有隔离性(Isolation),并发事务间相互独立,互不干扰;几个要点:
- 基本目标:不同线程,发起并发事务,要实现不同线程操作数据时,不互相干扰;
- 相互干扰:脏读、不可重复读、幻读;
- 不同隔离级别,4种:
- 读取未提交;
- 读取已提交:
- 可重复读;
- 序列化;
- 本质:数据库操作CRUD的并发效率 vs. 数据安全性(数据一致)。
- 实现机制:锁
并发事务,相互干扰的现象
并发事务,相互干扰的几类现象:
- 脏读:一个事务读取了另一个事务未提交的更新数据。(A和B事务并发执行,A改后还未提交,B读,A又改并提交或者回滚)。
- 不可重复读:一个事务读到另一个事务已提交的更新数据(A和B事务并发执行,A先读,B再改,A再读)。
- 幻读:一个事务读到另一个事务已提交的新插入的数据(A和B事务并发执行,A先读,A再改还未提交,B改其他,A再查)。
- A把所有的“黑色”改为“白色”
- B插入一条新的数据“黑色”
- A再查询,还有“黑色”
并发事务,隔离级别
通过设定不同的隔离级别,来解决上述的脏读、不可重复读、幻读的现象,具体:
- 读取未提交(Read Uncommitted):可以看到其他事务没有提交的数据,出现脏读、不可重复读、幻读;
- 读取已提交(Read Committed):只能看到其他事务已经提交的数据,避免了脏读,但存在不可重复读、幻读;
- 可重复度(Repeatable Read):事务A读取数据之后,对涉及的数据加锁,不允许其他事务进行修改,由于其他事务会插入新的数据,因此,会产生幻读;
- 可序列化(Serializable):事务一个接一个的执行,完全相互独立;
Tips:
大部分数据库系统,默认隔离级别:读取已提交;MySQL默认,可重复读。
不同隔离级别对应的现象,见下表:
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读取未提交 | Y | Y | Y |
读取已提交 | N | Y | Y |
可重复读 | N | N | Y |
序列化 | N | N | N |
MySQL中隔离级别
隔离级别,用于表述并发事务之间的相互干扰程度,其基于锁机制进行并发控制。
1. 可序列化 Serializable
实现可序列化要求在选定对象
上的读锁和写锁保持直到事务结束后才能释放。在 SELECT 的查询中使用一个 WHERE 子句
来描述一个范围时应该获得一个“范围锁(range-locks)”。这种机制可以避免“幻读(phantom reads)”现象。
2. 可重复读 Repeatable read
该级别保证了同一个事务中多次读取同样的记录的结果是一致的。
对选定对象
的读锁(read locks)和写锁(write locks)一直保持到事务结束,但不要求“范围锁(range-locks)”,因此可能会发生“幻读(phantom reads)”。
幻读:是因为没有保持范围锁,该事务执行了一个 where 子句的范围查询后,其他事务可能新增了一条处于该事务 where 查询范围内的记录,那么该事务再次执行范围查询时就会看到这些新增的记录行(幻行,Phantom row)。
可重复读是 MySQL 的默认事务隔离级别。
3. 读取已提交 Read committed
DBMS需要对选定对象的写锁(write locks)一直保持到事务结束,但是读锁(read locks)在SELECT操作完成后马上释放(因此“不可重复读”现象可能会发生,见下面描述)。和前一种隔离级别一样,也不要求“范围锁(range-locks)”。
不可重复读是因为,事务只维持了选定对象的写锁,如果一些选定对象只涉及读锁,那么在读锁释放之后,其它事务可以对这些对象进行修改,该事务再次读取时就不一致了。例如,事务A对数据对象拥有写锁,事务B读取了数据对象,后事务A有对数据对象进行的更改并提交,事务B再次读取数据对象,两次读取结果不同。
大多数数据库的默认事务隔离级别都是这个。
4. 未提交读 Read uncommitted
也称为脏读
(dirty read)。
一个事务可以读取到其它事务未提交的更改。
注意:
- 不可重复读的重点是修改:同样的条件,读取过的数据,再次读取出来发现值不一样了。
- 幻读的重点在于新增或者删除:同样的条件,第 1 次和第 2 次读出来的记录数不一样。
MySQL中多版本并发控制(MVCC)
MVCC 整体思路
整体思路:
- 多版本并发控制(MVCC),来实现 MySQL 上的
多事务
并发访问
时,隔离级别控制; - 数据版本:并发事务执行时,同一行数据有多个版本
- 事务版本:每个事务都有一个事务版本
- 版本有序:版本是通过
时间
来标识的- 数据版本:包含
创建版本
(创建时间)、删除版本
(删除时间) - 事务版本:事务的
创建时间
,作为事务版本
- Note:数据版本,并不包含
更新版本
(更新时间),因为,在多事务并发情况下,更新操作,实际是:删除原数据
+新增新数据
- 数据版本:包含
通过 MVCC 实现一种效果:
同一时刻
,同一张表
,多个并发事务
,看到的数据是不同的:
事务的版本
数据的版本
MVCC 小结:
- MVCC,本质使用了
copy-on-write
(写时复制),为每个数据保留多份 snapshot; - 不同 snapshot 之间,使用
指针
连接成链表
; update
操作,能看到的 snapshot 是受限的,是链表上,小于等于
当前事务版本的最大版本
(读取已提交:离当前事务最近
的已提交
版本)update
操作,创建一个新的 snapshot,并使用事务版本,作为创建版本;
MVCC 细节
多版本并发控制(MVCC)的实现是通过保存数据在某个时间点的快照来实现的。根据事务的创建时间不同,多个并发事务对同一张表,同一时刻看到的数据可能是不一样的。
InnoDB 的 MVCC 是通过在每行记录后面保存两个隐藏的列来实现的:
- 行的创建时间;(创建版本)
- 行的过期时间(或删除时间);(删除版本)
存储的实际值是系统版本号(system version number)。
每开始一个新的事务,系统版本号都会递增。事务开始时刻的系统版本号作为事务的版本号,用来和查询到的每行记录的版本号进行比较。
在 可重复读
隔离级别下,MVCC 的具体操作:
- select:InnoDB 要查找当前存在的行,并且当前未被删除的行,
同时具体
两个条件:- 创建版本号早于当前事务的版本号
- 删除版本号晚于当前事务的版本号
- insert:InnoDB 新插入的行,创建版本号 = 当前事务的版本号
- delete:InnoDB 删除的行,删除版本号 = 当前事务的版本号
- update:InnoDB 实际插入新纪录,具体:
- 对于新纪录,创建版本号 = 当前事务的创建版本号
- 对于原纪录,删除版本号 = 当前事务的创建版本号
MVCC 历史数据清理
多版本并发控制(MVCC),只发生在:并发事务访问
的时候,同一行数据,可能存在多个版本。
那有个问题:
并发事务访问
结束后,什么时候清除历史版本
数据?
MySQL修改事务隔离级别
todo:
参考来源
原文地址:https://ningg.top/inside-mysql-transaction-and-mvcc/