数据库事务隔离级别举例详解(续)

一、准备工作

MySQL8中,实际运行一些并发的事务,看四个隔离级别中脏读、不可重复读和幻读的影响。
在上一篇数据库事务隔离级别举例详解中涉及的事务A和事务B,分别对应Navicat的2个会话窗口。
测试每一种隔离级别前,应该在2个会话中,先一起设置好一样的隔离级别,如下,

-- 查看默认的自动提交
SHOW VARIABLES LIKE 'autocommit';
-- 查看默认存储引擎
SHOW VARIABLES LIKE '%engine%';
-- 查看MySQL版本
SELECT VERSION();
-- 查看默认的事务隔离级别(mysql8以下)
SELECT @@tx_isolation;
SHOW GLOBAL VARIABLES LIKE 'tx_isolation';
-- 查看默认的事务隔离级别(mysql8)
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、脏读

  • 事务A会话:
-- 读未提交的脏读
SHOW SESSION VARIABLES LIKE 'transaction_isolation';
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

-- 事务A在事务B提交前查看
BEGIN; -- step1
SELECT salary FROM pay_sheet WHERE NAME = 'Jay'; -- step4
SELECT salary FROM pay_sheet WHERE NAME = 'Jay'; -- step6
COMMIT;
ROLLBACK;
  • 事务B会话:
-- 读未提交的脏读
SHOW SESSION VARIABLES LIKE 'transaction_isolation';
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

-- 事务B修改
BEGIN; -- step2
UPDATE pay_sheet SET salary = '2000' WHERE NAME = 'Jay'; -- step3
COMMIT;
ROLLBACK; -- step5

按照上面对sql标注的step次序,依次执行对应行,

  1. 事务A的step4读出salary=2000;
  2. 接着事务B的step5回滚;
  3. 然后事务A的step6又读出salary=1000。

之前step4读到的2000就是脏读

2.1.2、不可重复读

  • 事务A会话:
-- 读未提交的不可重复读
SHOW SESSION VARIABLES LIKE 'transaction_isolation';
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

-- 事务A在事务B修改前后查看
BEGIN; -- step1
SELECT salary FROM pay_sheet WHERE NAME = 'Jay'; -- step3
SELECT salary FROM pay_sheet WHERE NAME = 'Jay'; -- step5
COMMIT;
ROLLBACK;
  • 事务B会话:
-- 读未提交的不可重复读
SHOW SESSION VARIABLES LIKE 'transaction_isolation';
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

-- 事务B修改
BEGIN; -- step2
UPDATE pay_sheet SET salary = '2000' WHERE NAME = 'Jay'; -- step4
COMMIT;
ROLLBACK; -- step6

按照上面对sql标注的step次序,依次执行对应行,

  1. 事务A(在事务B修改前)的step3读出salary=1000;
  2. 接着事务B的step4修改salary为2000;
  3. 然后事务A(在事务B修改后)的step5读出salary=2000。

事务A在事务B修改前后的step3和step5读不一致就是不可重复读

2.1.3、幻读

  • 事务A会话:
-- 读未提交的幻读
SHOW SESSION VARIABLES LIKE 'transaction_isolation';
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

-- 事务A在事务B新增前后查看
BEGIN; -- step1
SELECT SUM(salary) FROM pay_sheet WHERE NAME = 'Jay'; -- step3
SELECT SUM(salary) FROM pay_sheet WHERE NAME = 'Jay'; -- step5
COMMIT;
ROLLBACK;
  • 事务B会话:
-- 读未提交的幻读
SHOW SESSION VARIABLES LIKE 'transaction_isolation';
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

-- 事务B新增
BEGIN; -- step2
INSERT INTO `pay_sheet`(`item_name`, `salary`, `name`) VALUES ('额外薪资项', 500.00, 'Jay'); -- step4
COMMIT;
ROLLBACK; --step6

按照上面对sql标注的step次序,依次执行对应行,

  1. 事务A(在事务B新增前)的step3读出sum(salary)=1000;
  2. 接着事务B的step4新增了额外的薪资项salary为500;
  3. 然后事务A(在事务B新增后)的step5读出sum(salary)=1500。

