Featured image of post Java工程师 深入理解事务的隔离级别

Java工程师 深入理解事务的隔离级别

🌏Java工程师 深入理解事务的隔离级别 🎯 这篇文章用于记录深入探索事务的隔离级别

🎄数据来源

MySQL官方提供的测试数据库数据 https://gitcode.net/mirrors/datacharmer/test_db

🎄事务隔离

对于事务的隔离级别 其实大二一开始上数据库课的时候 就没有搞明白 导致后面每次遇到事务隔离的灵魂质问 都非常困惑 发现之前学事务隔离的问题在于 🙃没有自己动手实操 去演示每种隔离级别所存在的💩脏读💩不可重复读 💩幻读问题 一旦动手实操并试图给被人讲懂 就会发现自己并没有理解到位 所以这次打算花一天的时间把这个问题研究透彻 直接实操验证🎯 这样就能完全理解啦

需要注意的是 事务隔离这个话题是在事务并发执行的前提下讨论的 如果应用程序中从始至终只有一个事务在跑 则不存在事务隔离的问题 因为本身就只有一个事务 无需隔离其他事务对数据库的操作

🍭三个概念

💩什么是脏读

一个事务执行过程中读取到其他事务未提交的数据 ❗❗注意 举例来说 某一个时间段事务A和B并发执行 如果事务A删除/插入/修改了一条数据,然后事务B进行一个读操作,而这个读操作的范围不涉及事务A删除/插入/修改的那个数据,则事务B进行的读操作不会算作脏读;也就是说只有事务B读取的范围必须涉及到事务A删除/插入/修改的数据,这个时候才构成脏读!

💩什么是不可重复读

不可重复读是指在一个事务A中,第一次读取到的数据和事务结束前的任意时刻读到的同一批数据出现不一致的情况 这种不一致是由于其他在事务A的两次读操作期间对数据的修改、删除或者插入造成的 ❗❗ 注意 关于其他事务插入数据造成事务A的不可重复度的解释是:其他事务的插入操作覆盖了事务A之前读到的数据;而如果其他事务的插入操作没有覆盖事务A之前读到的数据,则不会造成不可重复读,这个情况就是幻读了

插入数据的操作如何影响已经存在的数据呢?比如ID为001的数据现在存在 而其他事务插入了一条数据且ID也为001 这个时候 之前的ID为001的数据就相当于被覆盖了 如果覆盖后和之前的数据不一样 则引起事务A的不可重复读

💩什么是幻读

一个事务执行过程中前后两次同一个SQL语句查询得到的结果集不一致 ❗❗ 同一个事务相同的两次SQL读操作,只要读到的数据集存在不一致(包括数据的修改、数据的增多与减少) 就构成幻读!❗❗ 所以不可重复读和幻读是下面的关系:不可重复读一定是幻读,而幻读不一定是不可重复读,幻读包含不可重复读

image-20230913134200517

另外需要注意的是 脏读不可重复读 幻读没有必然联系! 脏读只涉及到一次读, 而不可重复读 幻读涉及到两次读!把脏读不可重复读 幻读放在一起比较没有意义!

🍭四种隔离级别

隔离级别越高(对应下面的数字越大)事务之间的影响越小

1️⃣Read Uncommitted 读未提交

这种隔离级别下 每个单独的事务可以“感知”到其他事务未提交的数据操作 这些操作包括增删改 这种隔离级别 相当于各个事务之间在操场上裸奔 没有做任何隔离🙃 事务之间交织影响 非常混乱

这种隔离级别会导致所有的数据一致性问题:💩脏读💩不可重复读 💩幻读

下面的例子能够验证这种隔离级别会导致的数据一致性问题:

