从头开始搞懂 MySQL(03)事务隔离性
Posted 一起来搬砖呀
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从头开始搞懂 MySQL(03)事务隔离性相关的知识,希望对你有一定的参考价值。
1、什么是事务?
提到事务,我们肯定不陌生,和数据库打交道的时候,我们肯定会用到事物
经典案例就是转账,小孙要给小白转 100 块钱,现在小孙的卡里只有 100 块钱。转账过程具体到程序里会有一系列操作,查询余额、扣除 100、更新余额等,这些操作必须是一体的,不然在查询余额后,还没有扣除之前,借着这个时间差再查一次,然后再给另外一个朋友转账。这个时候就要用到事物这个概念了
事物就是要保证一组数据库操作,要么全部成功,要么全部失败,在 mysql 中,事物支持是在引擎层面实现的,并不是所有的引擎都支持事务,MyISAMy 引擎就不支持事务,我们常用的 InnoDB 支持事务,下面我们也会以 InnoDB 为例来加深我们对 MySQL 事务的理解
2、事务隔离性与隔离级别
提到事务,我们首先会想到事务的 ACID(Atomicty 原子性、 Consistency 一致性、 Isolation 隔离性、 Durability 持久性)
现在让我们来看看其中的 I,也就是
隔离性
2.1 为什么要有隔离级别
数据库有多个事务同时执行的时候,就可能出现脏读(dirty read)、幻读(phantom read)、不可重复读(non-repeatable read)的问题,为了解决这些问题,就有了隔离级别的概念
2.2 事务隔离级别
SQL 标准的事务隔离级别包括
读未提交(read uncommitted)
、读提交(read committed)
、可重复读(repeatable read)
、串行化(serializable)
- 读未提交:一个事务还没有提交时,它做的变更就能被别的事务看到
- 读提交:一个事务提交后,它做的变更才能被其他事务看到
- 可重复读:一个事务执行过程中看到的数据,总是跟这个事务启动时看到的数据是一致的,未提交变更对其他事务也是不可见的
- 串行化:对同一行记录,写会加写锁,读会加读锁,如果出现读写锁冲突,后访问的事务需要等待前一个事务执行完成后执行
2.3 不同隔离级别事务示例
首先有一张表 test,其中有一列 ID 的值为 1,按照下面时间顺序执行两个事务:
我们来看看在不同隔离级别下,事务 A 查询得到的值 V1、V2、V3 的返回值分别是什么
在实现上,使用 InnoDB 引擎数据库里面会创建视图,在 MVCC 中用到,在读提交和可重复读隔离级别下,SQL 访问的时候以视图的逻辑结果为准
读未提交
读未提交(read uncommitted)隔离级别下,会直接返回记录上的最新值,不会创建视图
事务 B 还未提交,但是结果已经被事务 A 看到了,所以 V1、V2、V3 的值都是 2
读提交
读提交(read committed)隔离级别下,视图在每个 SQL 执行的时候创建
V1 的值为 1,事务 B 提交之后事务 A 才看到,所以 V2、V3 的值为 2
可重复读
可重复读(repeatable read)隔离级别下,视图在事务启动的时候创建,事务存在期间都使用的同一张视图
事务在执行期间看到的值都是一致的,所以 V1、V2 的值都是 1,V3 的值为 2
串行化
串行化(serializable)隔离级别下,直接采用加锁的方式来防止并行访问
事务 B 在执行将值修改为 2 的时候,会被锁住,直到事务 A 提交后,事务 B 才能继续执行,所以 V1、V2 的值为 1,V3 的值为 2
配置事务隔离级别
查看当前数据库事务隔离级别 SQL 如下:
show variables like ‘transaction_isolation’;
2.4 事务隔离的原理
在 MySQL 中,每条记录在更新的时候都会在 undo log 里生成一条回滚操作,记录上的最新值,通过回滚操作,都可以得到前一个状态的值。
在 InnoDB 引擎中,每个事务都有自己唯一的事务 ID,transaction id
,事务开始的时候向系统申请,每次事务更新都会有一个新的版本,每个版本有不同的事务 ID。如下图所示:
这是一条数据的多个版本,最新的版本是 V2,图中的 undo log 1 对应的就是回滚日志,里面的 transaction id 的值都是通过 undo log 计算出来的。
在可重复读的事务隔离级别中,每个事务启动的时候都只能看到已提交的事务,并且在事务执行过程中不能读取其他事务的更新操作。在 InnoDB 中,每个事务都有一组当前事务 ID 的快照,记录下来当前事务开启时,正在执行的事务 ID 的集合。
如果新增加一个事务,可能会出现以下几种情况:
- 数据版本在事务开始前就已经提交了,当前版本对新增事务是可见的
- 数据版本在事务开始后变更,当前版本对新增事务不可见
- 数据版本的 transaction id 在数组中,是正在执行的事务,不可见
- 数据版本的 transaction id 不在数组中,事务已提交,可见
2.5 MVCC 事务并发
2.5.1 快照读
查询创建的是版本小于等于当前事务的版本号,保证在读取之前记录存在,读取的是快照版本
2.5.2 当前读
-
插入将当前事务的版本号保存到行的创建版本号,读取的是当前最新版本
-
修改重新插入一行,用当前事务的版本号作为新的版本号,将原来记录行的删除版本号设置为当前事务版本号,读取当前最新版本
-
删除将当前事务的版本号保存到行的删除版本号,读取当前最新版本
2.5.3 MVCC 多事务并发操作
数据表 test 中有一个字段 num 值为 100
如果有多个事务并发,事务 A(事务ID = 1)、B(事务 ID = 2)、C(事务 ID = 3)
查询:
- 事务 A(事务 ID = 1) 开始前,系统中只有已提交的事务,事务 A 查询字段 num
- 系统中总共有三个事务,事务 B 做更新操作 num = num + 1
- 事务 A 从可重复读的角度来看,事务 B 和事务 C 的版本 2、3 都不可见,所以查询结果 num = 100
更新:
- 事务 B 先开启事务进行更新操作 num = num + 1,事务 ID 集合中不包含 事务 C
- 事务 C 也进行更新操作 num = num + 1,并且在事务 B 之前进行提交
- 事务 B 的执行过程中,有事务 C 的提交流程,如果事务 B 进行回滚的话不能直接按照 undo log 回滚为 100,因为更新时为当前读,也就是最新的数据,事务 C 先提交后,事务 B 读取的是更新后的最新的数据 101,所以最后获取 num 的值的话应该是 101 + 1 = 102。
3、事务启动方式
显示启动事务
使用 begin
或者 start transaction
启动,使用 commit
进行提交,使用 rollback
进行回滚
关闭自动提交
使用 set autocommit = 0
关闭事务的自动提交,需要主动执行 commit
提交事务或者 rollback
进行回滚
为了避免长事务,最好使用 set autocommit = 1
显示语句的方式来启动事务
长事务查询
查询时间超过 60 秒的事务:
SELECT * FROM information_schema.innodb_trx where TIME_TO_SEC(timediff(now(), trx_started)) > 60;
以上是关于从头开始搞懂 MySQL(03)事务隔离性的主要内容,如果未能解决你的问题,请参考以下文章