MySQL —— 事务
概念
事务把⼀组SQL语句打包成为⼀个整体,在这组SQL的执行过程中,要么全部成功,要么全部失败。
这组SQL语句可以是⼀条也可以是多条。
ACID 特性
原子性
Atomicity(原子性):一个事务中的所有操作,要么全部成功,要么全部失败,不会出现只执行了一半的情况。如果事务在执行过程中发生错误,会回滚(Rollback )到事务开始前的状态,就像这个事务从来没有执行过一样。
一致性
Consistency (一致性):在事务开始之前和事务结束以后,数据库的完整性不会被破坏。这表示写入的数据必须完全符合所有的预设规则,包括数据的精度、关联性以及关于事务执行过程中服务器崩溃后如何恢复。
以转账为例:两个人之间相互转账,必须保证两个人的总金额是不变的,这就是一致性。
隔离性
Isolation (隔离性):**数据库允许多个并发事务同时对数据进行读写和修改,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。**事务可以指定不同的隔离级别,以权衡在不同的应用场景下数据库性能和安全。
以转账为例:张三进行购物,同时李四也进行购物,两人用于支付的银行卡(假设有一千元)是一样,假设两个人同时输入密码,同时发起转账申请,这时候银行那边就是接收到两个同时发生的事务,为了转账成功,两个事务的执行需要进行隔离,就是张三进行扣款100元之后,余额变为900,之后执行李四的扣款100元,余额变为800,这就是隔离性。
如果没有进行隔离,就是同时扣款100,最后余额会变成900。
持久性
Durability (持久性):事务处理结束后,对数据的修改将永久的写入存储介质,即便系统故障也不会丢失。
还是以转账为例:转账完成后,两个人的最后的金额要永久保存起来。
事务的使用
事务具备的ACID特性,是我们使用事务的原因,在我们日常的业务场景中有大量的需求要用事务来保证。支持事务的数据库能够简化我们的编程模型,不需要我们去考虑各种各样的潜在错误和并发问题,在使用事务过程中,要么提交,要么回滚,不用去考虑网络异常,服务器宕机等其他因素,因此我们经常接触的事务本质上是数据库对 ACID 模型的⼀个实现,是为应层用服务的。
要使用事务那么数据库就要支持事务,在MySQL中支持事务的存储引擎是InnoDB,可以通过show engines;
语句查看:
事务的语法:
# 开始一个新事务
start transaction;
# 或者
begin;
# 提交当前事务,并对更改持久化保存
commit;
# 回滚当前事务,取消其更改
rollback;
演示:
首先开启事务,并查看当前的学生表
把最后一行的白糖修改为赵六:
我们进行事务的回滚,事务没有进行提交,就不会进行数据的修改。
我们演示一下事务的提交:
开启事务,进行更新操作(将钱七这个名字改成王五),进行事务提交。
保存点
在事务执行的过程中设置保存点,回滚时指定保存点可以把数据恢复到保存点的状态
语法:
# 设置保存点
savepoint point_name;
# 回滚到指定保存点
rollback to point_name;
演示:
开启事务:
把王五这条记录删除,并且保存这个保存点
再次进行更新操作:
这时候突然发现,更新操作失误了,更新错人了,我们可以回滚到保存点1:
自动 / 手动提交事务
默认情况下,MySQL是自动提交事务的,也就是说我们执行的每个CRUD操作,,都会自动开启⼀个事务并在语句执行完成之后自动提交,发生异常时自动回滚。
我们来查看MySQL 事务提交是自动的还是手动的:
ON 表示是自动提交事务。
设置语法:
# 设置事务自动提交
set autocommit = 1; # 方式一
set autocommit = on; # 方式二
# 设置事务手动提交
set autocommi = 0; # 方式一
set autocommit = off; # 方式二
注意:
只要使用 START TRANSACTION
或 BEGIN
开启事务,必须要通过 COMMIT
提交才会持久化,与是否设置 SET autocommit 无关。
手动提交模式下,不用显示开启事务,执行修改操作后,提交或回滚事务时直接使用 commit 或 rollback
已提交的事务不能回滚
隔离级别
MySQL服务可以同时被多个客户端访问,每个客户端执行的DML语句以事务为基本单位,那么不同的客户端在对同⼀张表中的同⼀条数据进行修改的时候就可能出现相互影响的情况,为了保证不同的事务之间在执行的过程中不受影响,那么事务之间就需要要相互隔离,这种特性就是隔离性。
事务间不同程度的隔离,称为事务的隔离级别;不同的隔离级别在性能和安全方面做了取舍,有的隔离级别注重并发性,优点注重安全性,有的则是并发和安全始终,在MySQL的InnoDB 引擎中事务的隔离级别有四种:
read uncommitted 读未提交
read committed 读已提交
repeatable read 可重复读 (默认)
serializable 串行化
查看隔离级别
事务的隔离级别分为全局作用域和会话作用域,查看不同作用域事务的隔离级别,可以使用以下的方式:
# 全局作用域
select @@global.transaction_isolation;
# 当前会话作用域
select @@session.transaction_isolation;
MySQL默认隔离级别为可重复读
设置隔离级别 以及 不同隔离级别存在的问题
语法:
# 通过GLOBAL|SESSION分别指定不同作⽤域的事务隔离级别
SET [GLOBAL|SESSION] TRANSACTION ISOLATION LEVEL level|access_mode;
# 隔离级别
level: {
REPEATABLE READ # 可重复读
| READ COMMITTED # 读已提交
| READ UNCOMMITTED # 读未提交
| SERIALIZABLE # 串⾏化
}
# 访问模式
access_mode: {
READ WRITE # 表⽰事务可以对数据进⾏读写
| READ ONLY # 表⽰事务是只读,不能对数据进⾏读写
}
# ⽰例
# 设置全局事务隔离级别为串⾏化,后续所有事务⽣效,不影响当前事务
SET GLOBAL TRANSACTION ISOLATION LEVEL SERIALIZABLE;
# 设置会话事务隔离级别为串⾏化,当前会话后续的所有事务⽣效,不影响当前事务,可以在任何时候执⾏
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
# 如果不指定任何作⽤域,设置只针对下⼀个事务,随后的事务恢复之前的隔离级别
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
下面演示不同的隔离级别,会开启两个MySQL会话窗口。
读未提交
先设置隔离级别:
开启两个事务:
查看我们的课程表:
当我们开启事务进行修改操作的时候,发现事务还没有提交,就已经被人看到了事务未提交的内容,这种就叫做脏读
脏读就是读取到别人未提交的事务内容。
读已提交
设置隔离级别:
查看课程表:
开启两个事务:
现在我们进行修改操作,当没有提交的时候,其他会话读取的时候是读不到未提交的内容。
当我们提交事务之后,其他会话才能读到 ENGLISH 这条数据。
这时候有一个问题,就是一个会话已知进行修改与提交事务,另一个会话进行读取数据的时候就会发现数据会一直变化,可是另一个会话并不想接收到修改的数据ENGLISH,只是想查看之前的数据(英文)的时候,就会特别烦恼。
这种问题我们称为不可重复读,不可重复读指,读取到了修改的数据。
可重复读
设置隔离级别:
查看课程表:
首先开启两个事务:
其中一个事务将ENGLISH 修改未英文并提交事务,
另一个事务没有受到上面事务的影响,还是读取到的是ENGLISH
但是可重复读有一个问题:就是如果事务进行插入、删除操作的时候,影响的是结果集,在另一个事务在查询的时候,结果集也会随之发生改变。这种情况就叫做幻读。
演示一下:
首先开始两个事务,一个事务对课程表进行插入操作并且进行提交。
这时候另一个事务会话查看的时候发现多了一个结果,这就是幻读。
删除也是:
串行化
串行化就是最高程度的隔离级别。两个事务都互不影响。
一个事务进行CRUD 操作的时候,不会影响到其他事务对同一个表的查询,也就是串行化不存在脏读,不可重复读、幻读的隔离问题。
小结
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
read uncommitted | 存在 | 存在 | 存在 |
read committed | 不存在 | 存在 | 存在 |
repeatable read | 不存在 | 不存在 | 存在 |
serializable | 不存在 | 不存在 | 不存在 |
从上到下并发性依次降低,隔离性依次提高。