步骤 事务1 事务2 说明
1 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; 设置事务隔离级别READ UNCOMMITTED
2 BEGIN; BEGIN; 开启事务
3 INSERT INTO departments (dept_no, dept_name) values (‘d012’, ‘d012’); 事务1插入数据
4 SELECT * FROM departments WHERE LENGTH(dept_name) <= 5; 事务2读取到事务1未提交的数据 形成❌💩脏读
5 ROLLBACK; 事务1回滚
6 SELECT * FROM departments WHERE LENGTH(dept_name) <= 5; 事务2再次读取 前后两次读取数据不一致 构成 ❌💩幻读 和 ❌💩不可重复读 构成不可重复读的具体原因是ROLLBACK相当于删除了第一次读取的一条数据

总结:读未提交的隔离级别就相当于没做隔离… 这种隔离级别只是一个概念 不会被采用

2️⃣Read Committed 读已提交

这种隔离级别下 可以保证 每个单独的事务读取到的数据都是已经提交过(committed)的 未提交的事务所做的操作不会读取到

这种隔离级别正好可以解决✅💩脏读的问题 但是还不能解决💩不可重复读和💩幻读

下面的例子能够验证读已提交由插入数据能解决的脏读和仍然存在的幻读不可重复读 问题:

步骤 事务1 事务2 说明
1 SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; 设置事务隔离级别READ COMMITTED
2 BEGIN; BEGIN; 开启事务
3 INSERT INTO departments (dept_no, dept_name) values (‘d012’, ‘d012’); 事务1插入新数据
4 SELECT * FROM departments WHERE LENGTH(dept_name) <= 5; 事务2读不到事务1未提交的数据 解决了✅💩脏读的问题
5 COMMIT; 事务1提交
6 SELECT * FROM departments WHERE LENGTH(dept_name) <= 5; 事务2再次读取 前后两次读取数据集不一致 构成❌💩幻读 注意这里并没有构成不可重复读 因为第一次读取到的数据并没有在第二次读取到的时候发生变化(新增数据不引起不可重复读)

接下来再来验证读已提交更新数据引起的仍然存在的不可重复读幻读问题:(删除其中的某一条数据同样会引起不可重复读幻读问题)

步骤 事务1 事务2 说明
1 SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SET TRANSACTION ISOLATION LEVEL READ COMMITTED; 设置事务隔离级别READ COMMITTED
2 BEGIN; BEGIN; 开启事务
3 UPDATE departments SET dept_name = ‘UPDATE’ where dept_no = ‘d012’; 事务1修改数据
4 SELECT * FROM departments WHERE LENGTH(dept_name) <= 5; 事务2读不到事务1未提交的数据 解决了✅💩脏读的问题
5 COMMIT; 事务1提交
6 SELECT * FROM departments WHERE LENGTH(dept_name) <= 5; 事务2再次读取 读取到了事务1更新的'd012' 前后两次读取行数据不一致 形成❌💩不可重复读和❌💩幻读

总结:读已提交的隔离级别可以保证读取到的数据是已经提交的 解决了✅💩脏读的问题 但是不能隔离其他事务对数据库的插入、删除、修改操作 这些操作可能会导致💩不可重复读 和💩幻读

3️⃣Repeatable Read 可重复读

这种隔离级别下 默认支持2️⃣Read Committed 的隔离级别 所以可以解决✅💩脏读的问题

另外 可重复读可以屏蔽其他事务对数据库表的修改(修改包括对已经存在的数据插入相同ID的数据 这相当于覆盖了原来相同ID的数据)和删除 这样就解决了✅💩不可重复读 的问题

但是 这种隔离级别不能屏蔽在第一次和第二次查询之间有其他事务插入新的数据 所以一旦在两次查询操作期间有其他事务执行了插入新的数据的操作 当前事务的第二次查询的结果集就和第一次查询的结果集不一致了 这样就出现了❌💩幻读 所以 可重复读的隔离级别不能解决❌💩幻读 的问题

下面的例子能够验证可重复读能解决的和仍然存在的数据一致性问题:

