mysql事务 -- 如何理解事务,四个属性,查看是否支持事务,事务操作(提交方式,事务的开始和回滚,提交),事务的隔离性(如何理解,隔离级别概念,如何查看/设置)

目录

事务

引入

买票

转账

如何解决

概念

理解

例子

问题

四个属性

原子性

一致性

隔离性

持久性

总结

为什么要有事务

查看是否支持事务

事务操作

提交方式

查看

设置

隔离级别

测试表

事务的开始和回滚 

启动事务

设置保存点 

定向回滚

回滚

提交事务

执行期间发生异常

事务的提交方式

与手动开启事务无关

单条sql和事务的关系

总结

如何验证这一点?

事务的隔离性

理解

隔离性理解

隔离级别理解

总结

隔离级别

读未提交【Read Uncommitted】

读提交【Read Committed】

可重复读【Repeatable Read】

串行化【Serializable】

总结

查看隔离级别

全局 

当前会话

设置隔离级别

设置全局

设置当前会话


事务

引入

在MYSQL中,多个客户端可以同时连接到连接mysqld,并访问同一个数据库

  • 也就是多线程会对数据并发访问
  • 在这个过程中可能会出现各种各样的问题

我们来举一些场景的例子:

买票

当客户端1判断成功后,进入买票流程

  • 此时当前线程的时间片到了,另一个线程执行

客户端2来的时候,票还没有卖出去

  • 所以也成功判断,进入买票流程

之后就会出现一张票卖了两次的情况

转账

当某银行进行同行转账,正常流程是:卡1扣钱,卡2增加同数目的钱

  • 如果卡1成功扣款后,还没来得及给另一张卡加上,就出现了异常,导致操作中断
  • 中断后可能操作无法继续,于是就产生了一个中间过程

如何解决

mysql如何解决此类问题呢?

将操作过程变成原子的

  • 按照上面例子,就是要么不买,要么买到票 ; 如果转账不成功,就进行回滚,将数据恢复到初始状态(将扣掉的钱加回来),等待下一次再操作

操作之间不能互相影响

  • 不能在我买的时候,你也来买
  • 就和多线程访问同一份临界资源一样,在一个线程进行访问时,要保证其他线程不能打扰

购买成功是永久有效的

  • 不能付钱了又说购买失败/这张票失效 或者 转账后不久钱又莫名消失了

买前/买后状态要是确定的

  • 买前就是没买
  • 买后就是已经买了/没有成功
  • 没有其他中间状态,比如上面的转账,不能出现像上面那样预期之外的结果

以上是已经提出的事务方案定义的规则

概念

理解

要完成某个操作,一般是需要一批sql组合起来共同实现

  • 只有把这些sql语句看作一个整体,才有意义

比如,转账的时候:

  • 一定是先在一个账户上扣钱,再在另一个账户上加钱,需要两条sql语句
  • 如果单拎出来一个,没有任何意义,只是一条sql语句
  • 但如果看作整体,站在使用者的角度来说,这就是转账逻辑(两条语句之间存在某种逻辑关系)

这样组成的整体,是一组数据管理语言(DML),被称为事务

  • 如何理解事务的本质 -- 需要站在mysql上层(使用者)来看待这些sql语句,他们需要完成某个具体业务的动作
  • 某个业务的需求 --转化成-> 多条sql语句 = 事务

所以,事务就是由若干条sql语句构成的集合体,用于完成某个特定任务

例子

假设要删除某个已经毕业学生的全部信息

  • 这个工作一定涉及到多张表,也就需要多条sql语句

站在程序员角度:

  • 就只是多条del语句

但站在使用者来说:

  • 这些del合起来 = "在系统中去除该学生的所有信息",于是,这些sql语句就被封装成了一个事务

所以,事务是mysql为我们提供的一种程序的术语

  • 严格上来说不是程序员使用的术语,只是为了表示这一组sql语句是有逻辑关系的

问题

同一时刻,会有大量的sql语句被封装成事务,向mysql服务器发起事务处理请求

  • 如果访问的是同一份资源,就注定会有多条sql交叉并发运行的情况
  • 就可能会出现数据不一致的问题

