MySQL事务管理

事务的概念

事务是站在用户角度的。比如说,用户完成转账,对应的sql语句就是张三的账户扣除钱,李四的账户加上钱,完全这一任务(即钱的加和减少)需要俩个SQL语句。对应的这俩个updata语句就能完成转账这一“事务”。

MySQL在同一时间会同时存在大量的事务,大量的事务同时对一张表做CURD,如果不加控制,就会导致数据不一致的问题,常见的幻读、脏读、不可重复读等。

本文就会介绍事务的由来,如何开启事务,事务的四种特性:原子性、统一性、永久性、隔离性。介绍MVCC无锁并发版本以及实现原理。

当然一个事务并不是简单的SQL指令集合,还必须满足下列的特点:

  • 原子性:要么完成,要么没做。事务的执行过程,如果出错了,就必须进行数据的回滚(RallBack)。
  • 一致性:事务的前后是可预期的。
  • 隔离性:数据库允许多个并发的事务同时对数据的读取和修改(CURD)。隔离性是可以防止多个事务并发时的数据不一致。不同程度的隔离级别对事物的并发程度是不同的容忍的。
  • 持久性:在事务结束后,数据会被永久保存,不可回退。

为什么存在事务?

事务的存在是为了方便程序员的编写。事务的存在,我们只需要开启事务,如果编写错了,就回滚事务 ,事务结束后,只需要提交事务。

简单的三步操作就能将事务的性质全部做到。我们不在关系,网络问题,宕机了,数据如何恢复,事务帮我们做好了这一操作。

事务本质就是为了应用层服务的,是数据库与生俱来的。


事务的提交方式

事务的提交方式有俩种,手动提交和自动提交。一般来说,默认的是自动提交。

通过show  variables like "autocommit"查看事务提交的方式。

show variables like 'autocommit';

修改默认提交的方式

set autocommit = 1;


事务的实验

下面就来演示事务的性质,为了有利于测试,将隔离级别调到最低(读未提交级别)。以id为主键,创建InnoDB测试表。

修改全局的事务隔离级别为读未提交。隔离级别分为全局和当前会话,当前会话会在登录的时候,继承全局的隔离级别,所以修改全局后,要修改会话的隔离级别,就要重启mysql。

set global transaction isolation level read uncommitted;

为什么要修改隔离级别呢?为什么修改成读未提交?后面会详细介绍。 

 创建测试表:账户表,id为主键,姓名,薪资。

1)正常演示 - 证明事务的开始与回滚

操作1:启动俩个终端,分别开始事务(begin)。事务1和事务2.

左边终端设置保存点save1,右边事务查询表account ,得到结果为空。

 操作2:左边的终端插入一条数据(1,张三,100),并且设置回滚点save2。

现象:右边终端查询表account,看到刚刚插入的消息(1,张三,100)

由于隔离级别是最低的,读为提交,只要事务1操作,事务2就会读取到最新的数据,在事务1没有提交commit 时候,事务2就能读取到更改的数据。

操作3 :事务1插入消息(2,李四,100)并且,保存回滚点3 .事务2立马select查询。

现象:能够查询到俩条数据,张三、李四。逻辑和操作2完全一致。

操作 4:回滚数据:rollback to +保存点

事务1回滚(rolbackl)保存点2 ,事务2进行查询。

现象:事务2中查询的结果和操作2中一致,看到了历史的版本。

 从这个实验,就能发现事务的机制,提供数据的回滚。


非正常演示 - 为commit,客户端崩溃

同样开启双事务,事务1和事物2,事务1正常插入消息。之后将事物1退出,模拟mysql崩溃。

 左边终端输入ctr +\ 终止mysql ,事务2查询结果

观察到的现象,即使在事务1插入数据的情况下,事务2也能查询到数据。

但是事务1没有提交,一旦发生奔溃,数据就会自动回滚。

符合事务的原子性,即要么完成了,要么就是没做。


非正常演示 - 证明commit 后,客户端奔溃,也不影响数据 

同样开启双事务,事务一插入俩条数据后,commit提交。

终止客户端,模拟mysql奔溃,事务2查询结果。

在事务1提交之后,关闭mysql1,观察到事物2 依旧可以查询到数据。

符合事物的永久性,即一个事物提交commit 之后,数据就是被永久保存的,即使是mysql崩溃也不会回滚数据。


演示 - 事物会开启自动提交

事物是在开始之后,会开启自动提交,不论是否提前将自动提交关闭。

mysql客户端1,关闭自动提交后,开启事务。事务1更新某一行的数据,事务2读取。

模拟mysql崩溃,事务2查询,观察结果。

 演示:单条SQL实际上就是事务

实际上,autocommit的设置影响的是单条SQL 指令。

如果autocoomit 被设置为OFF,单条SQL需要commit才能持久化。

操作:

1)设置为自动提交,然后更新account的数据,最后关闭mysql1,观察结果。

2)设置为手动提交,然后更新account的数据,最后关闭mysql1,观察结果。

执行完单条sql后,将数据库终止,在另一个终端select 表,可以看到新增的数据。

将自动提交设置为手动提交。继续观察结果。

如果设置的是手动提交,单条sql语句被执行后,如果没有提交,因为设置了最低的隔离级别,另一个mysql客户端 也能看到。但是如果客户端出现崩溃,由于事务的原子性,数据会发生回滚,另一个客户端就不能看到更新的数据,看到的是历史版本。

总结一下:

  • 事务是为程序员服务的,事务有四个性质:隔离性,持久性,原子性,一致性。上述的例子实际上就算原子性和持久性。
  • 事务的begin和commit,在此期间,将提交方式修改为自动提交。如果发生错误会发生回滚数据。
  • 单条SQL实际上也是事务,收到autocommit的影响。
  • 如果一个事务没有设置回滚点(savepoint)如果发生回滚,会回滚到事务的起始位置。
  • 一个事务被提交,就无法在回滚。