步骤 事务1 事务2 说明
1 SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; 设置事务隔离级别REPEATABLE READ
2 BEGIN; BEGIN; 开启事务
3 INSERT INTO departments (dept_no, dept_name) values (‘d012’, ‘d012’); 事务1插入新数据
4 SELECT * FROM departments WHERE LENGTH(dept_name) <= 5; 事务2读不到事务1未提交的数据 解决了✅💩脏读的问题
5 COMMIT; 事务1提交
6 SELECT * FROM departments WHERE LENGTH(dept_name) <= 5; 事务2再次读取 前后两次读取数据集不一致 增加了新的数据 构成❌💩幻读

接下来继续验证可重复读隔离级别解决了不可重复读 问题:

步骤 事务1 事务2 说明
1 SET TRANSACTION ISOLATION LEVEL REPEATABLE; SET TRANSACTION ISOLATION LEVEL REPEATABLE; 设置事务隔离级别REPEATABLE
2 BEGIN; BEGIN; 开启事务
3 UPDATE departments SET dept_name = ‘UPDATE’ where dept_no = ‘d012’; 事务1更新数据
4 DELETE FROM departments WHERE dept_no = ‘d009’; 事务1删除数据
5 SELECT * FROM departments WHERE LENGTH(dept_name) <= 5; 事务2读不到事务1未提交的更新和删除 解决了✅💩脏读的问题
6 COMMIT; 事务1提交
7 SELECT * FROM departments WHERE LENGTH(dept_name) <= 5; 事务2再次读取 前后两次读取数据一致 解决了✅💩不可重复读的问题

总结:可重复读的隔离级别可以保证读取到的数据是已经提交的 解决了✅💩脏读的问题 并且能屏蔽其他事务的修改和删除操作 解决了✅💩不可重复读的问题 但是不能屏蔽其他事务的插入操作 不能解决❌💩幻读 的问题

🌱在MySQL中 如果使用InnoDB存储引擎 则默认的隔离级别是Repeatable Read

4️⃣Serialization 串行化

串行化是事务的最高隔离级别 在Serializable隔离级别下 所有的事务被同步了 也就是不存在并发的事务 所有的事务会一个接着一个执行 上一个执行完了 下一个才会开始 所以事务之间不存在影响 当然也就不会出现💩脏读💩不可重复读 💩幻读这样的一致性问题

如下面的示例,把隔离级别设置为Serialization 即使一个事务1执行了插入和更新操作 另外一个事务2也看不到、感知不到这个操作

步骤 事务1 事务2 说明
1 SET TRANSACTION ISOLATION LEVEL SERIALIZATION; SET TRANSACTION ISOLATION LEVEL SERIALIZATION; 设置事务隔离级别SERIALIZATION
2 BEGIN; BEGIN; 开启事务
3 INSERT INTO departments (dept_no, dept_name) values (‘d012’, ‘d012’); 事务1插入新数据
4 SELECT * FROM departments WHERE LENGTH(dept_name) <= 5; 事务2读不到事务1插入的数据 解决了✅💩脏读的问题
5 UPDATE departments SET dept_name = ‘UPDATE’ where dept_no = ‘d011’; 事务1更新一条行信息
6 COMMIT; 事务1提交
7 SELECT * FROM departments WHERE LENGTH(dept_name) <= 5; 事务2再次读取 读不到事务1做的插入和修改数据 解决了✅💩不可重复读的问题 也解决了✅💩幻读的问题

总结:串行化的隔离级别不存在数据一致性问题 可以屏蔽事务之间的所有操作

🍭事务隔离级别总结

  • 脏读 和 不可重复度、幻读 没有必然关系

  • 幻读包含不可重复度(不可重复读一定是幻读,而幻读不一定是不可重复读)

  • 事务的隔离级别越高 数据库的性能越低 需要权衡根据需要进行选择使用哪种隔离级别

  • MySQL如果使用InnoDB存储引擎 则默认的隔离级别是Repeatable Read

文章参考

https://www.liaoxuefeng.com/wiki/1177760294764384/1218728442198976

Licensed under CC BY-NC-SA 4.0
最后更新于 2023年9月20日