MySQL中事务的隔离级别亲测
事务的隔离级别
四种隔离级别
事务的隔离级别分为四种:
- READ-UNCOMMITED:读未提交。
- READ-COMMITED:读已提交。
- REPEATABLE-READ:可重复读。
- SERIALIZABLE:串行化。
隔离级别作用
上述四种事务的隔离级别,是为了解决以下三种问题:
- 脏读:读到其他事务未提交的数据。
- 幻读:同一事务中,多次读取的记录条数不等,其他事务添加或删除的数据会影响到当前事务。
- 不可重复读:同一事务中,多次读取相同记录的内容会有所不同,当前事务在执行的过程中会读取到其他事务已经修改的数据。
各隔离级别解决的问题为(SQL规范中定义如下解决范围):
- READ-UNCOMMITED:脏读,幻读,不可重复读,都会发生。
- READ-COMMITED:可以解决脏读的问题。
- REPEATABLE-READ:脏读,不可重复读都不会发生了。
- SERIALIZABLE:以上问题都不会发生了。
验证以上理论
好了,纸上谈兵终觉浅,绝知此事要躬行。换句话说Talk is cheap,Show me the code。我们来验证下吧。
准备工作
先创建一张表:
CREATE TABLE `user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`username` varchar(32) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '用户名',
`age` int(11) DEFAULT NULL COMMENT '年龄',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
向其中插入一条数据:
INSERT INTO `user` (username, age ) VALUES("jxl", 18);
READ-UNCOMMITED
在你的MySQL客户端中,执行以下语句,将隔离级别设置为READ-UNCOMMITED:
select @@global.tx_isolation,@@session.tx_isolation;
set @@global.tx_isolation='READ-UNCOMMITTED', @@session.tx_isolation='READ-UNCOMMITTED';
select @@global.tx_isolation,@@session.tx_isolation;
打开一个新的窗口,也就是打开一个新的会话,确定隔离级别已经生效:
-- --------------------------查看当前隔离级别-----------------------------------
select @@global.tx_isolation,@@session.tx_isolation;
START TRANSACTION;
-- --------------------------更改当前值,不提交-----------------------------------
SELECT `age` FROM `user` WHERE id = 1;
UPDATE `age` set `age`=`age`+ 1 WHERE id = 1;
SELECT `age` FROM `user` WHERE id = 1;
我们再打开一个窗口,查看age的值是否变化:
SELECT `age` FROM `user` WHERE id = 1;
经过验证,虽然第二个会话中update后,没有提交,但是在READ-UNCOMMITED的隔离级别下,其他的事务还是可以读到没有提交的变化,age已经由18变成了19。也就是出现了脏读的情况。
READ-COMMITED
在你的MySQL客户端中,执行以下语句,将隔离级别设置为READ-COMMITED:
select @@global.tx_isolation,@@session.tx_isolation;
set @@global.tx_isolation='READ-COMMITTED', @@session.tx_isolation='READ-COMMITTED';
select @@global.tx_isolation,@@session.tx_isolation;
打开一个新的窗口,也就是打开一个新的会话,确定隔离级别已经生效:
-- --------------------------查看当前隔离级别-----------------------------------
select @@global.tx_isolation,@@session.tx_isolation;
START TRANSACTION;
-- --------------------------更改当前值,不提交-----------------------------------
SELECT `age` FROM `user` WHERE id = 1;
UPDATE `age` set `age`=`age`+ 1 WHERE id = 1;
SELECT `age` FROM `user` WHERE id = 1;
我们再打开一个窗口,查看age的值是否变化:
START TRANSACTION;
SELECT `age` FROM `user` WHERE id = 1;
经过验证,age还是18,没有查到另一个会话中已经更改的19,脏读的问题已经没有了。
验证幻读是否会发生:
我们update的那个会话,进行commit,我们在刚才的会话中继续select,看是否可以查询到update后的值:
经过验证,在查询的会话中,可以查询到更新的值,说明出现了不可重复读。
REPEATABLE-READ
在你的MySQL客户端中,执行以下语句,将隔离级别设置为REPEATABLE-READ:
select @@global.tx_isolation,@@session.tx_isolation;
set @@global.tx_isolation='REPEATABLE-READ', @@session.tx_isolation='REPEATABLE-READ';
select @@global.tx_isolation,@@session.tx_isolation;
打开一个新的窗口,也就是打开一个新的会话,确定隔离级别已经生效:
-- --------------------------查看当前隔离级别-----------------------------------
select @@global.tx_isolation,@@session.tx_isolation;
START TRANSACTION;
-- --------------------------更改当前值,不提交-----------------------------------
SELECT `age` FROM `user` WHERE id = 1;
UPDATE `age` set `age`=`age`+ 1 WHERE id = 1;
SELECT `age` FROM `user` WHERE id = 1;
我们再打开一个窗口,查看age的值是否变化:
START TRANSACTION;
SELECT `age` FROM `user` WHERE id = 1;
经过验证,age还是18,没有查到另一个会话中已经更改的19,脏读的问题已经没有了。
这时,将update的那个会话的事务commit,在查询的这个会话中继续select,发现还是update之前的值,说明在同一个会话中,查询同一条数据的内容,总是相同的,即使过程中其他的会话已经提交了事务。说明不可重复读已经没有发生了。
下面才是幻读,一个会话中:
START TRANSACTION;
SELECT * FROM `user` WHERE age >= 18;
SELECT SLEEP(20);
SELECT * FROM `user` WHERE age >= 18;
另一个会话中:
START TRANSACTION;
SELECT * FROM `user` WHERE age >= 18;
INSERT INTO `user`(username, age) VALUES ('jxl', 21);
SELECT * FROM `user` WHERE age >= 18;
COMMIT;
我们会发现第一个会话中,两次查询的结果相同,说明没有幻读的情况发生了,这是因为我们采用的InnoDB引擎,它采用了MVCC(多版本并发控制)与next-key locks(范围锁)来解决幻读的问题。
快照读:也就是单存select的情况下,使用MVCC(多版本并发控制)来解决幻读的问题。
当前读:select加锁,及insert,update,delete情况下,使用next-key locks及Gap(范围锁)来解决幻读问题。
这时你一定疑惑了,那么READ-COMMITED的隔离级别是否有幻读的问题,经过笔者的验证,读提交的隔离级别还是会发生幻读的问题。因为以上说的解决幻读的问题,指的都是InnoDB引擎在可重复读REPEATABLE-READ的隔离级别下采用的解决幻读的方法。
SERIALIZABLE
此级别就不用验证了,因为他的级别比重复读的级别高,根本就是串行的,所以脏读,不可重复读,以及幻读的问题都不会发生了。