当前位置: 首页 > 后端技术 > Java

四种情况了解MySQL事务隔离级别

时间:2023-04-01 13:29:06 Java

@[toc]一直以来,很多朋友对MySQL的隔离级别心存疑虑。其实这个问题一点都不难。关键是看怎么说!光看理论肯定会头晕,但是如果我们通过几个实际的SQL来演示一些,大家就会发现这东西就是这么简单!今天宋哥想通过几个简单的案例来演示一下MySQL中的事务隔离级别。1、理论上,MySQL中事务有四种隔离级别,分别是:SERIALIZABLE、REPEATABLEREAD、READCOMMITTED和READUNCOMMITTED。隔离级别的四种不同含义分别如下:SERIALIZABLE如果隔离级别是序列化的,则用户当前事务依次执行。此隔离级别提供事务之间的最大隔离。REPEATABLEREAD在此隔离级别,事务不被视为一个序列。但是,当前正在执行的事务的变化对外仍然是不可见的,也就是说,如果用户在另一个事务中多次执行同一个SELECT语句,结果总是一样的。(因为正在执行的事务所产生的数据变化,外部是看不到的)。READCOMMITTEDREADCOMMITTED隔离级别不如REPEATABLEREAD隔离级别安全。READCOMMITTED级别的事务可以看到其他事务所做的数据修改。也就是说,在事务处理过程中,同一个事务的多个SELECT语句,如果其他事务修改了相应的表,可能会返回不同的结果。READUNCOMMITTEDREADUNCOMMITTED提供事务之间的最小隔离。除了容易出现幻读操作和不可重复读操作之外,处于此隔离级别的事务还可以读取其他事务尚未提交的数据。如果这个事务以其他事务未提交的变更为计算依据,那么那些未提交的已提交变更被其父事务撤销,从而导致大量的数据变更。在MySQL数据库中,默认的事务隔离级别是REPEATABLEREAD2。SQL实践下面通过几个简单的SQL来向读者验证以上理论。2.1查看隔离级别可以通过如下SQL查看数据库实例默认的全局隔离级别和当前会话的隔离级别:MySQL8之前,使用如下命令查看MySQL隔离级别:SELECT@@GLOBAL.tx_isolation,@@tx_isolation;查询结果如图:Yes看到,默认的隔离级别是REPEATABLE-READ,全局隔离级别和当前会话隔离级别。从MySQL8开始,使用如下命令查看MySQL默认隔离级别:SELECT@@GLOBAL.transaction_isolation,@@transaction_isolation;关键字已更改,但其他所有内容均相同。隔离级别可以通过如下命令修改(建议开发者修改时修改当前会话隔离级别,不要修改全局隔离级别):SETSESSIONTRANSACTIONISOLATIONLEVELREADUNCOMMITTED上面的SQL表示数据库隔离级别为当前会话设置为READUNCOMMITTED。设置成功后,再次查询隔离级别,发现当前session的隔离级别发生了变化,如图1-2所示:注意,如果只修改当前session的隔离级别,改变一个session后,隔离级别将再次改变。它会恢复到默认的隔离级别,所以我们在测试的时候,只要修改当前会话的隔离级别即可。2.2READUNCOMMITTED2.2.1准备测试数据READUNCOMMITTED是最低的隔离级别,脏读、不可重复读、幻读都在这个隔离级别,所以这里我们先看看这个隔离级别,这样大家可以明白这三个是什么问题。下面进行介绍。首先创建一个简单的表,预设两个数据,如下:表中的数据很简单,有两个用户javaboy和itboyhub,每个人的账户都有1000元。现在模拟这两个用户之间的转账操作。注意,如果你使用Navicat,不同的查询窗口对应不同的会话。如果使用SQLyog,不同的查询窗口对应同一个会话。因此,如果你使用SQLyog,你需要打开一个新的连接。在新连接中执行查询操作。2.2.2脏读当一个事务读取到另一个事务还没有提交的数据时,称为脏读。具体操作如下:首先打开两个SQL操作窗口,假设是A和B,在A窗口输入如下SQL(输入完成后不要执行):STARTTRANSACTION;UPDATEaccountsetbalance=balance+100wherename='javaboy';UPDATEaccountsetbalance=balance-100wherename='itboyhub';COMMIT;在B窗口执行如下SQL,修改默认事务隔离级别为READUNCOMMITTED,如下:SETSESSIONTRANSACTIONISOLATIONLEVELREADUNCOMMITTED接下来在B窗口输入如下SQL,输入完成后,首先执行第一行到开始交易(注意只需要一行):STARTTRANSACTION;从帐户中选择*;犯罪;然后在窗口A执行前两条SQL,即开始交易,给javaboy账户充值100元。进入B窗口,在B窗口执行第二条查询SQL(SELECT*fromuser;),结果如下:可以看到虽然A窗口的事务还没有提交,但是B窗口已经可以查询到数据了相关变化。这就是脏读问题。2.2.3不可重复读不可重复读是指一个事务连续读取同一条记录,但两次读取的数据不同,称为不可重复读。具体操作步骤如下(将两个账户中的钱恢复到操作前的1000):首先,打开A、B两个查询窗口,将B的数据库事务隔离级别设置为READUNCOMMITTED。具体的SQL参照上面的,这里不再赘述。在B窗口输入如下SQL,然后只执行前两条SQL,启动事务,查询javaboy的账号:STARTTRANSACTION;从name='javaboy'的帐户中选择*;犯罪;前两条SQL执行结果如下:在windowA中执行如下SQL,给javaboy账户增加100元,如下:STARTTRANSACTION;UPDATEaccountsetbalance=balance+100wherename='javaboy';提交;4。再次回到B窗口,执行第二次Checkjavaboy'saccount,执行如下SQL,结果如下:javaboy'saccount发生变化,即前后两次checkjavaboy'saccount结果不一致,非-可重复读取。和脏读的区别在于,脏读是看其他事务未提交的数据,而不可重复读是看其他事务提交的数据(因为当前SQL也在事务中,可能不想看其他交易已经提交数据)。2.2.4幻读幻读与不可重复读非常相似。光看名字就是错觉。我举个简单的例子。在窗口A中输入如下SQL:STARTTRANSACTION;insertintoaccount(name,balance)values('zhangsan',1000);COMMIT;然后在窗口B中输入如下SQL:STARTTRANSACTION;SELECT*fromaccount;deletefromaccountwherename='zhangsan';COMMIT;我们执行步骤如下:首先执行窗口B的前两行,开始一个事务,同时查询数据库中的数据。此时查询到的数据只有javaboy和itboyhub。执行窗口A的前两行,在数据库中添加一个名为zhangsan的用户,注意不要提交事务。执行窗口B的第二行,由于脏读问题,此时可以查询到用户zhangsan。执行B窗口第三行,删除名字为zhangsan的记录。这时候删除的时候就会出现问题。B窗口虽然可以查询到zhangsan,但是这条记录还没有提交。这是由于脏读。它就在那里,所以无法删除。就在这个时候,出现了幻觉。有张三,却删不掉。这是幻读。看完上面的案例,你应该明白脏读、不可重复读、幻读是什么意思了。2.3与READCOMMITTED和READUNCOMMITTED相比,READCOMMITTED主要解决脏读问题,没有解决不可重复读和幻读问题。将事务的隔离级别改为READCOMMITTED后,对脏读情况重复上述测试,发现脏读问题不复存在;在不可重复读的情况下重复上面的测试,发现不可重复读的问题依然存在。以上案例不适用于幻读测试。我们换一个幻读的测试用例。还有两个窗口A和B,将窗口B的隔离级别改为READCOMMITTED,然后在窗口A中输入如下测试SQL:STARTTRANSACTION;insertintoaccount(name,balance)values('zhangsan',1000);犯罪;在窗口B中输入以下测试SQL:STARTTRANSACTION;SELECT*fromaccount;insertintoaccount(name,balance)values('zhangsan',1000);COMMIT;测试方法如下:先在B窗口执行前两行SQL,打开Transaction查询数据,此时只找到javaboy和itboyhub。在窗口A执行前两行SQL,插入一条记录,但不提交事务。在windowB中执行第二行SQL,由于没有脏读问题,所以此时找不到windowA中添加的数据。在窗口B执行第三行SQL,因为name字段是唯一的,这里不能插入。就在这个时候,出现了幻觉。没有用户zhangsan,但是无法插入zhangsan。2.4与READCOMMITTED相比,REPEATABLEREAD进一步解决了不可重复读的问题,但是幻读并没有解决。REPEATABLEREAD中的幻读测试和上一节基本相同,不同的是在第二步,记得执行insertSQL后commit事务。由于REPEATABLEREAD解决了不可重复读,即使第二步提交了事务,第三步也找不到提交的数据,第四步继续插入就会出错。请注意,REPEATABLEREAD也是InnoDB引擎的默认数据库事务隔离级别2.5SERIALIZABLESERIALIZABLE提供事务之间的最大隔离。在这种隔离级别下,事务依次顺序执行,没有脏读、不可重复读、幻读问题,最安全。如果当前事务隔离级别设置为SERIALIZABLE,那么此时启动其他事务时,会被阻塞。它必须等待当前事务提交,其他事务才能成功打开。因此,之前的脏读、不可重复读、幻读问题就不存在了。会发生。3.总结总的来说,隔离级别与脏读、不可重复读、幻读的对应关系如下:隔离级别脏读、不可重复读、幻读READdoesnotallowSERIALIZABLEallowed,disallowed,disallowed,disallowed,性能关系如图:好了,这篇文章就先和小伙伴们聊这么多,大家不妨写几行SQL试试看。