事务的隔离级别 

  • 事务的隔离性是保证事务的执行过程,尽量不受到干扰。
  • 比如有俩个事务同时对一张表进行CURD,不加保护就会造成数据不一致。
  • 事务是由多条sql语句组合的,就会决定事务有三个过程:执行前,执行后,执行中。事务的原子性决定,在事务执行中发生错误,会发生数据回滚。但是俩个事务,事务1和事务2,事务1先开始执行,事务2后到来,事务2要么看到执行前,要么看到执行后。
  • 不同程度的隔离级别,就代表事务看到不同程度的干扰。

事务的隔离级别

  • 读未提交:事务隔离最低的级别,上述的实验都是按照读未提交来做的。假设有俩个sql客户端,一个客户端就数据表进行修改,没有commit,另一个客户端也没有commit,也能看到对应的修改记录。因为读未提交,隔离级别是最低的,会发生很多并发的问题:脏读、幻读、不可重复读等。
  • 读提交 :读提交是大部分数据库默认的隔离级别,它的定义是:俩个事务之间,另一个事务只能看到已经commit的事务操作。隔离级别比读未提交高一点,但是它也存在很多并发问题:幻读、不可重复读等。
  • 可重复读:MySQL默认的隔离级别,读取的前后会看到相同的结果,但是会产生幻读。不过MySQL的InnoDB存储引擎中,通过索引锁定(next-key locking)策略来减少幻读的发生,严格意义上不可避免。
  • 串行化:最高的隔离级别,强制排序,使之不发生幻读。读的时候使用共享锁保护,但是会导致锁的竞争,效率非常慢,不推荐使用。

查看与设置隔离级别 

查看/修改事务的隔离级别主要有俩种:全局隔离级别、当前会话隔离级别。

当前会话隔离级别会在登录的时候,继承全局的隔离级别。

修改全局的隔离级别,不会影响当前会话隔离级别,需要将mysql重启。

修改当前会话的隔离级别,在下一次登录mysql之后,会发生重置。

查看和修改全局隔离级别

查看全局隔离级别:

select @@global.tx_isolation;

修改全局隔离级别:

set global transaction isolation level read committed;

查看/修改当前会话隔离级别 

查看当前会话隔离级别

select @@session.tx_isolation;
select @@tx.isolation; //上面的简写

修改会话隔离级别

//修改隔离级别为串行化
set session transaction isolation level serializable;


读未提交

读未提交是最低的隔离级别,事务更新什么,但是没有提交,其它事务立马就能读到。

不建议使用。

 演示:修改隔离级别读未提交。开启双事务,事务1和事务2。实验读未提交。

事务1对数据表进行修改,事务2立马可见,双方没有提交。这是隔离级别最低的事务隔离性。

在这一隔离级别通常都会发生脏读一个事务进行中,读到另外事务更新但是没有提交的数据。

读提交

 修改隔离级别。创建双事务,事务1和事务。正常插入数据。

修改隔离级别为读为提交

 事务1插入数据,事务2查询。

实验的结果:在事务1新增数据后,事务2不能到读取到数据,只有当事务1commit提交数据记录之后,事务2才能查询到结果。这就是读提交的现象。

读提交会发生一个常见的问题----不可重复读:在不同时间,查询出来的结果不一致。

不可重复读是一个问题:
假如有个成绩等级表,张三在80分段。这时候开启俩个事务,事务1发现张三的成绩分段错误把张三移到90分段,之后提交。事务2,在之前查询过一下,在修改过后也查询过发现张三即在80分段,也在90分段。这个表就是错误的!

可重复读 

将当前会话的隔离级别设为可重复读。

事务1插入数据,事务2读取数据。最后分别提交,观察结果。

查看当前会话默认隔离级别。并且开启事务

 往事务1中插入一条数据,事务2读取数据,发现无法读取到新增的数据。

事务1更新id=1的姓名为赵六,事务2继续查询,查询不到更新的数据

 事务1提交,事务2读取,发现依旧无法读取,只有当事务2也提交后,才能读取到结果。

  • 可重复读只有当只有提交了,才能读取到修改的结果。避免了不可重复读问题,在任意一个时间段读取到的结果是一致的。
  • 幻读问题:可重复读针对insert操作会产生幻读,insert的数据,在多次读取中被读取出来了,导致前后读取的结果不一致。

为什么会产生幻读?

因为隔离性是对数据加锁实现的,但是insert前,这一条数据是不存在的,因此是无法被互斥锁保护。但是在InnoDB中通过GAP解决了幻读。

 串行化

串行化是最高的隔离级别,是依靠锁来实现的。

开启双事务,事务1和事务2。事务1是新增数据,事务2查询。

查看隔离级别

 事务1新增一条数据,事务2进行查询。

事务1提交后 ,事务2立马出现结果。

就像线程的阻塞等待。

串行化隔离级别是最高的,代价就是效率过低,不推荐使用。


事务隔离级别的总结

  • 隔离级别越高,安全性就越高,效率就会越低。
  • 一般来说,不修改mysql默认的隔离级别(可重复读)。
  • 脏读是读取到了未提交的数据。不可重复读是在不同时间段读取到结果不一致。幻读是针对新增问题,有时候新增之后,就算没有提交也会看到数据记录。

关于一致性

一致性是可预期的,如果成功commit,结果就是确定的。如果不成功,数据就会回滚。

一致性既有用户提供,也有mysql的原子性保证。只有用户输入正确了,才能达到预期的结果,只有mysql提供原子性,才能保证正确的结果。

下文就会介绍MVCC无所并发版本的实现机制。 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

深度搜索

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值