一、数据表准备
CREATE TABLE `student` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
`name` 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 `student` (`id`, `name`, `age`)
VALUES
(1, 'tomy', 3),
(3, 'lily', 3),
(4, 'cookie', 5),
(5, 'jerry', 4),
(6, 'sushi', 3);
二、数据库环境准备
使用两个窗口连接这个数据库,窗口A和窗口B。分别设置数据库环境:
--查看MySQL默认是什么隔离等级
show variables like "transaction_isolation";
+-----------------------+-----------------+
| Variable_name | Value |
+-----------------------+-----------------+
| transaction_isolation | REPEATABLE-READ |
+-----------------------+-----------------+
可以看到MySQL默认是可重复读(REPEATABLE-READ)隔离等级。
在两个窗口分别执行命令,使两个窗口的隔离等级都变成最低级,并关闭自动提交:
set transaction_isolation='READ-UNCOMMITTED';
set autocommit=0;
三、模拟脏读问题
以下A表示A窗口,B表示B窗口,依次执行下边的sql语句:
A: start transaction;
A: select * from student;
A: update student set age=18 where id=1;
B: start transaction;
B: select * from student;
A: rollback;
B: select * from student;
B: commit;
发现,当A更新了数据还没提交(commit)或者回退(rollback)的时候,B已经可以读取到A更新的数据。当A回退(rollback)的时候,被A更改的数据回到了初始值。此时B读到了一个并不存在的数据,就是脏数据。所以在隔离等级为读未提交(READ-UNCOMMITTED)的时候,可以发生脏读问题。
将隔离等级设置为读已提交(READ-COMMITTED)后,只要A没完成事务,B读取到的值永远是初始值。就解决了脏读问题。
四、模拟不可重复读问题
首先,将窗口A和窗口B的MySQL隔离等级都改为READ-COMMITTED:
set transaction_isolation='read-committed';
然后,依次执行下边的sql语句:
A: start transaction;
A: select * from student;
A: update student set age=19 where id=3;
B: start transaction;
B: select * from student;
-- 此时,B读到的是初始值
A: commit;
B: select * from student;
-- 此时,B读到的是更新后的值
B: commit;
可以看到,在B这个窗口,同一次事务多次读取出现数据不一致情况。这就是不可重复读问题。
将隔离等级提升为可重复读(REPEATED-READ)后,按照上边的步骤进行,会发现当A窗口将事务提交之后,B窗口还是只能查询到初始值。只有当B窗口也提交了事务之后,B窗口才能查询到更新之后的值。此时解决了不可重复读问题,但是会有幻读问题。
五、幻读问题
首先,将窗口A和窗口B的MySQL隔离等级都改为REPEATED-READ:
set transaction_isolation='repeatable-read';
然后,依次执行下边语句:
A: select * from student;
B: start transaction;
B: select * from student;
A: insert into student values (7, 'sami', 6);
A: select * from student;
B: select * from student;
-- 此时,B看不到A刚刚插入的数据
B: insert into student values (7, 'gucci', 9);
-- 虽然B这里显示没有id=7的数值,但是当插入这个条数据时,窗口进入等待状态
-- 如果此时A窗口执行commit提交,则B窗口得到一个报错;
-- 如果此时A窗口执行rollback回退,则B窗口得到一个插入成功的消息;
-- 这就是幻读问题
六、隔离的最高等级
MySQL事务隔离的最高等级是序列化(SERIALIZABLE),下边感受一下:
set transaction_isolation="SERIALIZABLE";
依次执行下边的sql语句:
A: start transaction;
B: start transaction;
A: select * from student;
-- 此时,A能够正常查询数据
B: select * from student;
-- 此时,B也能正常查询数据
A: insert into student values (11, 'fantasy', 12);
-- 此时,A执行插入语句之后,进入等待状态
B: commit;
-- 只有在B窗口完成这次事务,A窗口才能够插入成功;
B: select * from student;
-- 此时,因为A还未提交,所以B进入等待状态;
A: commit;
-- 只有A提交事务之后,B才能够查询到数据
网友评论