【MySQL】 -- 事务

如果对表中的数据进行CRUD操作时,不加控制,会带来一些问题。

比如下面这种场景:

有一个tickets表,这个数据库被两个客户端机器A和B用时连接对此表进行操作。客户端A检查tickets表中还有一张票的时候,将票出售了,而火车票的总数是一个固定值,客户端A此时还没有来得及更新数据库

这时客户端B检查了票数,发现还有一张,又卖了一次票。这种情况,就出现了同一张票被卖了两次的场景。

这就是在进行CRUD时,不加控制带来的问题。

只要当进进行CRUD时满足以下属性,才能解决上面的问题:

  1. 买票的过程得是原子的
  2. 买票互相应该不能影响
  3. 买完票应该要永久有效
  4. 买前,和买后都要是确定的状态

而MySQL的事务机制,就能够有效的解决此问题。

1. MySQL事务

事务就是一组DML语句组成 ,这些语句在逻辑上存在相关性,这一组DML语句要么全部成功,要么全部失败,是一个整体。

MySQL提供一种机制,保证我们达到这样的效果。事务还规定不同的客户端看到的数据是不相同的。

事务就是要做的或所做的事情 ,主要用于处理操作量大,复杂度高的数据。假设一种场景:你毕业了,学校的教务系统后台MySQL中,不再需要你的数据,要删除你的所有信息, 那么要删除你的基本信息(姓名,电话,籍贯等)的同时,也删除和你有关的其他信息,比如:你的各科成绩,你在校表现,甚至你在论坛发过的文章等。这样,就需要多条 MySQL 语句构成,那么所有这些操作合起来,就构成了一个事务。

一个 MySQL数据库,可不止一个事务在运行,同一时刻,甚至有大量的请求被包装成事务,在向MySQL服务器发起事务处理请求。而每条事务至少一条 SQL ,最多很多SQL ,这样如果大家都访问同样的表数据,在不加保护的情况,就绝对会出现问题。甚至,因为事务由多条SQL构成,那么,也会存在执行到一半出错或者不想再执行的情况,那么已经执行的怎么办呢?

所以一个完整的事务,不仅仅是简单的DML组合而成,还要满足以下4个特点:

  • 原子性(Atomicity) :事务中的所有操作要么全部完成,要么全部不完成。
  • 一致性(Consistency) :事务执行前后,数据库的状态必须保持一致。
  • 隔离性(Isolation) :一个事务的执行不能被其他事务干扰。
  • 持久性(Durability) :一旦事务提交,其对数据库的修改应该永久保存。

以上 四个属性可以简称为ACID

所以事务就是一条或者多条SQL语句组成并包含ACID

有了事务这种机制后,能够简化我们的编程模型, 不需要我们去考虑各种各样的潜在错误和并发问题.可以想一下当我们使用事务时,要么提交,要么回滚,我 们不会去考虑网络异常了,服务器宕机了,同时更改一个数据怎么办对吧?因此事务本质上是为了应用层服 务的.而不是伴随着数据库系统天生就有的。

1.1 MySQL事务支持的版本

MySQL 中只有使用了 Innodb 数据库引擎的数据库或表才支持事务, MyISAM不支持。

使用show engines可以查看。

1.2 事务的提交方式

事务的提交方式常见的有两种

  • 自动提交
  • 手动提交

使用show variables like 'autocommit';可以查看事务提交的方式。


ON表示为自动提交。

可以通过set autocommit来更改自动提交模式。值为0,表示禁止自动提交。

值为1,表示开启自动提交。

1.3 事务常见的操作方式

为了方便演示,这里创建一张测试表account。

sql 复制代码
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;

这里还需要不同的主机连接到同一个数据库中,我使用本地和服务器的mysql客户端进行连接。

使用SHOW PROCESSLIST;可以查看数据库的连接情况。

除此之外还需要将mysql的默认隔离级别设置成读未提交。
set global transaction isolation level READ UNCOMMITTED;

使用此命令设置完成后,重启客户端进行连接隔离级别才会生效。

先查看事务是否是自动提交,使用show variables like 'autocommit';命令进行查看。

ON表示开启自动提交。以下提交的方式都是自动提交的。

1.3.1 开始一个事务

开始一个事务,可以使用being或者start transaction

1.3.2 创建一个保存点

使用savepoint 保存点名进行设置一个保存点

1.3.3 回滚到保存点

使用rollback to 保存点名即可回滚到指定的保存点中。

使用rollback可以直接回滚到最开始。

使用示例-事务的开始和回滚

此时另一个客户端在查询时,也可以查询到。做到了一致性。

这样两个客户端同时操作一个表的时候,就可以有效的解决冲突了。

