从根儿上理解MySQL | 事务的隔离级别与MVCC
目录
事务简介
事务的概念
- 事务的四个特性
- 原子性:事务包含的所有操作要么全做,要么全不做。
- 一致性:使数据库从一个一致性状态变换到另一个一致性状态(符合所有现实世界中的约束)。
- 隔离性:当多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰。
- 持久性:事务一旦被提交,对数据库中的数据的改变就是永久性的。
- 事务的状态
- 活动的:事务对应的数据库操作正在执行过程中。
- 部分提交的:当事务中的最后一个操作执行完成,但由于操作都在内存中执行,所造成的影响并没有刷新到磁盘。
- 失败的:当事务处在
活动的
或者部分提交的
状态时,可能遇到了某些错误而无法继续执行,或者人为停止当前事务的执行。 - 中止的:回滚执行完成。
- 提交的:处在
部分提交的
状态的事务将修改过的数据都同步到了磁盘上。
从上图可以看出,只有当事务处于提交的或者中止的状态时,一个事务的生命周期才算是结束了。
MySQL中事务的语法
- 开启事务
有两种语句可以开启事务:BEGIN [WORK]; 或者 START TRANSACTION;两者有着相同的功效,但START TRANSACTION
语句后边可以跟随几个修饰符:
START TRANSACTION READ ONLY; --标识当前事务是一个只读事务
START TRANSACTION READ WRITE; --标识当前事务是一个读写事务,默认
START TRANSACTION WITH CONSISTENT SNAPSHOT; --启动一致性读
START TRANSACTION READ ONLY, WITH CONSISTENT SNAPSHOT; --跟随多个修饰符,用逗号隔开
- 提交事务
COMMIT [WORK];
- 手动中止事务
如果我们写了几条语句之后发现上边的某条语句写错了,我们可以手动的使用下边这个语句来将数据库恢复到事务执行之前的样子,
ROLLBACK [WORK];
- 保存点
保存点就是在事务对应的数据库语句中打几个点,我们在调用ROLLBACK
语句时可以指定会滚到哪个点,而不是回到最初的原点。
SAVEPOINT 保存点名称;
当我们想回滚到某个保存点时,可以使用下边这个语句:
ROLLBACK [WORK] TO [SAVEPOINT] 保存点名称;
事务的隔离级别
事务并发执行遇到的问题
- 脏写:一个事务修改了另一个未提交事务修改过的数据;
- 脏读:一个事务读到了另一个未提交事务修改过的数据;
- 不可重复读:一个事务只能读到另一个已经提交的事务修改过的数据,并且其他事务每对该数据进行一次修改并提交后,该事务都能查询得到最新值;
- 幻读:一个事务先根据某些条件查询出一些记录,之后另一个事务又向表中插入了符合这些条件的记录,原先的事务再次按照该条件查询时,能把另一个事务插入的记录也读出来。
MySQL中支持的四种隔离级别
在SQL标准中有四种隔离级别:
- 未提交读:可能发生脏读、不可重复读、幻读问题;
- 提交读:解决了脏读问题,可能发生不可重复读、幻读问题;
- 可重复读:解决了脏读、不可重复读问题,可能发生幻读问题;
- 串行化:解决了脏读、不可重复读、幻读问题;
MySQL
虽然支持4种隔离级别,但与SQL标准
中所规定的各级隔离级别允许发生的问题却有些出入,MySQL在REPEATABLE READ隔离级别下,是可以禁止幻读问题的发生的。MySQL
的默认隔离级别为REPEATABLE READ
,我们也可以手动修改事务的隔离级别:
SET [GLOBAL|SESSION] TRANSACTION ISOLATION LEVEL level;
--level: { REPEATABLE READ | READ COMMITTED | READ UNCOMMITTED | SERIALIZABLE}
注:GLOBAL
关键字表示只对执行完该语句之后产生的会话起作用,对当前已经存在的会话无效;而SESSION
关键字对当前会话的所有后续的事务有效;如果两个关键字都不用,只对当前会话中下一个即将开启的事务有效,注意该语句不能在已经开启的事务中间执行,否则会报错。
MVCC原理
版本链
对于使用InnoDB
存储引擎的表来说,它的聚簇索引记录中都包含两个必要的隐藏列:
-
trx_id
:每次一个事务对某条聚簇索引记录进行改动时,都会把该事务的事务id
赋值给trx_id
隐藏列。 -
roll_pointer
:每次对某条聚簇索引记录进行改动时,都会把旧的版本写入到undo日志
中,然后这个隐藏列就相当于一个指针,可以通过它来找到该记录修改前的信息。
对某一记录的每一次更新,都会将旧值放到一条undo日志
中,随着更新次数的增多,所有的版本都会被roll_pointer
属性连接成一个链表,这个链表就是版本链
,而版本链的头节点就是当前记录最新的值。
ReadView
在READ COMMITTED
和REPEATABLE READ
两种隔离级别下,为了判断版本链中的哪个版本是当前事务可见的,提出了ReadView的概念,它主要包含4个比较重要的内容:
- m_ids:表示在生成ReadView时当前系统中活跃的读写事务的事务id列表;
- min_trx_id:表示在生成ReadView时当前系统中活跃的读写事务中最小的事务id,也就是m_ids中的最小值;
- max_trx_id:表示生成ReadView时系统中应该分配给下一个事务的id值;
- creator_trx_id:表示生成该ReadView的事务的事务id。
有了这个ReadView,这样在访问某条记录时,如果被访问版本的trx_id
属性值与ReadView
中的creator_trx_id
值相同,意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问;如果被访问版本的trx_id
属性值在ReadView的[
min_trx_id,max_trx_id)区间且
在m_ids
列表中,说明说明创建ReadView
时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建ReadView
时生成该版本的事务已经被提交,该版本可以被访问。如果某个版本的数据对当前事务不可见,就顺着版本链找到下一个版本的数据,直到版本链中的最后一个版本。
MVCC小结
MVCC
(Multi-Version Concurrency Control ,多版本并发控制)指的就是在使用READ COMMITTD
、REPEATABLE READ
这两种隔离级别的事务在执行普通的SELECT
操作时访问记录的版本链的过程,这样子可以使不同事务的读-写
、写-读
操作并发执行,从而提升系统性能。READ COMMITTD
、REPEATABLE READ
这两个隔离级别的一个很大不同就是:生成ReadView的时机不同,READ COMMITTD在每一次进行普通SELECT操作前都会生成一个ReadView,而REPEATABLE READ只在第一次进行普通SELECT操作前生成一个ReadView,之后的查询操作都重复使用这个ReadView就好了。
声明:本博客纯粹为读书笔记,如想详细了解MySQL相关知识请访问《MySQL是怎么运行的:从根儿上理解MySQL》原作者撰写资料