目录
介绍
实操
自动提交事务
手动提交事务
张飞自减回滚
张飞关羽转账回滚
mysql事务隔离级别
读未提交
读已提交
可重复读
可串行化
介绍
MySQL的事务处理是数据库管理系统(DBMS)提供的一种机制,用于确保一系列的数据库操作能够作为一个完整的、不可分割的工作单元来执行。事务处理的主要目的是维护数据的完整性和一致性,特别是在高并发或多用户环境中。
事务具有四个关键属性,通常称为ACID特性:
-
原子性(Atomicity):事务中的所有操作要么全部完成,要么一个也不做。如果事务的一部分失败,那么整个事务都将回滚到开始之前的状态,确保数据的完整性。
-
一致性(Consistency):事务的执行应该让数据库从一个一致性状态转变到另一个一致性状态。这意味着事务执行前后的数据应该满足所有的约束和业务规则。
-
隔离性(Isolation):事务的执行应该是独立的,不受其他事务的影响。即使多个事务同时进行,它们也应该像单独执行一样,不会相互干扰。
-
持久性(Durability):一旦事务被提交,它对数据库的改变将是永久的,即使在系统故障后也能恢复。
在MySQL中,事务处理主要通过以下命令控制:
START TRANSACTION
或BEGIN
:开始一个新的事务。COMMIT
:提交事务,使其更改成为永久的。ROLLBACK
:回滚事务,撤销所有未提交的更改。
MySQL的InnoDB存储引擎支持事务处理,而MyISAM等其他引擎则不支持。这是因为InnoDB使用行级锁定和事务日志来支持并发和恢复,而MyISAM使用的是表级锁定,不支持回滚。
事务处理在需要处理复杂业务逻辑、涉及多个表更新的情况下特别重要,比如银行转账、订单处理等场景,它可以确保数据的一致性和安全性。
实操
之前在听别人讲mysql事务时总觉得非常抽象,没有结合一些具体的案例来进行说明,搞得云里雾里的,下面模拟些实际场景来说明mysql的事务
自动提交事务
mysql默认是自动提交事务的,可以查看下mysql的事务提交方式
查看事务提交状态
select @@autocommit;
下面想象一个场景:
有两个人在互相转账或去银行取钱,A首先要查询下自己的余额,然后要转给B 1000块,那么这里是需要手写三个sql
查询A余额sql
减掉A余额sql
增加B余额sql
建表进行模拟数据
此处模拟张飞转账给关羽1000
此时张飞,关羽都是3000,下面进行转账:
直接选中三条sql一起执行
查看张飞,关羽余额
此时张飞,关羽余额已正确转账
那么如果在前两条sql正确执行后,在执行第三条sql前业务出现某种异常,此时还能正常转账吗.先将两者金额都调整回3000
直接在第二条sql和第三条sql之间写一些非sql语句来进行模拟异常
全选再次执行
查看转账结果
此时可以看到张飞减余额成功,但是关羽的余额并没有增加,此时就出现了问题,因为mysql的事务此时是隐式自动 提交的,也就是每条sql都是单独的事务进行提交,三条sql并没有作为一整个事务进行执行,从而导致了这种问题的发生
手动提交事务
我们可以将mysql的事务提交调整为手动提交,然后当事务中的各个sql有异常时进行事务回滚就可以规避前面的问题
调整mysql事务为手动提交
set @@autocommit = 0;
查看事务提交方式
张飞自减回滚
先将张飞减1000
a点保存点做的事情是将张飞金额减了1000
再进行保存点b
保存点b将金额又减了500
此时事务还没有提交,我们可以根据保存点来进行回退到指定的状态
还可以再回退到a点
注意
如果rollback 没有指定回退的保存点,默认回到第一个保存点
如果有多个顺序保存点 a-->b-->c ,在rollback时倒序按顺序进行回退时,可以进行逐个回退,但是如果有跳跃点进行回退时,回退到指定的点后不可以再向后回退,只能向前回退 比如:a-->b-->c,进行回退时,可以c-->b-->a,进行回退. 如果是一开始就回退到b点,那么后续就只能向b的前面进行回退,不能再向后回退,如果回退点是b,只能b-->a,不能再向c进行回退,当然如果一开始就回退到了a,那么a后面的b和c就不能再回退了,需要注意回退的顺序
如果进行提交 commit,那么之前的保存点都会失效,不能再进行回退
张飞关羽转账回滚
下面进行张飞关羽的转账进行事务回滚
数据初始化
模拟报错,此时还没有进行commit,使用rollback进行回滚数据
回滚成功
mysql事务隔离级别
查看当前会话的事务隔离级别
select @@tx_isolation;
5.8以上版本
select @@transaction_isolation;
读未提交
准备两个连接mysql的会话A 和B
可以看到此时两个会话的隔离级别都是可重复读
将B会话的隔离级别调整为读未提交
set session transaction isolation level read uncommitted;
两个会话分别开启事务
两边查询下当前user表数据
都是相同的三条数据
此时,使用会话A进行新增一条数据:
此时A会话的事务还未提交,接下来看下B会话是否可以查询到
B会话读取到了A会话未提交事务的数据,此时就会有问题,如果该数据A事务将其回滚或者进行其他操作后再进行事务提交,而B会话读取到了后就将其数据进行查询或者其他操作,那么A的提交肯定会影响到B会话的数据,此时B会话读取到的数据就是脏数据
此时我们继续操作,用A会话进行数据的修改和再新增一条数据
A会话修改数据和新增数据后提交事务 此时B会话查询到了由A会话在进行修改操作并提交后的数据,每次查询造成不同的结果,从而造成了不可重复读
B会话查询到了由A会话再进行新增操作并提交事务后的数据,每次查询造成不同的结果,从而造成了幻读
此时B会话也开启了事务,但是此时B会话的数据却受到了A会话事务的影响,读取到了A事务提交的数据,这就是不合理的,B会话由于开启了事务,此时B会话中的数据就应该是一开始时开启事务时读取到的数据库的数据,不应该收到其他事务的影响
提交B会话事务,开启新的事务进行演示读已提交
读已提交
B会话上一个事务提交后,将B会话的事务隔离级别此时调整为读已提交
set session transaction isolation level read uncommitted;
两边分别开启新的事务
此时在A会话中进行新增一条数据,但并不提交事务,查看B事务是否还能读取到A事务未提交数据
可以看到当B会话的事务隔离级别调整为读已提交时,此时B会话读取不到A会话未提交事务的数据了,也就是没有出现脏读的数据
此时再用A会话进行修改数据和新增数据,并进行事务提交,查看B会话事务是否会读取到A会话事务的修改和新增的数据
此时B会话还是读取到了A会话进行提交的修改和新增数据,每次返回了不同的结果集造成了不可重复读和幻读
B会话进行事务提交,调整隔离级别为可重复读,并两边重新开启事务
可重复读
将B会话前面的事务将提交后,B会话的隔离级别调整为可重复读
set session transaction isolation level repeatable read;
两边都开启事务
先确定下此时两个会话都查询到的初始数据
此时,再使用A会话进行新增数据,修改数据后进行提交,然后使用B会话查看是否能查询到A会话的事务提交数据
可以看到此时B会话事务查询到的还是之前的原始数据,没有受到A事务的影响,没有出现脏读,不可重复读和幻读
可串行化
将前面两个事务进行提交,然后将B会话的事务隔离级别调整为可串行化
设置B会话事务隔离级别为可串行化
set session transaction isolation level serializable;
可串行化会对会话事务进行加锁,也就是当B事务进行查询数据表或者修改操作时 它会查看是否有其他事务正在操作表,如果有会进行加锁,卡在那里不动.知道其他事务进行提交后再进行操作
使用A会话进行新增数据或者查询数据,然后此时不提交,使用B会话进行查询数据,观察B会话变化
可以看到A会话进行操作后未提交事务时,用B会话进行数据查询,此时光标卡着不动了,因为已经被事务加锁了,而A事务进行提交后B 会话立马查询到了A会话提交的数据,解决了脏读,不可重复读和幻读