Ⅰ. 初识事务
一、事务 && ACID
事务指把一组 SQL 语句打包成为一个整体,在这组 SQL 的执行过程中,要么全部成功,要么全部失败。这组 SQL 语句可以是一条也可以是多条。
一个完整的事务,绝对不是简单的 sql 集合,还需要满足如下四个属性:
-
原子性 (
Atomicity) :一个事务中的所有操作,要么全部完成,要么全部不完成 。事务在执行过程中发生错误,会被回滚到事务开始前的状态,就像这个事务从来没有执行过一样。 -
一致性 (
Consistency) :一个事务的执行前后,数据必须保持一致,或者修改的数据必须符合预期 。 -
隔离性 (
Isolation) :多个事务之间不能相互影响,通过不同的隔离级别对安全和性能做出平衡 。 -
持久性 (
Durability) :事务处理结束后,对数据的修改必须保存到磁盘中,即便系统故障也不会丢失 。
之所以要使用事务,是因为在日常的业务场景中大量存在需要用事务来保证安全的情况,而有支持事务的数据库能够简化我们的编程模型,让我们专注于业务逻辑本身,而不是关心底层的事务安全问题,简单的说,事务就是为了给应用层服务而产生的 。
InnoDB引擎通过什么技术来保证事务的这四个特性的呢❓❓❓
原子性:
undo log(回滚日志)持久性:
redo log(重做日志)隔离性:
MVCC(多版本并发控制)、锁机制一致性: 由持久性、原子性、隔离性 共同来保证
Ⅱ. 使用事务
一、语法
# 开始⼀个新的事务
START TRANSACTION;
# 或
BEGIN;
# 提交当前事务,并对更改持久化保存
COMMIT;
# 回滚当前事务到某个保存点(若无指定保存点则回滚到最开始的状态),取消其更改
ROLLBACK [to 保存点];
# 设置保存点
SAVEPOINT 保存点名称二、事务的提交方式
在 MySQL 中,自动提交(Auto Commit)和手动提交(Manual Commit)是两种不同的事务提交方式。查看当前事务的提交方式指令:
show variables like 'autocommit';-
自动提交 :
-
默认情况下,
MySQL处于自动提交模式 。 -
每个
SQL语句都会被立即提交到数据库,无需手动执行提交操作 。当执行一条SQL语句后,该语句的结果就会立即生效,且无法回滚。 -
适用于简单的操作,如查询和更新单个记录。
-
-
手动提交 :
-
手动提交模式需要显式地执行提交操作,以将一组相关的
SQL语句作为一个事务进行提交。 -
在手动提交模式下,多个
SQL语句可以作为一个原子操作进行提交 ,要么全部成功提交,要么全部回滚。 -
适用于需要保证数据的一致性和完整性的复杂操作,如插入、更新、删除多个相关记录。
-
通过以下语句设置事务为自动或手动提交:
# 设置事务自动提交
SET AUTOCOMMIT=1; # 方式一
SET AUTOCOMMIT=ON; # 方式二
# 设置事务手动提交
SET AUTOCOMMIT=0; # 方式一
SET AUTOCOMMIT=OFF; # 方式二 三、注意事项⭐⭐⭐
-
只要使用
START TRANSACTION或BEGIN开启事务,就必须通过COMMIT提交才会持久化,与是否设置set autocommit无关。因为只要启动了事务,mysql 就会自动切换为手动提交模式。 -
手动提交模式下不用显式开启事务,在执行修改操作后,提交或回滚事务时直接使用
commit或rollback。 -
如果一个事务被提交了(
commit),则不可以回滚(rollback) -
回滚(
rollback)不会结束事务。 -
当出现异常情况如客户端崩溃的时候,
MySQL会自动回滚。 -
在自动提交情况下,对于
InnoDB每一条SQL语言都默认封装成事务,进行自动提交(select有特殊情况,因为MySQL有MVCC,后面会讲)
Ⅲ. 并行事务引发的问题
MySQL 服务端是允许多个客户端连接的,这意味着 MySQL 会出现同时处理多个事务的情况。那么在并发处理多个事务的时候,就可能出现 脏读 、不可重复读 、**幻读 ** 的问题。
一、脏读 dirty read
脏读是指一个事务读取了另一个事务可能尚未提交的数据 。当一个事务读取了另一个事务的未提交数据时,如果另一个事务最终回滚,则读取到的数据实际上是无效的,没有意义。
比如在事务 A 中执行了一条 INSERT 语句,在没有执行 COMMIT 的情况下,会在事务 B 中被读取到,此时如果事务 A 执行回滚操作,那么事务 B 中读取到事务 A 写入的数据将没有意义,这种现象就是 "脏读" 。
二、不可重复读 non-repeatable read
不可重复读是指在一个事务内,多次读取同一数据时,得到的【结果】不一致 。这是因为在事务执行期间,其它事务可能修改 了被读取的数据。
例如,事务 A 先对某条数据进行了查询,之后事务 B 对这条数据进行了修改,并且提交事务,事务 A 再对这条数据进行查询时,得到了事务 B 修改之后的结果,虽然这不是 "脏读",但这导致了事务 A 在同一个事务中以相同的条件查询得到了不同的值,这种现象就是 "不可重复读"。
三、幻读 phantom read
幻读是指在一个事务内,多次执行同一个查询时,得到的【结果集】不一致 。这是因为在事务执行期间,其他事务可能插入 或删除 了符合查询条件的数据。
例如,事务 A 查询了一个区间的记录得到结果集 A,事务 B 向这个区间的间隙中写入了一条记录并提交,事务 A 再查询这个区间的结果集时会查到事务 B 新写入的记录得到结果集 B,两次查询的结果集不一致,这种现象就是 "幻读"。
☠注意: 虽然 InnoDB 在可重复读隔离级别中引入了 MVCC 和 next-key lock 两种解决方案,但可重复读级别还是避免不了幻读的问题,只是在很大程度上避免了幻读问题 ,要想真正解决幻读问题,还是需要使用可序列化级别!
幻读和不可重复读的区别如下所示:
不可重复读 主要涉及到并发事务中的更新操作 ,而 幻读 主要涉及到并发事务中的插入和删除操作 。
不可重复读 关注的是查询结果的稳定性 ,而 幻读 关注的是查询结果的一致性 。
- 稳定性 关注的是系统的可靠性和鲁棒性,而 一致性 关注的是数据的正确性和一致性。
假设有 A 和 B 这两个事务同时在处理,事务 A 先开始从数据库查询账户余额大于 100 万的记录,发现共有 5 条,然后事务 B 也按相同的搜索条件也是查询出了 5 条记录。
接下来,事务 A 插入了一条余额超过 100 万的账号,并提交了事务,此时数据库超过 100 万余额的账号个数就变为 6。
然后事务 B 再次查询账户余额大于 100 万的记录,此时查询到的记录数量有 6 条,发现和前一次读到的记录数量不一样了,就感觉发生了幻觉一样,这种现象就被称为幻读。
Ⅳ. 事务的隔离性与隔离级别
一、概念
在数据库中,为了保证事务执行过程中尽量不受干扰 ,引入了一个重要特性:隔离性 。
在数据库中,允许事务受不同程度的干扰 ,引入了一种重要特征:隔离级别 。
按隔离级别高低排序如下:

针对不同的隔离级别,并发事务时可能发生的现象也会不同:

二、四种隔离级别
-
读未提交 (
read uncommitted)- 在该隔离级别中,所有的事务都可以看到其它事务没有提交的执行结果,相当于没有任何隔离性 。这种隔离级别会会引起脏读、不可重复读、幻读 。
-
读提交 (
read committed)- 在该隔离级别中,一个事务只能看到其它已经提交的事务所做的改变。这种隔离级别会引起不可重复读、幻读 。
-
可重复读 (
repeatable read)- 这是
MySQL** ** 的默认隔离级别 ,它确保同一个事务,在执行多次读取操作数据时,能看到同样的数据行。这种隔离级别会引起幻读 。
- 这是
-
串行化 (
serializable)- 这是事务的最高隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决了幻读的问题。它在每个读的数据行上面加上共享锁,但是可能会导致超时和锁竞争**(这种隔离级别太极端,导致并发效率不高,实际生产基本不使用)**
三、查看和设置隔离级别
① 查看全局隔离级别
MySQL** 默认使用全局隔离级别** ,这意味着所有会话都将使用相同的隔离级别。
mysql> select @@global.tx_isolation;
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| READ-UNCOMMITTED | # 默认是读未提交级别
+-----------------------+② 查看当前会话隔离级别
一共有两种方式,它们是等价的 ,只不过第二种写法是第一种写法的缩写而已!
mysql> select @@session.tx_isolation;
+------------------------+
| @@session.tx_isolation |
+------------------------+
| READ-UNCOMMITTED |
+------------------------+
mysql> select @@tx_isolation;
+------------------+
| @@tx_isolation |
+------------------+
| READ-UNCOMMITTED |
+------------------+③ 设置隔离级别
# 通过 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;