事务A在事务B新增前后的step3和step5读的总薪资不一致就是幻读

2.2、读已提交

2.2.1、不可重复读

  • 事务A会话:
-- 读已提交的不可重复读
SHOW SESSION VARIABLES LIKE 'transaction_isolation';
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

-- 事务A在事务B修改前后查看
BEGIN; -- step1
SELECT salary FROM pay_sheet WHERE NAME = 'Jay'; -- step3
SELECT salary FROM pay_sheet WHERE NAME = 'Jay'; -- step6
COMMIT;
ROLLBACK;
  • 事务B会话:
-- 读已提交的不可重复读
SHOW SESSION VARIABLES LIKE 'transaction_isolation';
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

-- 事务B修改
BEGIN; -- step2
UPDATE pay_sheet SET salary = '2000' WHERE NAME = 'Jay'; -- step4
COMMIT; -- step5
ROLLBACK;	

按照上面对sql标注的step次序,依次执行对应行,

  1. 事务A(在事务B修改前)的step3读出salary=1000;
  2. 接着事务B的step4修改salary为2000,且step5手动提交;
  3. 然后事务A(在事务B修改后)的step6读出salary=2000。

事务A在事务B修改前后的step3和step6读不一致就是不可重复读

2.2.2、幻读

  • 事务A会话:
-- 读已提交的幻读
SHOW SESSION VARIABLES LIKE 'transaction_isolation';
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

-- 事务A在事务B新增前后查看
BEGIN; -- step1
SELECT SUM(salary) FROM pay_sheet WHERE NAME = 'Jay'; -- step3
SELECT SUM(salary) FROM pay_sheet WHERE NAME = 'Jay'; -- step6
COMMIT;
ROLLBACK;
  • 事务B会话:
-- 读已提交的幻读
SHOW SESSION VARIABLES LIKE 'transaction_isolation';
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

-- 事务B新增
BEGIN; -- step2
INSERT INTO `pay_sheet` (`item_name`, `salary`, `name`) VALUES ('额外薪资项', 500.00, 'Jay'); -- step4
COMMIT; -- step5
ROLLBACK;

按照上面对sql标注的step次序,依次执行对应行,

  1. 事务A(在事务B新增前)的step3读出sum(salary)=1000;
  2. 接着事务B的step4新增了额外的薪资项salary为500,且step5手动提交;
  3. 然后事务A(在事务B新增后)的step6读出sum(salary)=1500。

事务A在事务B新增前后的step3和step6读的总薪资不一致就是幻读

2.3、可重复读

2.3.1、幻读

  • 事务A会话:
-- 可重复读的幻读
SHOW SESSION VARIABLES LIKE 'transaction_isolation';
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;

-- 事务A在事务B新增前后查看
BEGIN; -- step1
SELECT SUM(salary) FROM pay_sheet WHERE NAME = 'Jay'; -- step3
SELECT SUM(salary) FROM pay_sheet WHERE NAME = 'Jay'; -- step6
COMMIT;
ROLLBACK;
  • 事务B会话:
-- 可重复读的幻读
SHOW SESSION VARIABLES LIKE 'transaction_isolation';
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;

-- 事务B新增
BEGIN; -- step2
INSERT INTO `pay_sheet` (`item_name`, `salary`, `name`) VALUES ('额外薪资项', 500.00, 'Jay'); -- step4
COMMIT; -- step5
ROLLBACK;

按照上面对sql标注的step次序,依次执行对应行,

  1. 事务A(在事务B新增前)的step3读出sum(salary)=1000;
  2. 接着事务B的step4新增了额外的薪资项salary为500,且step5手动提交;
  3. 然后事务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、读写互斥

  • 事务A会话:
-- 串行化的读写互斥
SHOW SESSION VARIABLES LIKE 'transaction_isolation';
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;

-- 事务A先查看一些行数据
BEGIN; -- step1
SELECT salary FROM pay_sheet WHERE NAME = 'Jay'; -- step3
COMMIT; -- step5
ROLLBACK;
  • 事务B会话:
-- 串行化的读写互斥
SHOW SESSION VARIABLES LIKE 'transaction_isolation';
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;

