一、准备工作
在MySQL8 中,实际运行一些并发的事务,看四个隔离级别中脏读、不可重复读和幻读的影响。 在上一篇数据库事务隔离级别举例详解中涉及的事务A和事务B,分别对应Navicat的2个会话窗口。 测试每一种隔离级别前,应该在2个会话中,先一起设置好一样的隔离级别,如下,
SHOW VARIABLES LIKE 'autocommit';
SHOW VARIABLES LIKE '%engine%';
SELECT VERSION();
SELECT @@tx_isolation;
SHOW GLOBAL VARIABLES LIKE 'tx_isolation';
SELECT @@transaction_isolation;
SHOW GLOBAL VARIABLES LIKE 'transaction_isolation';
SHOW SESSION VARIABLES LIKE 'transaction_isolation';
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
实例中涉及到的建薪资明细表和插薪资项数据相关的sql脚本需要提前执行,如下,
CREATE TABLE `pay_sheet` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`item_name` varchar(255) DEFAULT NULL,
`salary` decimal(10,2) unsigned DEFAULT NULL,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
INSERT INTO `pay_sheet`(`item_name`, `salary`, `name`) VALUES ('默认薪资项', 1000.00, 'Jay');
二、测试
2.1、读未提交
2.1.1、脏读
SHOW SESSION VARIABLES LIKE 'transaction_isolation';
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
BEGIN;
SELECT salary FROM pay_sheet WHERE NAME = 'Jay';
SELECT salary FROM pay_sheet WHERE NAME = 'Jay';
COMMIT;
ROLLBACK;
SHOW SESSION VARIABLES LIKE 'transaction_isolation';
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
BEGIN;
UPDATE pay_sheet SET salary = '2000' WHERE NAME = 'Jay';
COMMIT;
ROLLBACK;
按照上面对sql标注的step次序,依次执行对应行,
- 事务A的step4读出salary=2000;
- 接着事务B的step5回滚;
- 然后事务A的step6又读出salary=1000。
之前step4读到的2000就是脏读 。
2.1.2、不可重复读
SHOW SESSION VARIABLES LIKE 'transaction_isolation';
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
BEGIN;
SELECT salary FROM pay_sheet WHERE NAME = 'Jay';
SELECT salary FROM pay_sheet WHERE NAME = 'Jay';
COMMIT;
ROLLBACK;
SHOW SESSION VARIABLES LIKE 'transaction_isolation';
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
BEGIN;
UPDATE pay_sheet SET salary = '2000' WHERE NAME = 'Jay';
COMMIT;
ROLLBACK;
按照上面对sql标注的step次序,依次执行对应行,
- 事务A(在事务B修改前)的step3读出salary=1000;
- 接着事务B的step4修改salary为2000;
- 然后事务A(在事务B修改后)的step5读出salary=2000。
事务A在事务B修改前后的step3和step5读不一致就是不可重复读 。
2.1.3、幻读
SHOW SESSION VARIABLES LIKE 'transaction_isolation';
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
BEGIN;
SELECT SUM(salary) FROM pay_sheet WHERE NAME = 'Jay';
SELECT SUM(salary) FROM pay_sheet WHERE NAME = 'Jay';
COMMIT;
ROLLBACK;
SHOW SESSION VARIABLES LIKE 'transaction_isolation';
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
BEGIN;
INSERT INTO `pay_sheet`(`item_name`, `salary`, `name`) VALUES ('额外薪资项', 500.00, 'Jay');
COMMIT;
ROLLBACK;
按照上面对sql标注的step次序,依次执行对应行,
- 事务A(在事务B新增前)的step3读出sum(salary)=1000;
- 接着事务B的step4新增了额外的薪资项salary为500;
- 然后事务A(在事务B新增后)的step5读出sum(salary)=1500。
事务A在事务B新增前后的step3和step5读的总薪资不一致就是幻读 。
2.2、读已提交
2.2.1、不可重复读
SHOW SESSION VARIABLES LIKE 'transaction_isolation';
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
BEGIN;
SELECT salary FROM pay_sheet WHERE NAME = 'Jay';
SELECT salary FROM pay_sheet WHERE NAME = 'Jay';
COMMIT;
ROLLBACK;
SHOW SESSION VARIABLES LIKE 'transaction_isolation';
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
BEGIN;
UPDATE pay_sheet SET salary = '2000' WHERE NAME = 'Jay';
COMMIT;
ROLLBACK;
按照上面对sql标注的step次序,依次执行对应行,
- 事务A(在事务B修改前)的step3读出salary=1000;
- 接着事务B的step4修改salary为2000,且step5手动提交;
- 然后事务A(在事务B修改后)的step6读出salary=2000。
事务A在事务B修改前后的step3和step6读不一致就是不可重复读 。
2.2.2、幻读
SHOW SESSION VARIABLES LIKE 'transaction_isolation';
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
BEGIN;
SELECT SUM(salary) FROM pay_sheet WHERE NAME = 'Jay';
SELECT SUM(salary) FROM pay_sheet WHERE NAME = 'Jay';
COMMIT;
ROLLBACK;
SHOW SESSION VARIABLES LIKE 'transaction_isolation';
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
BEGIN;
INSERT INTO `pay_sheet` (`item_name`, `salary`, `name`) VALUES ('额外薪资项', 500.00, 'Jay');
COMMIT;
ROLLBACK;
按照上面对sql标注的step次序,依次执行对应行,
- 事务A(在事务B新增前)的step3读出sum(salary)=1000;
- 接着事务B的step4新增了额外的薪资项salary为500,且step5手动提交;
- 然后事务A(在事务B新增后)的step6读出sum(salary)=1500。
事务A在事务B新增前后的step3和step6读的总薪资不一致就是幻读 。
2.3、可重复读
2.3.1、幻读
SHOW SESSION VARIABLES LIKE 'transaction_isolation';
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN;
SELECT SUM(salary) FROM pay_sheet WHERE NAME = 'Jay';
SELECT SUM(salary) FROM pay_sheet WHERE NAME = 'Jay';
COMMIT;
ROLLBACK;
SHOW SESSION VARIABLES LIKE 'transaction_isolation';
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN;
INSERT INTO `pay_sheet` (`item_name`, `salary`, `name`) VALUES ('额外薪资项', 500.00, 'Jay');
COMMIT;
ROLLBACK;
按照上面对sql标注的step次序,依次执行对应行,
- 事务A(在事务B新增前)的step3读出sum(salary)=1000;
- 接着事务B的step4新增了额外的薪资项salary为500,且step5手动提交;
- 然后事务A(在事务B新增后)的step6读出sum(salary)=1500。
事务A在事务B新增前后的step3和step6读的总薪资不一致就是幻读 。
2.4、串行化
串行化隔离级别中,通过互斥锁避免了脏读、不可重复读和幻读。 若事务A和事务B同时读相同的数据行不会加锁互斥;若不是读读,而是读写、写读、写写时,后面操作的事务只能等待前一个事务提交后(释放锁),才会继续(获取到锁)自动执行。 测试串行化之前,可以在pay_sheet表中,加个不相关的记录做对比,如下,
INSERT INTO `pay_sheet`(`item_name`, `salary`, `name`) VALUES ('默认薪资项', 8000.00, 'Tony');
这样表中就有了两行数据。
2.4.1、读写互斥
SHOW SESSION VARIABLES LIKE 'transaction_isolation';
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN;
SELECT salary FROM pay_sheet WHERE NAME = 'Jay';
COMMIT;
ROLLBACK;
SHOW SESSION VARIABLES LIKE 'transaction_isolation';
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN;
SELECT salary FROM pay_sheet WHERE NAME = 'Tony';
UPDATE pay_sheet SET salary = '2000' WHERE NAME = 'Jay';
COMMIT;
ROLLBACK;
按照上面对sql标注的step次序,依次执行对应行,
- 事务A的step3读出Jay的salary=1000(此时Jay这一行记录就被锁住了);
- 接着事务B的step4先尝试读取Tony这一行,成功(Tony这一行没有被锁);
- 但是事务B的step4也尝试修改Jay的salary为2000,发现会话阻塞(一直在等待释放Jay这一行记录的锁);
- 然后事务A的step5提交或回滚,与此同时事务B可以自动修改Jay的salary(获取到了锁)。
如果事务A一直不释放,事务B的会话报错锁等待超时: Lock wait timeout exceeded; try restarting transaction, Time: 51.563000s
2.4.2、写读互斥
SHOW SESSION VARIABLES LIKE 'transaction_isolation';
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN;
SELECT salary FROM pay_sheet WHERE NAME = 'Jay';
COMMIT;
ROLLBACK;
SHOW SESSION VARIABLES LIKE 'transaction_isolation';
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN;
UPDATE pay_sheet SET salary = '2000' WHERE NAME = 'Jay';
COMMIT;
ROLLBACK;
就相当于把上面读写互斥的顺序反过来,
- 事务B的step3修改Jay的salary为2000(此时Jay这一行记录就被锁住了);
- 接着事务A的step4也尝试读取Jay的salary,发现会话阻塞(一直在等待释放Jay这一行记录的锁);
- 然后事务B的step5提交或回滚,与此同时事务A才自动查询到Jay的salary(获取到了锁)。
2.4.3、写写互斥
SHOW SESSION VARIABLES LIKE 'transaction_isolation';
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN;
UPDATE pay_sheet SET salary = '3000' WHERE NAME = 'Jay';
COMMIT;
ROLLBACK;
SHOW SESSION VARIABLES LIKE 'transaction_isolation';
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN;
UPDATE pay_sheet SET salary = '2000' WHERE NAME = 'Jay';
COMMIT;
ROLLBACK;
- 事务B的step3先修改Jay的salary为2000(此时Jay这一行记录就被锁住了);
- 接着事务A的step4也尝试修改Jay的salary为3000,发现会话阻塞(一直在等待释放Jay这一行记录的锁);
- 然后事务B的step5提交或回滚,与此同时事务A才可以自动修改Jay的salary为3000(获取到了锁)。
2.4.4、读读不互斥
SHOW SESSION VARIABLES LIKE 'transaction_isolation';
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN;
SELECT salary FROM pay_sheet WHERE NAME = 'Jay';
COMMIT;
ROLLBACK;
SHOW SESSION VARIABLES LIKE 'transaction_isolation';
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN;
SELECT salary FROM pay_sheet WHERE NAME = 'Jay';
COMMIT;
ROLLBACK;
事务A和事务B的读都不阻塞。
|