事务使用示例-自动回滚

当提交方式为自动提交的时候,客户端崩溃的后,MySQL会自动回滚。(隔离级别设置为提交未读)

在客户端A中使用事务进行插入。如下。

插入一条记录后,从另一个客户端B中就可以查询到,如下:

此时,客户端A崩溃了,那么MySQL就会自动回滚。

自动回滚后,在客户端B中就无法查询到插入的这条记录了。

事务使用示例-提交后,客户端崩溃后,MySQL的数据不会在受到影响。(持久化特性)

在客户端A事务中插入一条记录,然后手动提交,客户端A崩溃。

此时在另一个客户端B中进行查询,依然可以查询到。

事务做到了数据持久化,不会被影响。

事务使用示例-begin操作会自动更改提交方式,不会受MySQL是否自动提交影响

事务使用示例-单条 SQL 与事务的关系

单条SQL:

此时客户端B在进行查询的时候就无法查询到此记录。

事务:

此时客户端B在进行查询的时候依然可以查询到此记录。数据已经被持久化了。

1.4 总结

  • 只要输入begin或者start transaction,事务便必须要通过commit提交,才会持久化,与是 否设置set autocommit无关。
  • 事务可以手动回滚,同时,当操作异常,MySQL会自动回滚
  • 对于 InnoDB 每一条 SQL 语言都默认封装成事务,自动提交。(select有特殊情况,因为 MySQL 有 MVCC )

事务操作注意事项:

  • 如果没有设置保存点,也可以回滚,只能回滚到事务的开始。直接使用 rollback(前提是事务 还没有提交)。
  • 如果一个事务被提交了(commit),则不可以回退(rollback)
  • 可以选择回退到哪个保存点
  • InnoDB 支持事务, MyISAM 不支持事务
  • 开始事务可以使 start transaction 或者 beginbegin更简洁 推荐使用。

2. 事务隔离级别

  • MySQL服务可能会同时被多个客户端进程(线程)访问,访问的方式以事务的方式进行。
  • 一个事务可能由多条SQL语句构成,也就意味着任何一个事务,都有执行前、执行中和执行后三个阶段,而所谓的原子性就是让用户层要么看到执行前,要么看到执行后,执行中如果出现问题,可以随时进行回滚,所以单个事务对用户表现出来的特性就是原子性。
  • 但毕竟每个事务都有一个执行的过程,在多个事务各自执行自己的多条SQL时,仍然可能会出现互相影响的情况,比如多个事务同时访问同一张表,甚至是表中的同一条记录。
  • 数据库中,为了保证事务执行过程中尽量不受干扰,就有了一个重要特征:隔离性
  • 数据库中,允许事务受不同程度的干扰,就有了一种重要特征:隔离级别

数据库的隔离级别一共有以下四种。

  • 读未提交【Read Uncommitted】: 在该隔离级别,所有的事务都可以看到其他事务没有提交的 执行结果。(实际生产中不可能使用这种隔离级别的),但是相当于没有任何隔离性,也会有很多 并发问题,如脏读,幻读,不可重复读等,上面为了做实验方便,用的就是这个隔离性。
  • 读提交【Read Committed】 :该隔离级别是大多数数据库的默认的隔离级别(不是 MySQL 默 认的)。它满足了隔离的简单定义:一个事务只能看到其他的已经提交的事务所做的改变。这种隔离级别会引起不可重复读,即一个事务执行时,如果多次 select, 可能得到不同的结果。
  • 可重复读【Repeatable Read】: 这是 MySQL 默认的隔离级别,它确保同一个事务,在执行中,多次读取操作数据时,会看到同样的数据行。但是会有幻读问题。
  • 串行化【Serializable】: 这是事务的最高隔离级别,它通过强制事务排序,使之不可能相互冲突, 从而解决了幻读的问题。它在每个读的数据行上面加上共享锁,。但是可能会导致超时和锁竞争 (这种隔离级别太极端,实际生产基本不使用)

2.1 查看隔离性

  • SELECT @@global.tx_isolation;:查看全局的隔离级别
  • SELECT @@session.tx_isolation; 或者 SELECT @@tx_isolation;:查看当前会话的隔离级别

2.2 设置隔离级别

  • 设置当前会话 or 全局隔离级别语法:
sql 复制代码
SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | Serializable}

比如设置当前会话隔离级别为Serializable。

设置全局隔离性的话,另起一个会话就会被影响。重启MySQL客户端生效

2.2.1 读未提交【Read Uncommitted】

该隔离级别,效率虽然很高,但是问题太多,严重不建议使用。

问题如下:

1、将事务的隔离级别设置为Read Uncommitted