除此之外,要是执行到一半,遇到异常无法正常继续执行 / 不想继续执行

  • 怎么办?总不能数据修改到一半就不管了吧

所以,单纯只有事务的概念还不够,还需要满足4个属性保证事务的正确运行

四个属性

原子性

保证事务中的所有操作没有中间状态,要么全部完成,要不全部不完成

  • 不会结束在中间某个环节
  • 如果执行过程中发生错误,会被回滚至当前事务执行前的状态

注意,我们需要在外界看来,事务是原子的

  • 但我们心里要清楚,肯定是会有[事务执行中的过程]的
  • 我们要做的措施都是基于正在执行的事务
一致性

事务开始之前和事务结束以后,数据库的完整性没有被破坏

  • 一个状态->另一个状态,结果是可预期的

在技术上,并没有设置策略来保证一致性

  • 通过另外三个属性,以及用户的配合来保证一致性的
隔离性
数据库允许多个并发事务同时对其数据进行读写和修改的能力
  • 那么相应的,数据库就要提供技术支持 -- 隔离性,它可以防止多个事务并发执行时由于交叉执行而导致数据的不一致

隔离性分为多个级别,来应对不同的需求:

  • 包括读未提交( Read uncommitted )、读提交( read committed )、可重复读( repeatable read )和串行化( Serializable ​​​​​​​
持久性

一旦将事务处理完成,对数据的修改不能因为故障等问题丢失

这里只是大概了解下概念,后续会详细介绍

总结

事务就是在ACID四条属性的加持下,由一条或多条sql语句组成的集合体

既然mysql中会存在大量事务,就需要将他们管理起来 -- 先描述,再组织

  • 所以,多条sql语句到来后,将这些sql打包成一个事务对象 -> 放入事务处理列表中

为什么要有事务

事务不是mysql天然就有的,而是被设计出来的

  • 本质上是为了让应用层编写程序时更方便,不需要应用程序来解决并发问题

就像是我们去银行柜台办服务

  • 我们不需要考虑这笔钱在流通过程中会出现什么问题,出现问题该怎么办
  • 操作都是由银行人员完成的,只需要告诉他们我要干嘛就行

这就是事务封装存在的意义

查看是否支持事务

不是所有的存储引擎都支持事务(transactions)

  • 使用show engines\G查看,通过comment / transactions可以知道该存储引擎是否支持事务

这里的innodb就是支持事务的:

而myisam及其其他存储引擎,都是不支持事务的:

目前只有使用了innodb数据库引擎的数据库/表才支持事务

除了关系型数据库外,nosql,redis 等也支持事务

  • 只不过实现的方式不同,也没有mysql设计的这么完善

事务操作

我们一边做测试,一边介绍事务操作

提交方式

查看
show variables like 'autocommit';

  • 默认是将事务自动提交的
设置
SET AUTOCOMMIT=1/0;
  • 设置该字段为1,自动提交事务
  • 为0,关闭自动提交

我们这里就不修改了,为后面的测试做准备

隔离级别

为了之后的事务测试,我们先将隔离级别设置成读未提交(最低级别,方便我们查看操作结果)

  • set global transaction isolation level READ UNCOMMITTED;
  • 设置好后,需要重启客户端才能生效

查看隔离级别

select @@tx_isolation;
  • 可以看到,我们将隔离级别修改成功:

我们使用两个客户端来模拟两个并发访问mysql服务器的客户端,来制作出若干并发场景

测试表

员工工资表 :

create table if not exists account(
    id int primary key,
    name varchar(50) not null default '',
    blance decimal(10,2) not null default 0.0
)ENGINE=InnoDB DEFAULT CHARSET=UTF8;

事务的开始和回滚 

启动事务

start transaction / begin

  • 这条语句执行后,后续输入的语句都属于同一个事务

当我们让两个客户端都启动事务,两边就开始并发执行:

设置保存点 

savepoint + 保存点名称 

  • 支持定向回滚
  • 就跟玩游戏存档一样,我们可以随时读档

我们在表1中插入每条数据前,都设置一个保存点:

当我们使用客户端1向表中插入数据后,另一个客户端可以看到数据的增加:

定向回滚

如果在事务进行中,我们突然后悔了,不想插入某条数据,我们可以进行定向回滚

  • rollback to + 保存点名称 

跟游戏读档一样,但有一点不同:

  • 一旦回滚后,该保存点之后的操作全都没有了(相当于后续的存档也消失了)

客户端1执行回滚后,在客户端2中也能看见更新后的数据:

回滚

如果不设置保存点,还能回滚吗?

  • 可以回滚,只是不能定向回滚了,会直接回滚至事务开始前
  • 语法 -- rollback

提交事务

commit

  • 一旦事务提交,修改的数据就被持久化了,即使再执行回滚操作也不会有变化
  • 所以,执行回滚都是在事务运行期间进行的
执行期间发生异常

如果在事务执行期间发生异常怎么办?

按照我们前面在原子性里介绍的,异常后数据库会自动进行回滚,所以我们需要让客户端崩溃

  • crtl \ 让客户端1在执行事务期间崩溃退出:
  • 查看客户端2的表数据时,发现已经更新成事务执行前的状态了:

也就说明mysql已经完成了自动回滚

如果关闭终端

  • 因为事务依然没有提交,所以还是自动回滚了

如果我们先提交事务,再崩溃:

  • 客户端2的数据依然会保留下来,并不会因为崩溃而丢失:

事务的提交方式

与手动开启事务无关

我们之前查看过,事务的自动提交是开启的,那为什么在客户端崩溃后依然被自动回滚了呢?

  • 当我们使用begin创建事务时,是需要手动提交的,因为手动开启,就要对应手动提交
  • [手动开启事务]与[事务自动被提交],这是两个概念,互相并不冲突

如果我们把自动提交关闭,依然还是刚才的现象

  • 说明自动提交是否设置,与begin操作无关
单条sql和事务的关系

我们先将自动提交关闭:

我们在客户端1直接执行单条sql,可以在客户端2看到数据确实被删掉了:

但当我们让客户端1异常退出时,再次查看2中的表数据,已经被删掉的数据竟然又回来了:

  • 说明数据被自动回滚了
  • 而这是在自动提交被关闭的情况下进行的

如果我们开启自动提交,重新上述步骤:

即使客户端1崩溃,客户端2中查看到的数据依然被删除了:

总结

事务自动提交是否设置会影响单sql的执行结果

因为每一条sql语句都会被mysql封装成一个事务

  • 所以,如果我们没有设置自动提交,单条sql语句执行后,依然处于事务执行中
  • 一旦客户端崩溃,就会自动回滚

而如果设置了自动提交

  • 语句执行完,事务就自动提交了
  • 所以即使发生异常,数据也不会丢失

因为自动提交是默认打开的

  • 所以我们感知不到
  • 即使我们不了解事务的概念,也不影响操作
如何验证这一点?

我们可以在关闭自动提交的情况下,执行完单sql后,进行手动提交(commit):

  • 发现即使客户端崩溃,数据也没有被回滚
  • 说明单sql语句确实是被包装成了事务

以上,我们可以看出事务的两大特性 -- 原子性(回滚),持久性(commit)

事务的隔离性

mysqld作为服务器,一定会被多个客户端并发访问 -- 这是前提认知

理解

在技术上,sql的执行过程一定是分为执行前,执行中,执行后(因为可能包含多条sql)

  • 我们希望一个事务在[执行中的过程]不被其他因素影响,如果确实被影响了,那就直接回滚到执行前
  • 上述过程在外层看来,它就是原子的

就像多线程中的锁一样

  • 拿到锁的线程依然会在执行过程中被切走,但在其他线程看来,即使被切走,他们也无法访问那份临界资源
  • 所以依然是原子的

注意"实际情况"和"看上去"的区别

正因为实际会有执行中的状态,才会出现多个事务可以并发执行

  • 才会有多条sql语句访问同一张表,甚至同一行数据
  • 所以一定会出现多个事务之间互相影响的情况
  • 比如,读着读着数据不一样了

为了保证一个事务尽量不被其他事务影响,就出现了隔离性

  • 数据库允许事务受不同程度的干扰,就有了隔离级别

隔离性理解

每个事务到来时,应该只能看到它到来时的东西

  • 所以,需要隔离性来保证 -- 一个事务在执行时,能在一个稳定的环境中进行,不被其他事务干扰,导致看见了不应该被看见的数据

事务的执行过程就像是时间轴上的一条线

  • 因为是一条线,并且先到来的不一定先退出,就有可能会和后到来的产生交集:
  • 即使[先到来的]在[后到来的]的执行过程中完成了,[后到来的]也不应该看见被更新的数据
  • 这样才符合我们前面讨论的[每个事务只能看见到来时的数据]
  • 只有当[后到来的]在[先到来的]执行完后才到来,才应该看见更新后的数据
  • 也符合隔离性的目的 -- 保证事务不被打扰,只要看不见被其他事务更新的数据,就相当于没有被干扰

隔离级别理解

具体的隔离性,应该隔离到什么程度?

假如没有隔离性:

  • 两个客户端在并行访问时,只要一方修改了数据,另一方立马能看到

如果添加隔离性:

  • 就可以实现我们前面讨论的,在执行过程中,其他客户端更新的数据"不会被看到"
  • 但这个"不会被看到"到什么程度,也就对应了不同的隔离级别

为了满足不同的应用场景,定义了不同的隔离级别

  • 可以让我们在隔离性的前提条件下看到不同的内容

总结

事务场景中,隔离是必要的

  • 多个事务中的多条sql可能会并发运行,为了保证数据的一致性和完整性,必须将这些事务隔离开来
  • 为了保证事务运行中,不会/受到特定程度的干扰,所以有了隔离性
  • 隔离,不是针对单条sql语句的隔离,而是运行中的事务进行互相隔离

隔离级别

读未提交【Read Uncommitted】

在该隔离级别,所有的事务都可以看到其他事务没有提交的执行结果

  • 相当于没有任何隔离性,也会有很多并发问题,如脏读,幻读,不可重复读等

我们前面做实验时,就是使用读未提交的方式

读提交【Read Committed】

该隔离级别是大多数数据库的默认的隔离级别
  • 但不是mysql默认的
一个事务只能看到其他的已经提交的事务所做的改变
  • 这种隔离级别会引起不可重复读,即一个事务执行时,如果多次 select, 可能得到不同的结果

可重复读【Repeatable Read】

只有当 自己这端结束事务后,才能看见其他客户端对数据库操作后的数据
  • 它确保同一个事务在执行中多次读取操作数据时,会看到同样的数据行
  • 这是mysql默认的隔离级别
  • 会有幻读问题

串行化【Serializable】

事务的最高隔离级别,它通过强制要求 所有语句按照到来顺序,一个一个执行
  • 保证了数据的绝对安全
  • 但会导致效率问题,因为是串行操作

总结

以上级别,前三个都与读有关

  • 它们定义的是,在并发环境下,事务在读取数据时的可见性
  • 如果是事务之间需要同时修改同一份数据,此时只能是串行操作

但如果所有操作都只是单纯的加锁,可能会影响mysql的效率

  • 所以通过隔离性,可以保证在数据安全的情况下,让mysql做更多的读写并发工作
  • 读写并发 -- 一方插入/修改/删除,一方读数据,而这也是mysql最常见的场景

查看隔离级别

全局 
SELECT @@global.tx_isolation;

会以全局隔离级别作为当前会话的初始隔离级别

当前会话
  • 当你拿着账户密码去登录mysql后,进入的命令行界面,就是一个会话
  • 该会话的生命周期从登录成功开始,到退出登录为止
SELECT @@session.tx_isolation;

SELECT @@tx_isolation;

第二个是第一个的缩写

设置隔离级别

set session / global transaction isolation level read uncommitted / read committed / repeatable read / serializable 
设置全局

全局的修改了会影响后续所有客户端

  • 但不会影响正在运行的会话(可以把这两个认为是两个变量,一个的改变并不会影响另一个)
  • 只有在会话启动时,才会读取这个全局隔离级作为初始值

一般修改隔离级别,需要保持所有会话遵循相同的规则

  • 所以一般都会修改全局的
设置当前会话

当前会话的修改了只影响当前这个客户端,并且退出后又恢复了

  • 在使用时,根据就近原则,会使用当前会话的隔离级别
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值