文章目录
- 事务概述
- 事务基本概念
- 事务四大特性(ACID)
- 演示MySQL事务
- 手动开启事务
- MySQL默认事务机制
- 事务的隔离级别
- 隔离级别基本概述
- 三种现象
- 脏读
- 不可重复读
- 幻读
- 查看和设置隔离级别
- 四种隔离级别及演示
- 读未提交(read uncommitted)
- 读提交(read committed)
- 可重复读(repeatable read)
- 串行化(serializable)
- 可重复读的幻读问题
事务概述
事务基本概念
- 事务是最小的一个工作单元, 在数据库当中, 事务表示一件完整的事
- 一个业务的完成可能需要多条DML语句共同配合才能完成, 例如转账业务, 需要执行两条DML语句, 先更新张三的业务, 再更新李四的业务, 为了保证转账业务不出现问题, 就必须保证这两条DML语句必须同时成功或者同时失败, 所以我们就需要借助业务这一机制来完成
- 也就是说使用了事务机制之后, 在同一个事务内部, 多条DML语句会同时成功或者同时失败, 不会出现一部分成功一部分失败的现象
- 事务只针对DML语句(insert, delete, update)有效, 因为只有这三个语句是改变表中数据的
- 事务只针对DML语句有效, 但是在一个事务内部是可以有DQL语句进行结果查询的
事务四大特性(ACID)
- 原子性(Atomicity)
是指事务包含的所有操作要么全部成功,要么同时失败 - 一致性(Consistency)
是指事务开始前,和事务完成后,数据应该是一致的。例如张三和李四的钱加起来是5000,中间不管进行过多少次的转账 操作(update),总量5000是不会变的。这就是事务的一致性。 - 隔离性(Isolation)
隔离性是当多个⽤户并发访问数据库时,⽐如操作同⼀张表时,数据库为每⼀个⽤户开启的事务,不能被其他事务的操作所⼲扰,多个并发事务之间要相互隔离, 隔离性主要解决的是多个事务并发的情况, 这在下面的内容里很重要 - 持久性(Durability)
持久性是指⼀个事务⼀旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作
演示MySQL事务
手动开启事务
在dos命令行窗口的基本操作指令
事务操作 | 基本指令 |
---|---|
开启事务 | start transaction / begin |
提交事务(成功提交事务) | commit |
回滚事务(未成功提交事务) | rollback |
只要执行上面的commit或者是rollback事务都会结束, 前者是成功提交操作(即所有的操作都生效), 后者是回滚(未成功提交)撤销事务中的所有操作
演示如下
首先是查找一下t_student表中的数据
现在我们开启事务并执行一些DML操作后commit(成功提交)
我们发现成功插入了一条数据
现在我们再次开启一个事务并执行一些DML操作后rollback(未成功提交)
此时我们的stu_no = 13的这行记录并没有被删除…
MySQL默认事务机制
关于MySQL默认的事务机制:
MySQL默认情况下采用的事务机制是 : 自动提交, 所谓自动提交就是只要执行一条DML语句, 就会自动的开启事务并且执行结束之后直接commit, 所以这时候我们rollback是无法进行回滚的…(ACID中的持久性)
测试如下
分析一下底层的运行机制, 当执行insert语句的时候, 底层自动开启了事务机制, 这条SQL执行结束之后默认进行commit操作, 所以此时rollback并不能使之前的事务回滚
事务的隔离级别
隔离级别基本概述
事物的隔离级别这一块主要针对的就是上面事务四大特性中的隔离性(Isolation)
隔离性感性的理解可以视为两个不同事务之间的"墙", 而隔离级别就是墙的厚度大小
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交(read uncommitted) | 存在 | 存在 | 存在 |
读提交(read committed) | 不存在 | 存在 | 存在 |
可重复读(repeatable read) | 不存在 | 不存在 | 存在(大部分可避免) |
串行化(serializable) | 不存在 | 不存在 | 不存在 |
事务的隔离级别分为四种(按照隔离程度从低到高)
读未提交 < 读提交 < 可重复读 < 串行化
不同隔离级别产生的现象(按照严重程度从低到高)
脏读 > 不可重复读 > 幻读
三种现象
脏读
官方术语
指的是一个事务读取了另一个事务尚未提交的数据,即读取了另一个事务中的脏数据(Dirty Data)。在此情况下,如果另一个事务回滚了或者修改了这些数据,那么读取这些脏数据的事务所处理的数据就是不准确的。
简单点说就是, 现在假设开启了A, B两个事务, B事务对其中的数据进行了DML语句操作(增删改), B事务并没有提交,此时我们在A事务的内部进行select语句查询, 居然能读取到被B事务增删改的数据, 是不是就说明A, B两个事务之间的隔离级别非常弱(两个事务之间的"墙"厚度很薄)
不可重复读
官方术语
注意, 这里所说的现象是针对同一条SQL来说的, 也就是同一条SQL的结果不一样
指在一个事务内,多次读取同一个数据行,得到的结果可能是不一样的。这是由于其他事务对数据行做出了修改操作,导致数据的不一致性。
翻译一下, 现在假设我们开启了A, B两个事务, 现在在A事务中执行一条DQL查询语句, 此时我们在B事务中使用DML语句中的update语句进行操作A中查询的区间内的数据, B是否提交未知, 我们此时在A中再次执行之前那条DQL语句, 会发现我们前后的数据是不一样的, 为什么说B是否提交未知呢, 因为如果是B执行之前进行select查询, 这种情况就属于脏读(也是不可重复读), 所以说脏读的严重性要大于不可重复读
幻读
官方术语
指在事务执行过程中,前后两次相同的查询条件得到的结果集不一致,可能会变多或变少。
其实就是A, B两个事务开启之后, B中添加几条/删除几条数据, A中可以观测到
关于不可重复度和幻读的区别, 不可重复读是指的是一条数据的内部更新变化, 而幻读指的是添加或者删除某些数据
查看和设置隔离级别
MySQL默认的隔离级别是可重复读(repeatable read)
查看隔离级别(全局/对话)
-- 查看当前会话的隔离级别
-- 也就是当前dos窗口的隔离级别(退出窗口之后失效)
select @@transaction_isolation;
-- 查看当前全局的隔离级别(整个MySQL服务)
select @@global.transaction_isolation;
演示如下
查看当前对话的
查看全局的
设置隔离级别
当设置全局的隔离级别之后, 重新打开对话之后对话的隔离级别也会随之改变
-- 设置当前对话的隔离级别
set session transaction isolation level [级别名称]
-- 设置全局的隔离级别
set global transaction isolation level [隔离名称]
下面演示的是把当前对话的隔离级别改成读提交
这种设置的作用范围就是当前的对话, 一旦对话撤销之后也随之失效
下面演示的是设置全局的隔离级别为读提交
这种全局的隔离级别一旦设置, 全局范围内都有效, 不能随意切换
四种隔离级别及演示
我们对隔离级别的模拟是通过开启两个dos窗口来进行模拟并发的
读未提交(read uncommitted)
A事务与B事务,A事务可以读取到B事务未提交的数据。这是最低的隔离级别。几乎两个事务之间没有隔离。这种隔离级别是一种理论层面的,在实际的数据库产品中,没有从这个级别起步的。当事务隔离级别是读未提交时,三种现象都存在:脏读,不可重复读,幻读。
首先设置一下全局的隔离级别
-- 设置全局的隔离级别为读未提交
set global transaction isolation level read uncommitted;
下面是演示
我们使用的数据演示表是下面的学生表, 这个是初始的数据情况
事务A | 事务B |
---|---|
开启事务A | 开启事务B |
插入一条数据 | |
更改一条数据 | |
再次查询一下数据 | |
rollback回滚B事务 |
通过对比我们很明显的看到, 在B事务没有提交的情况下, 我们A事务就可以查看到B事务中新插入的这条信息的数据(幻读), 也可以查询到B事务中对A事务中信息的更新(不可重复读), 这两个都同时属于脏读, 可见此时的隔离级别是非常低的
读提交(read committed)
A事务与B事务,A事务可以读取到B事务提交之后的数据。Oracle数据库默认的就是这种隔离级别。
此时存在不可重复读和幻读
首先设置一下全局的隔离级别为读提交
-- 设置全局的隔离级别为读提交
set global transaction isolation level read committed;
下面是演示
事务A | 事务B |
---|---|
开启事务A | 开启事务B |
查询一下数据 | |
插入一条数据 | |
更新一条数据 | |
在B事务未提交之前查一下数据 | |
提交B事务 | |
重新查一下A事务数据 | |
提交A事务 |
分析一下上面的过程, 在B事务没有提前之前, 我们在B中执行的更新删除的操作都不会影响事务A中的查询结果, 也就是避免了脏读, 但是当提交了B事务中的数据, A事务就可以查找出来B事务增加和修改的数据, 也就是存在不可重复读和幻读
可重复读(repeatable read)
这个隔离级别是MySQL数据库默认的。A事务和B事务,A事务开启后,读取了某一条记录,然后B事务对这条记录进行修改并提交,A事务读取到的还是修改前的数据。这种隔离级别称为可重复读。
这个隔离级别可以避开脏读, 不可重复读, 大部分的幻读(待会再说)
首先还是修改一下当前的隔离级别
-- 设置全局的隔离级别为可重复读
set global transaction isolation level repeatable read;
下面是演示
A事务 | B事务 |
---|---|
开启A事务 | 开启事务B |
查询一下原始数据 | |
插入一条数据 | |
更新一条数据 | |
再次查看数据 | |
提交事务 | |
再次查看数据 | |
使用for update查询 | |
提交事务 |
经过上面的演示, 我们发现在B事务提交之前, B事务中执行的任何操作A事务中都无法查询到, 所以这就避免了脏读, 此时我们提交事务B, 再次对A事务中的事务尽心查询, 我们发现这里不仅避免了不可重复读(同一条数据都是一样的), 同时甚至避免了幻读(数据的条数并没有增加), 此时我们使用了for update查询(这是查询语句可以读取到当前最新的数据), 就会发现产生了幻读, 所以我们说MySQL可以最大程度的避免幻读但是也不能完全避免, 至于什么时候可以, 什么时候不可以我们下面会介绍, 还有人疑问这里难道不是不可重复读么, 注意此时select语句发生了变化, 所以此时还是满足可重复读的
串行化(serializable)
这种隔离级别最高,避免了所有的问题,缺点是效率低,因为这种隔离级别会导致事务排队处理,不支持并发。所以我们一般不会采用这种隔离级别
首先还是设置一下全局的隔离级别
set global transaction isolation level serializable;
下面是演示
事务A | 事务B |
---|---|
开启A事务 | 开启B事务 |
事务B进行操作 | |
提交事务 | |
此时B事务才可以执行 |
分析一下, 此时隔离级别为串行化的级别, 此时不允许并发了, 我们发现我们在B事务(后开启)中执行DQL查询操作, 光标就会一直闪烁, 卡在这里不执行, 等待A事务提交完毕之后才可以执行, 当A事务提交之后, B事务自动执行, (45.89 sec 就是B事务等待的秒数), 所以这种情况我们很少用, 不支持并发之后效率很低
可重复读的幻读问题
上面我们说了, MySQL中的可重复读可以尽自己最大的努力来避免产生幻读问题, 但是还是可能存在的, 下面我们详细的分析一下可重复度隔离级别中的幻读问题