2、在一个终端A中开启事务,然后插入一条记录。不进行提交。

3、在另一个终端中进行查询

这就是Read Uncommitted隔离级别做严重的问题,读到了未提交的数据。这种现象称之为脏读~

2.2.2 读提交【Read Committed】

按照命名来看,对比读未提交,读提交只读已提交的数据。但是这种隔离级别有新的问题。读到的值不同。问题如下:

1、将隔离级别设置为Read Committed

2、开启事务,不提交在另一个客户端进行查询

在另一个终端中读不到。

3、进行提交,在另一个终端再次读取。

终端A使用commit提交后,在终端B中就可以查到了。

到此,读提交解决了读未提交的问题。

但是,如果在终端A更新插入一条记录,不提交,终端B就能读到了。回到了读未提交的现象。

终端B还是读到了

这种现象叫做不可重复读,解决这个问题,又有了新的隔离级别。 可重复读【Repeatable Read】

2.2.3 可重复读【Repeatable Read】

这种隔离级别解决了读未提交和读提交的问题,但是又带来了幻读的问题。如下:

1、设置隔离级别为Repeatable Read

2、开启事务,插入记录

不提交,此时在另一个终端就无法进行查询到。解决读未提交的问题。

继续在终端A中插入一条记录

此时在终端中就能查询到了。

继续插入,不提交,另一个终端也无法查到。

同样的,在终端A中插入一条记录,并提交结束事务。

然后再终端B查询终端Acommit后的纪律。

但是,多次查询后,在终端B中无法查询到A commit到的记录。

这倒也没有什么影响,也符合可重复的特点。

但是,一般的数据库在可重复读情况的时候,无法屏蔽其他事务insert的数据(为什么?因为隔离性实现是对数据加锁完成的,而insert待插入的数据因为并不存在,那么一般加锁无法屏蔽这类问题),会造成虽然大部分内容是可重复读的,但是insert的数据在可重复读情况被读取出来,导致多次查找时,会多查找出来新的记录,就如同产生了幻觉。这种现象,叫做幻读 (phantom read)。

2.2.4 串行化【Serializable】

对所有操作全部加锁,进行串行化,不会有问题,但是只要串行化,效率很低,几乎完全不会被采用。

两个终端都使用事务,当A进行更新的时候,会一直阻塞,直到终端B提交后才可以。

实际这种完全不会被采用。

2.2.5 总结

  • 事务隔离级别越严格,安全性越高,但数据库的并发性能也就越低,往往需要在两者之间找一个平衡点。
  • 不可重复读的重点是修改和删除:同样的条件, 你读取过的数据,再次读取出来发现值不一样了
  • 幻读的重点在于新增:同样的条件, 第1次和第2次读出来的记录数不一样
  • 说明: mysql 默认的隔离级别是可重复读,一般情况下不要修改
  • 上面的例子可以看出,事务也有长短事务这样的概念。事务间互相影响,指的是事务在并行执行的 时候,即都没有commit的时候,影响会比较大。

3. 事务的一致性

  • 事务执行的结果,必须使数据库从一个一致性状态,变到另一个一致性状态。当数据库只包含事务 成功提交的结果时,数据库处于一致性状态。如果系统运行发生中断,某个事务尚未完成而被迫中 断,而改未完成的事务对数据库所做的修改已被写入数据库,此时数据库就处于一种不正确(不一 致)的状态。因此一致性是通过原子性来保证的。
  • 其实一致性和用户的业务逻辑强相关,一般MySQL提供技术支持,但是一致性还是要用户业务逻辑 做支撑,也就是说,一致性,是由用户决定的。
  • 而技术上,通过AID保证C
相关推荐
小汤猿人类11 分钟前
open Feign 连接池(性能提升)
数据库
阳冬园32 分钟前
mysql数据库 主从同步
数据库·主从同步
XiaoH23343 分钟前
培训机构Day15
sql·mysql
ThisIsClark1 小时前
【后端面试总结】MySQL主从复制逻辑的技术介绍
mysql·面试·职场和发展
Mr.132 小时前
数据库的三范式是什么?
数据库
Cachel wood2 小时前
python round四舍五入和decimal库精确四舍五入
java·linux·前端·数据库·vue.js·python·前端框架
Python之栈2 小时前
【无标题】
数据库·python·mysql
风_流沙2 小时前
java 对ElasticSearch数据库操作封装工具类(对你是否适用嘞)
java·数据库·elasticsearch
亽仒凣凣2 小时前
Windows安装Redis图文教程
数据库·windows·redis
亦世凡华、2 小时前
MySQL--》如何在MySQL中打造高效优化索引
数据库·经验分享·mysql·索引·性能分析