MyBatis 的事务管理
什么是事务?
- 事务是指将多条 SQL 语句作为一个整体执行,要么全部成功,要么全部失败,确保批量处理的“增、删、改”操作保持一致性。
- -事务是一个最小的不可在分的工作单元;通常一个事务对应一个完整的业务(例如银行账户转账业务,该业务是一个最小的工作单元)
- 一个完整的业务需要批量的DML(insert、update、delete)语句共同联合完成。
- 事务只和DML语句有关,或者说DML语句才有事务。这个和业务逻辑有关,业务逻辑不同,DML语句的个数不同。
事务相关的术语:
- 开启事务:
START TRANSACTION
- 事务结束:
END TRANSACTION
- 事务提交:
COMMIT TRANSACTION
- 事务回滚:
ROLLBACK TRANSACTION
事务的四大特征
- 原子性:事务必须是一个不可分割的操作,要么全部成功,要么全部失败。
- 持久性:一旦事务提交,对数据库的更改就是永久的,后续操作或故障不会影响已提交的事务。
- 隔离性:多个事务同时执行时,相互之间不能干扰,以避免数据不一致。
- 一致性:事务执行的结果必须使数据库从一个一致状态转换到另一个一致状态。
- 一致性 —>事务执行的结果必须是使数据库从一个一致状态变到另一个一致状态
持久性:
- 内存无法永久性存储数据,因此在出现故障时,缓存中的数据可能无法及时写回磁盘。未配置事务的 SQL 语句可能会放弃数据的持久化。而配置了事务的 SQL 语句将确保缓存中的数据被持久化到磁盘。
- 事务如何实现持久性?通过重做日志:一旦主机重新启动,可以通过重做日志恢复数据并将其写回磁盘。
如何使用事务?
# 张三给李四转账100
# 非事务提交
UPDATE act SET money = 400 WHERE id = 2;
UPDATE act SET money = 200 WHERE id = 1;
# 执行失败示例
UPDATE act SET money = 400 WHERE id = 2;
UPDATE act SET money = 200 WHERE id = 1w; -- 模拟第二条语句出现错误
# 执行成功示例
START TRANSACTION;
UPDATE act SET money = 400 WHERE id = 2;
UPDATE act SET money = 200 WHERE id = 1;
COMMIT;
# 执行失败示例
START TRANSACTION;
UPDATE act SET money = 400 WHERE id = 2;
UPDATE act SET money = 200 WHERE id = 1w;
ROLLBACK;
# 一旦事务开启,我们需要一个判断
# 如果执行成功则提交事务 (COMMIT),否则回滚 (ROLLBACK),数据库恢复为原来的状态。
事务的隔离级别
MySQL 默认的事务隔离级别为“可重复读”。为了观察并发事务产生的问题,可以将 MySQL 的事务隔离级别设置为“读未提交”。
SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
# 查看当前隔离级别
SELECT @@global.tx_isolation, @@tx_isolation;
事务的隔离等级
为了解决并发所产生的问题,我们提出了事务的隔离级别,隔离级别越高,解决并发产生的问题越多。
背景知识:读锁和写锁,在读数据时上读锁,在写数据时上写锁。
数据上读锁后不能被其他事务修改,直到读锁释放。
数据上写锁其他事务不能读也不能修改。
隔离性的隔离级别
1.读未提交 ( read uncommitted)
2.读已提交 (read committed )
3.可重复读 (repeatable read)
4.串行化 (serializable)
- 读未提交 read uncommitted
事物A和事物B,事物A未提交的数据,事物B可以读取到。
这种隔离级别最低,这种级别一般是在理论上存在,数据库隔离级别一般都高于该级别。
三种并发问题都没解决。
演示:
#1.创建表:
create table t_user(id int primary key auto_increment,username varchar(255));
#设置读未提交的隔离级别
set global transaction isolation level read uncommitted;
#查看当前隔离级别
select @@global.tx_isolation,@@tx_isolation;
MySQL8中隔离级别的变量跟之前的版本不一样,之前是tx_isolation,MySQL8改成了transaction_isolation。
- 读已提交 read committed
#事物A和事物B,事物A提交的数据,事物B才能读取到
#这种隔离级别高于读未提交
#换句话说,对方事物提交之后的数据,我当前事物才能读取到
#这种级别可以避免“脏数据”
#这种隔离级别会导致“不可重复读取”
#Oracle默认隔离级别
#设置读已提交的隔离级别
set global transaction isolation level read committed;
#查看当前隔离级别
select @@global.tx_isolation,@@tx_isolation;
可以看出,插入但没有提交,是读不出来的,提交之后才能读出来。
不可重复读和脏读的区别是,脏读读取到的是一个未提交的数据,而不可重复读读取到的是前一个事务已提交的数据。
而不可重复读在一些情况也并不影响数据的正确性,比如需要多次查询的数据也是要以最后一次查询到的数据为主。
可重复读 repeatable read
- 事务A和事务B,事务A提交之后的数据,事务B读取不到
事务B是可重复读取数据 - 这种隔离级别高于读已提交
换句话说,对方提交之后的数据,我还是读取不到
这种隔离级别可以避免“不可重复读取”,达到可重复读取
比如1点和2点读到数据是同一个 MySQL默认级别
解决了 “不可重复读” 和 “幻读” 设置可重复读的隔离级别
set global transaction isolation level repeatable read;
#查看当前隔离级别
select @@global.tx_isolation,@@tx_isolation;
可见,无论是删除还是添加,commit后都是成功的,但是另一边却还是读出原来的数据,这就是可重复读,读取的是备份数据不是真正的数据。
串行化 serializable
事务A和事务B,事务A在操作数据库时,事务B只能排队等待
这种隔离级别很少使用,吞吐量太低,用户体验差
这种级别可以避免“幻像读”,每一次读取的都是数据库中真实存在数据,事务A与事务B串行,
而不并发
#设置串行化的隔离级别
set global transaction isolation level serializable;
#查看当前隔离级别
select @@global.tx_isolation,@@tx_isolation;
-
隔离性:多个事务同时执行可能导致以下问题:
并发问题
-
脏读:事务 A 读取到了事务 B 修改但未提交的数据。
-
不可重复读:在同一事务中,多次读取同一数据,但数据的值发生了改变。
-
幻读:在一个事务中,多次查询同一范围的数据,结果却发现有新增或减少的行。
这是因为在这个事务进行的过程中,另一个事务插入或者删除了符合查询条件的数据,导致前后两次查询结果不一致
模拟事务脏读
为了模拟脏读现象,可以创建两个事务,其中一个事务修改数据但未提交,另一个事务读取该数据。在“读未提交”隔离级别下,读取事务可以读取到未提交的数据。然而,在更高的隔离级别下(如“可重复读”或“串行化”),读取事务将无法读取到未提交的数据,从而避免了脏读现象。
-
#现在做一次脏读 SELECT *FROM t_user; #模拟事务A #脏读:事务A读取到了事务B修改但是没有提交的数据 START TRANSACTION ; SELECT *FROM t_user; SELECT *FROM t_user;
#现在做一次脏读 SELECT *FROM t_user; #模拟事务B #脏读:事务A读取到了事务B修改但是没有提交的数据 START TRANSACTION ; UPDATE t_user SET money = 700 WHERE id = 1; #commit未提交 事务A读取到了700
set global transaction isolation level read uncommitted;
#查看当前隔离级别
select @@global.tx_isolation,@@tx_isolation;
修改后,未提交的事务无法查询,确保了数据安全性。
本文详细阐述了事务的四大特征、持久性的实现机制、事务的使用方法以及事务的隔离级别和并发问题。在数据库操作中,合理使用事务可以确保数据的一致性和完整性,提高系统的可靠性和稳定性。同时,了解不同隔离级别的特点和适用场景,有助于在实际应用中选择合适的事务隔离级别,以平衡性能和一致性需求。