文章目录
- 一.什么是事务
- 1.1. 事物的属性
- 1.2. 事务的版本支持
- 1.3. 事务的提交方式
- 1.4. 事务常见操作
- 证明事务的回滚
- 事务崩溃情况下验证回滚
- 结论
- 二.事务隔离级别
- 2.1. 如何理解隔离性
- 2.2. 隔离性级别
- 2.3. 脏读,幻读,不可重复读
- 2.4. 查看,设置隔离级别
- 2.5. 隔离性验证
- 三.一致性(Consistency)
一.什么是事务
如果CURD不加控制,会有什么问题
类似于我们刚学多线程写的抢票逻辑代码,数据库中的数据也是临界资源,也需要考虑并发性带来的问题,
CURD满足什么属性,能解决上述问题
- 买票过程必须满足原子性, 即:我在买票的时候,你不能买,我买完你才能买
- 买票不能相互影响
- 买完票应该永久有效
- 买前买后都要是确定的状态
什么是事务
1)事务就是由一组DML(数据操控语言,比如insert、update…)语句组成,这些语句在逻辑上存在相关性,这一组DML语句要么全部成功,要么全部失败,是一个整体
2)事务就是需要做的事情,这个事情数据量比较大,处理比较复杂
- 假设一种场景:你毕业了,学校的教务系统后台 MySQL 中,不再需要你的数据,要删除你的所有信息
- 那么要删除你的基本信息(姓名,电话,籍贯等)的同时,也删除和你有关的其他信息,如:你的各科成绩,你在校表现,甚至你在论坛发过的文章等
- 这样,就需要多条 MySQL 语句构成,那么所有这些操作合起来,就构成了一个事务
3)事务本质上是为了应用层服务的,而不是伴随着数据库系统天生就有的,
1.1. 事物的属性
一个 MySQL 数据库,不止一个事务在运行,同一时刻,甚至有大量的请求被包装成事务,在向 MySQL 服务器发起事务处理请求,而每条事务至少一条 SQL语句,这样如果大家都访问同样的表数据,在不加保护的情况,就绝对会出现问题甚至,因为事务由多条 SQL 构成,那么,也会存在执行到一半出错或者不想再执行的情况,那么已经执行的怎么办呢?
所以,一个完整的事务,绝对不是简单的 sql 集合,还需要满足如下四个属性:
1)原子性: 一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节,事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样,
2)一致性 :在事务开始之前和事务结束以后,数据库的完整性没有被破坏,这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作
- 比如,A想给B转账,A账户扣钱,B账户加钱,不能A账户不扣钱或者少扣钱,B账户多加钱
3)隔离性: 数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致,事务隔离分为不同级别,包括读未提交, 读提交, 可重复读 和 串行化 , 隔离性是为了保证数据的安全提出来的同时为了尽可能的保证效率,提出了隔离性的等级
4)持久性: 事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失
上面四个属性,可以简称为 ACID : 原子性(Atomicity,或称不可分割性),一致性(Consistency),隔离性(Isolation,又称独立性),持久性(Durability)
1.2. 事务的版本支持
在 MySQL 中只有使用了 Innodb 数据库引擎的数据库或表才支持事务, MyISAM 不支持,
1.3. 事务的提交方式
事务常见的提交方式有两种:自动提交,手动提交 事物提交方式是在全局设置的
查看事务的提交方式,
show variables like 'autocommit';
自动提交默认被打开,如果相对它进行设置,可以用下面的命令:
set autocommit=0; #关闭自动提交
set autocommit=1; #开启自动提交
1.4. 事务常见操作
- 开始一个事务:
begin
/start transaction;
- 创建一个保存点:
savepoint+保存点名字;
- 回滚操作:
rollback to 保存点名字;
,如果直接rollback
则是直接回滚到事务的最开始, - 提交事务:
commit;
- 查看事务的隔离级别:
select @@tx_isolation;
默认的隔离级别为可重复读
证明事务的回滚
以下面的测试表为例:
可以回滚到之前的保存点,也可以回滚到最开始, 一个事物一旦被提交commit之后就不能回滚了,begin和commit之间的sql叫事务
mysql> select * from account;#空表
mysql> begin;#启动一个事务
mysql> savepoint save1;#在事务的一开始创建保存点
mysql> insert into account values(1,'张三','100.5');#插入测试数据
mysql> select * from account; #查询表的内容
+----+--------+--------+
| id | name | blance |
+----+--------+--------+
| 1 | 张三 | 100.50 |
+----+--------+--------+
1 row in set (0.00 sec)
mysql> savepoint save2;#插入一条数据以后,再创建一个保存点
mysql> insert into account values(2,'李四','200.5');#插入测试数据
mysql> rollback to save2;#回滚到save2,可以看到save2之后的数据没了
mysql> select * from account;#查询表的内容
+----+--------+--------+
| id | name | blance |
+----+--------+--------+
| 1 | 张三 | 100.50 |
+----+--------+--------+
1 row in set (0.00 sec)
mysql> rollback to save1;#回滚到save1
mysql> select * from account;
Empty set (0.00 sec)
#save2在save1之后,不能从save1回滚到save2,也就是说只能往前回滚,不能往后
mysql> rollback to save2;
ERROR 1305 (42000): SAVEPOINT save2 does not exist
mysql> insert into account values(1,'张三','100.5');
mysql> insert into account values(2,'李四','200.5');
mysql> rollback;#直接回滚到事务的最开始
mysql> select * from account;
事务崩溃情况下验证回滚
在隔离级别设置为读未提交的情况下:
自动提交的作用(不启动事务的时候):自动提交开启,即使客户端异常崩溃,也会自动提交, 自动提交关闭,异常情况下数据会回滚,
- 自动提交开启的时候 (不启动事务的时候)
我们之前所写的所有单条DML,SQL语句,在mysql中默认就是一个事务 ; 默认就是执行完,会被mysql自动提交.
- 自动提交关闭的时候: (不启动事务的时候)
**事务会自动更改提交方式,不受MySQL是否启动自动提交影响,**即begin启动事务后与mysql是否自动提交无关
- 实验1:即使开启了自动提交, 事务未commit,客户端崩溃,MySQL自动会回滚,
自动提交已经开启,可是崩溃后还是回滚了,说明事务并不受自动提交影响
实验2:事务commit,客户端崩溃,MySQL数据不会受影响,已经持久化
结论
从上面的例子,我们能看到事务本身的原子性(回滚),持久性(commit),
1.只要输入begin或者start transaction启动事务, 那么事务便必须要通过commit提交,才会持久化,如果运行期间异常终止,就会回滚到最初的状态, 与是否设置自动提交set autocommit无关
2.事务可以手动回滚,同时,当操作异常,(如果没有commit),那么MySQL会自动回滚,自动回滚的场景一般是客户端出现异常崩溃了,
3.对于 InnoDB 每一条 SQL 语言都默认封装成事务,自动提交,(select有特殊情况,因为 MySQL 有 MVCC )
总结:当进行事务的一方突然断开,未能提交事务,那么MySQL回自动进行回滚,事务的操作会自动取消,但一旦提交后,数据就持久化变为永久状态,本质上单条SQL,也被MySQL包装成一个事务,所以如果突然退出,MySQL也会自动回滚,但前提是将MySQL的自动提交关闭,MySQL会自动回滚未完成的事务,以此来维护MySQL事务的原子性,
事务操作注意事项:
1.如果没有设置保存点,也可以回滚,只能回滚到事务的开始,直接使用 rollback(前提是事务还没有提交),如果一个事务被提交了(commit),则不可以回滚(rollback), 我们可以选择回退到哪个保存点,
2.InnoDB 支持事务, MyISAM 不支持事务,
3.开始事务可以使 start transaction 或者 begin,
二.事务隔离级别
隔离性:能还是不能让你看到某些东西 隔离级别:哪些东西应该看到,哪些不应该看到
2.1. 如何理解隔离性
数据库中,为了保证事务执行过程中尽量不受干扰,就有了一个重要特征:隔离性
1.mysql服务可能会同时被多个进程(或者线程)访问,以事务的方式进行访问
2.一个事务可能由多条sql语句构成,因此一个事务有执行前、执行中、执行后的阶段,执行中出现问题,可以随时回滚,所以单个事务,对用户表现出来的特性,就是原子性,
3.所有的事务都需要有执行过程,那么在执行多个事务的时候,很有可能会互相产生影响,从而收到干扰,导致错误的发生,例如:多个事务同时访问同一张表,甚至同一行数据, 为了保证数据执行过程之中尽量不受干扰,就有了一个重要的特征,隔离性
例子:
你妈妈给你说:你要么别学,要学就学到最好,至于你怎么学,中间有什么困难,你妈妈不关心,那么你的学习,对你妈妈来讲,就是原子的,那么你学习过程中,很容易受别人干扰,此时,就需要将你的学习隔离开,保证你的学习环境是健康的
2.2. 隔离性级别
允许事务收到不同程度的干扰,就有了一个重要特征,隔离性级别,
读未提交(read uncommitted)
所有的事务,都可以看到其它事务没有提交的执行结果,相当于没有任何隔离性
- 带来的后果就是脏读(读取未提交的数据)、幻读(前后多次读取,数据总量不一致)、不可重复读等问题(前后多次读取,数据内容不一致),
读提交(read committed):
一个事务只能看到其它已经提交的事务,这种隔离级别解决了脏读,但是幻读和不可重复读没解决,
可重复读(repeatable read)
这个是MySQL默认隔离级别,确保一个事务在执行中,多次读取数据时,会看到同样的数据
- 比如,事务A提交,事务B在执行中,不能看到A提交的内容,即事务B在任意时刻,select看到的内容都是一样的,不会受事务A提交的内容影响
串行化(serializable)
这是事务的最高隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决了幻读的问题,它在每个读的数据行上面加上共享锁,但是可能会导致超时和锁竞争(这种隔离级别太极端,实际生产基本不使用)
总结:
名称 | 解释 |
---|---|
读未提交(Read Uncommitted) | 所有事务都可以读到其他事务未提交的结果,相当于没有隔离 |
读提交(Read Committed) | 一个事务只能读到其他事务提交后的结果,可能会造成不可重复读 |
可重复读(Repeatable Read) | 确保一个事务多次读取只会看到同样的数据,可能会造成幻读 |
串行化(Serializable) | 通过强制事务排序,使之不可能发生冲突,但可能会导致超时和锁竞争 |
大多数数据库的隔离级别都是读提交,MySQL默认的隔离级别是可重复读,从上到下,隔离逐渐增强,实际生产不会使用读未提交和串行化,都太极端
为什么存在这么多种类的隔离级别?
隔离性:mysql的内部机制,让"同时"启动, 并发执行的各个事务,看到不同的数据修改(增删改),就叫做隔离性,我们作为一个事务,可以看到不同可见性的数据,程度的不同,叫做隔离级别
为什么存在隔离级别
为了安全! 但是如果只是为了安全,不需要种类繁多的隔离级别,无脑使用串行化不就好了吗,不就是最安全的吗!所以隔离级别不仅仅是为了考虑安全问题,而是在安全+效率之间找平衡点,这个不说mysql决定的,mysql不是这个平衡点的决策者,而是执行者, 所以才会存在种类繁多的隔离级别,相当于我把各种情况列出来,由用户说了算
2.3. 脏读,幻读,不可重复读
脏读
A事务读取B事务尚未提交的数据,未提交意味着这些数据可能会回滚,也就是可能最终不会存到数据库中,此时如果B事务发生错误并执行回滚操作,那么A事务读取到的数据就是脏数据
- 脏读最大的问题就是可能会读到不存在的数据,从宏观来看,就是事务A读出了一条不存在的数据
幻读
事务前后多次读取,数据总量不一致
- 例如:A事务读取表中的数据,如果此时B事务往表中插入或删除数据并提交,A再次读,就和之前读到的数据总量不一样 幻读指的是插入而谈的
幻读的重点在删除和插入,在同一事务中,同样的条件,第一次读取的记录数第二次读取的记录数不一致(因为中间有其他事务提交了插入和删除操作)
不可重复读
事务前后多次读取,数据内容不一致
- 例如:A事务读取表中的数据,如果此时B事务修改了表中的数据并提交,A再次读就和之前读到的数据内容不一样
不可重复读的重点在修改,在同一事务中,同样的条件,第一次读取的数据和第二次读取的数据不一样(因为中间有其他事务提交了修改), 可重复读指的是插入和删除/修改谈的
2.4. 查看,设置隔离级别
注意:修改全局隔离级别以后,其他客户端要重启才能生效,
隔离级别的分类:读未提交(read uncommitted), 读提交(read committed):可重复读(repeatable read),串行化(serializable)
查看全局隔离级别:
select @@global.tx_isolation;
查看当前会话隔离级别
select @@session.tx_isolation; #简写为:select @@tx_isolation;
设置全局隔离级别,(其它的会话也会被影响):
set global transaction isolation level 隔离级别名称;
设置当前会话隔离级别,只会影响当前会话
set session transaction isolation level 隔离级别名称;
2.5. 隔离性验证
隔离级别 | 脏读 | 不可重复读 | 幻读 | 加锁读 |
---|---|---|---|---|
读未提交(read uncommitted) | 未解决 | 未解决 | 未解决 | 不加锁 |
读提交(read committed) | 解决 | 未解决 | 未解决 | 不加锁 |
可重复读(repeatable read) | 解决 | 解决 | MySQL内部解决 | 不加锁 |
串行化(serializable) | 解决 | 解决 | 解决 | 加锁 |
1.当然,所有的隔离机制都是针对并发执行的多个事务,而不是所在的客户进程,隔离级别越严格,安全性越高,但数据库的并发性能也就越低,往往需要我们寻求一个平衡点
2.所谓隔离性,就是一种MySQL内部的机制,让并发执行的多个事务,看到和修改不同的数据,就叫做隔离性,而哪些数据可以看到可以修改,其程度的不同,就由隔离级别决定,
3.事务具有隔离性是为了安全,种类繁多的隔离机制是为了兼顾效率,
验证读未提交 (read uncommitted)
读未提交,相当于没有锁,会产生很多问题,比如脏读,读到另一个执行中事务的未提交数据就叫脏读,实际生产环境是不会使用的
验证读提交(read committed)
读提交会导致其他进程一个事务内同样的两次读取,读到的数据不一样,这个现象就是不可重复读**,不可重复读是有问题的,问题在于不应该让与当前事务并行的事务也读到,造成一个事务读取前后不一致**会影响自身上层业务逻辑
例子:根据存钱的不同把用户筛选出来,发不同的奖品,如果是读提交则可能一个人有多份奖品,多次查询都能查询到他
验证可重复读(repeatable read)
可重复读,MySQL的默认隔离级别,就算修改一方事务已经提交,其他同时存在的事务也无法看到修改的数据,也就是屏蔽与自身并发的事务所做出的修改,
一般数据库只能屏蔽修改和删除操作,无法屏蔽插入操作的
- 因为隔离性实现是对数据加锁完成的,而insert新增数据是不存在的,也就无法预先加锁,那么一般加锁无法屏蔽这类问题,这也就导致了幻读(多次读取读到新增数据)
但是MySQL在可重复读情况的隔离级别下,解决了幻读问题的
验证串行化:(serializable)
对所有操作加锁进行串行化,不会存在任何问题,但是效率很低,几乎完全不被使用,
当然读取操作select是不会被阻塞的,因为它不会修改数据,如果并发事务一方进行读取写入等操作,另一方事务就会被阻塞住,会被定时解锁
三.一致性(Consistency)
1.事务执行的结果,必须使数据库从一个一致性状态,变到另一个一致性状态,当数据库只包含事务成功提交的结果时,数据库处于一致性状态
2.如果系统运行发生中断,某个事务尚未完成而被迫中断,而该未完成的事务对数据库所做的修改已被写入数据库,此时数据库就处于一种不正确(不一致)的状态,因此一致性是通过原子性来保证的,
3.一致性和用户的业务逻辑强相关,一般MySQL提供技术支持,但是一致性还是要用户业务逻辑做支撑,也就是,一致性,是由用户决定的 ,一致性由上层业务和底层MySQL共同决定,
- 比如,A想给B转账,A账户扣钱,B账户加钱;业务逻辑不能设置为A账户不扣钱或者少扣钱,B账户加钱
4.一致性不是MySQL中真实存在的一种具体状态,是由原子性、持久性、隔离性共同保证数据库处于一致性的状态
- 一致性不是一种具体的方案, 而是事务维护的最终目标