MySQL-事务(TRANSACTION)
文章目录
- 1. 事务概述
- 2. 事务的四大特性(ACID)
- 3. 控制事务
- 4. 并发事务产生的问题
- 5. 事务的隔离级别
- 6. 拓展
- 6.1 InnoDB如何解决幻读?
- 6.2 MySQL实现事务的原理?
1. 事务概述
- 定义:数据库的事务( Transaction) 是一种机制、一个操作序列,包含了一组数据库操作命令。事务把所有的命令作为一个整体一起向系统提交或撤销操作请求,即这一组数据库命令要么都执行,要么都不执行,因此事务是一个不可分割的工作逻辑单元。
- 作用: 事务可以将一系列的数据操作捆绑成一个整体进行统一管理,如果某一事务执行成功,则在该事务中进行的所有数据更改均会提交,成为数据库中的永久组成部分。如果事务执行时遇到错误,则就必须取消或回滚。取消或回滚后,数据将全部恢复到操作前的状态,所有数据的更改均被清除。
2. 事务的四大特性(ACID)
- 事务具有 4 个特性,即原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability),这 4 个特性通常简称为 ACID。
- 原子性:事务是一个完整的操作,事务的各元素是不可分的(原子的),事务中的所有元素必须作为一个整体提交或回滚。如果事务中的任何元素失败,则整个事务将失败。以银行转账事务为例,如果该事务提交了,则这两个账户的数据将会更新。如果由于某种原因,事务在成功更新这两个账户之前终止了,则不会更新这两个账户的余额,并且会撤销对任何账户余额的修改,事务不能部分交。
- . 一致性:当事务完成时,数据必须处于一致状态,也就是说,在事务开始之前,数据库中存储的数据处于一致状态。在正在进行的事务中. 数据可能处于不一致的状态,如数据可能有部分被修改。然而,当事务成功完成时,数据必须再次回到已知的一致状态。通过事务对数据所做的修改不能损坏数据,或者说事务不能使数据存储处于不稳定的状态。以银行转账事务事务为例。在事务开始之前,所有账户余额的总额处于一致状态。在事务进行的过程中,一个账户余额减少了,而另一个账户余额尚未修改。因此,所有账户余额的总额处于不一致状态。事务完成以后,账户余额的总额再次恢复到一致状态。
- 隔离性:对数据进行修改的所有并发事务是彼此隔离的,这表明事务必须是独立的,它不应以任何方式依赖于或影响其他事务。修改数据的事务可以在另一个使用相同数据的事务开始之前访问这些数据,或者在另一个使用相同数据的事务结束之后访问这些数据。另外,当事务修改数据时,如果任何其他进程正在同时使用相同的数据,则直到该事务成功提交之后,对数据的修改才能生效。张三和李四之间的转账与王五和赵二之间的转账,永远是相互独立的。
- 持久性:事务的持久性指不管系统是否发生了故障,事务处理的结果都是永久的。一个事务成功完成之后,它对数据库所作的改变是永久性的,即使系统出现故障也是如此。也就是说,一旦事务被提交,事务对数据所做的任何变动都会被永久地保留在数据库中。
ACID的作用:事务的 ACID 原则保证了一个事务或者成功提交,或者失败回滚,二者必居其一。因此,它对事务的修改具有可恢复性。即当事务失败时,它对数据的修改都会恢复到该事务执行前的状态。
3. 控制事务
-
查看/设置事务提交方式:默认MySQL的事务是自动提交的,也就是说,当执行完一条DML语句时,MySQL会立即隐式的提交事务。
SELECT @@autocommit ; #查看事务提交方式 SET @@autocommit = 0 ; #设置事务手动提交
-
开启事务:
START TRANSACTION 或 BEGIN ;
-
提交事务:
COMMIT;
-
回滚事务:
ROLLBACK;
-
注意事项:
- 事务尽可能简短
- 事务的开启到结束会在数据库管理系统中保留大量资源,以保证事务的原子性、一致性、隔离性和持久性。如果在多用户系统中,较大的事务将会占用系统的大量资源,使得系统不堪重负,会影响软件的运行性能,甚至导致系统崩溃。
- 事务中访问的数据量尽量最少
- 当并发执行事务处理时,事务操作的数据量越少,事务之间对相同数据的操作就越少。
- 查询数据时尽量不要使用事务
- 对数据进行浏览查询操作并不会更新数据库的数据,因此应尽量不使用事务查询数据,避免占用过量的系统资源。
- 在事务处理过程中尽量不要出现等待用户输入的操作
- 在处理事务的过程中,如果需要等待用户输入数据,那么事务会长时间地占用资源,有可能造成系统阻塞。
4. 并发事务产生的问题
- 问题产生的原因==>事务的隔离性:当多个事务同时运行时,各事务之间相互隔离,不可互相干扰。
- 产生的问题:脏读、不可重复读和幻读。
-
脏读:一个事务读到另外一个事务还没有提交的数据。
-
不可重复读:一个事务先后读取同一条记录,但两次读取的数据不同,称之为不可重复读。
-
幻读:在同一个事务中,前后两次查询相同的范围时,得到的结果不一致,好像出现了 “幻影”。
5. 事务的隔离级别
-
为了解决并发事务所引发的问题,在数据库中引入了事务隔离级别。主要有以下几种:
隔离级别 脏读 不可重复读 幻读 Read uncommitted (读未提交) √ √ √ Read committed(读已提交) × √ √ Repeatable Read(可重复读、MySQL默认的事务隔离级别) × × √ Serializable(串行化) × × × MySQL 的事务的隔离级别由低到高分别为
READ UNCOMITTED
、READ COMMITTED
、REPEATABLE READ
、SERIALIZABLE
。低级别的隔离级别可以支持更高的并发处理,同时占用的系统资源更少。
- 读未提交:如果一个事务读取到了另一个未提交事务修改过的数据,那么这种隔离级别就称之为读未提交,隔离级别在实际应用中很少使用。
- 读已提交:如果一个事务只能读取到另一个已提交事务修改过的数据,并且其它事务每对该数据进行一次修改并提交后,该事务都能查询得到最新值,那么这种隔离级别就称之为读提交。 这是大多数数据库系统的默认事务隔离级别(例如 Oracle、 SQL Server),但不是 MySQL 默认的。
- 可重复读:如果有事务正在读取数据,就不允许有其它事务进行修改操作,可重复读是 MySQL 的默认事务隔离级别。
- 串行化:SERIALIZABLE 是最高的事务隔离级别,主要通过强制事务排序来解决幻读问题。简单来说,就是在每个读取的数据行上加上共享锁实现,这样就避免了脏读、不可重复读和幻读等问题。但是该事务隔离级别执行效率低下,且性能开销也最大,所以一般情况下不推荐使用。
-
查看事务的隔离级别:
show variables like '%tx_isolation%'; select @@tx_isolation;
提示:在 MySQL 8.0.3 中,
tx_isolation
变量被transaction_isolation
变量替换了。在 MySQL 8.0.3 版本中查询事务隔离级别,只要把上述查询语句中的tx_isolation
变量替换成transaction_isolation
变量即可。 -
修改事务的隔离级别:
SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE}
SESSION:表示修改的事务隔离级别将应用于当前 session(当前 cmd 窗口)内的所有事务;
GLOBAL:表示修改的事务隔离级别将应用于所有 session(全局)中的所有事务,且当前已经存在的 session 不受影响;
如果省略 SESSION 和 GLOBAL,表示修改的事务隔离级别将应用于当前 session 内的下一个还未开始的事务。
6. 拓展
6.1 InnoDB如何解决幻读?
-
幻读的定义:在同一个事务中,前后两次查询相同的范围时,得到的结果不一致,好像出现了 “幻影”。
-
解决方案:InnoDB 引入了间隙锁和 next-key Lock 机制来解决幻读问题 。虽然 InnoDB 中通过间隙锁的方式解决了幻读问题,但是加锁之后会影响到并发性能,因此,如果对性能要求较高的业务场景中,可以把隔离级别设置成 RC,该级别中不存在间隙锁 。
-
记录锁:当我们通过主键索引查询一条记录,并且对这条记录通过
for update
加锁,被锁定的记录在锁释放之前,其他事务无法对这条记录做任何操作。select * from student where id = 1 for update;
-
间隙锁:当我们查询某个范围的记录时,对指定区间范围的记录通过
for update
加锁,意味着在这种情况下,其他事务对这个区间的数据进行插入、更新、删除都会被锁住。select * from student where id > 2 and id < 5 for update;
-
next-key Lock :相当于间隙锁和记录锁的合集,记录锁锁定存在的记录行,间隙锁锁住记录行之间的间隙,而 next-key Lock 锁住的是两者之和。
select * from student where id > 4 for update;
因此,当通过 id>4 这样一种范围查询加锁时,会加 next-key Lock,锁定的区间范围是:(4, 7] , (7,10],(10,+∞]。
6.2 MySQL实现事务的原理?
- Mysql 里面的事务,满足 ACID 特性,所以在我看来,MySQL 的事务实现原理,就是InnoDB 是如何保证 ACID 特性的。
-
首先,A 表示 Atomic 原子性,也就是需要保证多个 DML 操作是原子的,要么都成功,要么都失败。那么,失败就意味着要对原本执行成功的数据进行回滚,所以 InnoDB 设计了一个UNDO_LOG 表,在事务执行的过程中,把修改之前的数据快照保存到UNDO_LOG 里面,一旦出现错误,就直接从UNDO_LOG里面读取数据执行反向操作就行了。
-
其次,C 表示一致性,表示数据的完整性约束没有被破坏,这个更多是依赖于业务层面的保证,数据库本身也提供了一些,比如主键的唯一余数,字段长度和类型的保证等等。
-
接着,I 表示事物的隔离性,也就是多个并行事务对同一个数据进行操作的时候,如何避免多个事务的干扰导致数据混乱的问题。InnoDB 默认的隔离级别是 RR(可重复读),然后使用了 MVCC 机制解决了脏读、不可重复读的问题,然后使用了间隙锁、next-key Lock 机制解决了幻读的问题。
-
最后一个是 D,表示持久性,也就是只要事务提交成功,那对于这个数据的结果的影响一定是永久性的。不能因为宕机或者其他原因导致数据变更失效。理论上来说,事务提交之后直接把数据持久化到磁盘就行了,但是因为随机磁盘 IO 的效率确实很低,所以 InnoDB 设计了Buffer Pool 缓冲区来优化,也就是数据发生变更的时候先更新内存缓冲区,然后在合适的时机再持久化到磁盘。那在持久化这个过程中,如果数据库宕机,就会导致数据丢失,也就无法满足持久性了。所以 InnoDB 引入了 Redo_LOG 文件,这个文件存储了数据被修改之后的值,当我们通过事务对数据进行变更操作的时候,除了修改内存缓冲区里面的数据以外,还会把本次修改的值追加到 REDO_LOG 里面。当提交事务的时候,直接把 REDO_LOG 日志刷到磁盘上持久化,一旦数据库出现宕机,在 Mysql 重启在以后可以直接用 REDO_LOG 里面保存的重写日志读取出来,再执行一遍从而保证持久性。