-- 事务B尝试修改事务A刚刚读的数据
BEGIN; -- step2
SELECT salary FROM pay_sheet WHERE NAME = 'Tony'; -- step4
UPDATE pay_sheet SET salary = '2000' WHERE NAME = 'Jay'; -- step4
COMMIT;
ROLLBACK;

按照上面对sql标注的step次序,依次执行对应行,

  1. 事务A的step3读出Jay的salary=1000(此时Jay这一行记录就被锁住了);
  2. 接着事务B的step4先尝试读取Tony这一行,成功(Tony这一行没有被锁);
  3. 但是事务B的step4也尝试修改Jay的salary为2000,发现会话阻塞(一直在等待释放Jay这一行记录的锁);
  4. 然后事务A的step5提交或回滚,与此同时事务B可以自动修改Jay的salary(获取到了锁)。

如果事务A一直不释放,事务B的会话报错锁等待超时:
Lock wait timeout exceeded; try restarting transaction, Time: 51.563000s
锁等待超时

2.4.2、写读互斥

  • 事务A会话:
-- 串行化的写读互斥
SHOW SESSION VARIABLES LIKE 'transaction_isolation';
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;

-- 事务A尝试修改事务B刚刚修改的数据
BEGIN; -- step1
SELECT salary FROM pay_sheet WHERE NAME = 'Jay'; -- step4
COMMIT;
ROLLBACK;
  • 事务B会话:
-- 串行化的写读互斥
SHOW SESSION VARIABLES LIKE 'transaction_isolation';
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;

-- 事务B先查看一些行数据
BEGIN; -- step2
UPDATE pay_sheet SET salary = '2000' WHERE NAME = 'Jay'; -- step3
COMMIT;
ROLLBACK; -- step5

就相当于把上面读写互斥的顺序反过来,

  1. 事务B的step3修改Jay的salary为2000(此时Jay这一行记录就被锁住了);
  2. 接着事务A的step4也尝试读取Jay的salary,发现会话阻塞(一直在等待释放Jay这一行记录的锁);
  3. 然后事务B的step5提交或回滚,与此同时事务A才自动查询到Jay的salary(获取到了锁)。

2.4.3、写写互斥

  • 事务A会话:
-- 串行化的写写互斥
SHOW SESSION VARIABLES LIKE 'transaction_isolation';
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;

-- 事务A再尝试修改事务B刚刚修改的数据
BEGIN; -- step1
UPDATE pay_sheet SET salary = '3000' WHERE NAME = 'Jay'; -- step4
COMMIT;
ROLLBACK;
  • 事务B会话:
-- 串行化的写写互斥
SHOW SESSION VARIABLES LIKE 'transaction_isolation';
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;

-- 事务B先修改一些行数据
BEGIN; -- step2
UPDATE pay_sheet SET salary = '2000' WHERE NAME = 'Jay'; -- step3
COMMIT;
ROLLBACK; -- step5
  1. 事务B的step3先修改Jay的salary为2000(此时Jay这一行记录就被锁住了);
  2. 接着事务A的step4也尝试修改Jay的salary为3000,发现会话阻塞(一直在等待释放Jay这一行记录的锁);
  3. 然后事务B的step5提交或回滚,与此同时事务A才可以自动修改Jay的salary为3000(获取到了锁)。

2.4.4、读读不互斥

  • 事务A会话:
-- 串行化的读读不互斥
SHOW SESSION VARIABLES LIKE 'transaction_isolation';
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;

-- 事务A先查看Jay的这一行数据
BEGIN; -- step1
SELECT salary FROM pay_sheet WHERE NAME = 'Jay'; -- step3
COMMIT;
ROLLBACK; -- step5
  • 事务B会话:
-- 串行化的读读不互斥
SHOW SESSION VARIABLES LIKE 'transaction_isolation';
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;

-- 事务B也尝试查看Jay的这一行数据
BEGIN; -- step2
SELECT salary FROM pay_sheet WHERE NAME = 'Jay'; -- step4
COMMIT;
ROLLBACK;

事务A和事务B的读都不阻塞。

猜你喜欢

转载自blog.csdn.net/songzehao/article/details/124021068
今日推荐