Python全栈入门到实战【数据库篇 18】MySQL事务隔离级别详解,并发数据安全的核心控制
前言
上一篇《数据库篇 17》中,我们已经掌握了事务的基本操作和ACID四大核心特性,学会了如何通过事务保证数据的原子性和一致性。本篇作为数据库篇的第十八篇,我们将深入讲解事务ACID中的隔离性,这是并发数据安全的核心。当多个事务同时操作同一批数据时,如果没有适当的隔离机制,就会出现脏读、不可重复读、幻读等严重的数据不一致问题。我们将详细讲解这三个并发问题,以及MySQL提供的四种事务隔离级别,教你如何根据业务需求选择合适的隔离级别。
本文为Python全栈开发者与数据库入门者量身打造,通过"问题定义+场景示例+实战验证"的方式,清晰讲解每一个并发问题的产生原因和解决方法,每一个隔离级别都有可直接运行的验证代码,同时重点标注生产环境中的最佳实践和常见陷阱,即使是完全没有并发编程经验的同学,也能快速掌握事务隔离级别的核心知识。
本节核心学习内容:
- 并发事务问题:脏读、不可重复读、幻读的定义与场景
- 事务隔离级别:四种隔离级别的对比与解决能力
- 隔离级别操作:查看与设置会话/全局隔离级别的语法
- 完整实战:逐级别验证并发问题的存在与解决
- 常见误区:全局与会话隔离、幻读的本质等避坑指南
- 最佳实践:生产环境隔离级别的选择原则
- 核心总结:并发问题与隔离级别速查表
文章目录
- 前言
- 一、并发事务带来的三个问题
- 1.1 脏读
- 1.2 不可重复读
- 1.3 幻读
- 二、MySQL的四种事务隔离级别
- 2.1 隔离级别解决能力对比
- 2.2 查看与设置隔离级别
- 查看当前隔离级别
- 设置隔离级别
- 三、实战演示:不同隔离级别的效果验证
- 3.1 准备测试数据
- 3.2 验证读未提交(Read uncommitted)
- 3.3 验证读已提交(Read committed)
- 3.4 验证可重复读(Repeatable Read)
- 3.5 验证串行化(Serializable)
- 四、常见误区与最佳实践
- 五、核心总结:事务并发问题与隔离级别速查表
- 并发问题总结
- 隔离级别总结
- 常用操作语法
- 六、专栏订阅
一、并发事务带来的三个问题
当多个事务同时访问数据库中的同一批数据时,如果数据库没有提供隔离机制,就会出现以下三个经典的并发问题,导致数据不一致:
| 问题 | 描述 |
|---|---|
| 脏读 | 一个事务读到了另外一个事务还没有提交的数据 |
| 不可重复读 | 一个事务先后读取同一条记录,但两次读取的数据不同 |
| 幻读 | 一个事务按照条件查询数据时,没有对应的行;但插入数据时,又发现该行已经存在,好像出现了"幻影" |
1.1 脏读
定义:一个事务读到了另一个事务未提交的修改数据。
场景示例:
- 事务A读取张三的账户余额为2000元
- 事务B修改张三的账户余额为1000元,但尚未提交事务
- 事务A再次读取张三的账户余额,得到1000元(读到了事务B未提交的数据)
- 事务B回滚,张三的账户余额恢复为2000元
- 此时事务A读到的1000元就是"脏数据",基于此数据的所有操作都是错误的
1.2 不可重复读
定义:在同一个事务内,先后两次读取同一条记录,得到的结果不同。
场景示例:
- 事务A读取张三的账户余额为2000元
- 事务B修改张三的账户余额为1000元,并提交事务
- 事务A再次读取张三的账户余额,得到1000元
- 在同一个事务A中,两次读取同一条记录的结果不同,这就是不可重复读
1.3 幻读
定义:一个事务按照条件查询数据时,没有找到对应的行;但当它尝试插入该行数据时,又发现该行已经存在,好像出现了"幻影"。
场景示例:
- 事务A查询id=1的用户,结果为空
- 事务B插入id=1的用户,并提交事务
- 事务A尝试插入id=1的用户,报错"主键冲突"
- 事务A再次查询id=1的用户,结果仍然为空(在可重复读隔离级别下)
- 对于事务A来说,id=1的用户就像"幻影"一样,看不见但又插不进去
二、MySQL的四种事务隔离级别
为了解决上述并发问题,MySQL提供了四种事务隔离级别,隔离级别从低到高依次为:
- Read uncommitted(读未提交)
- Read committed(读已提交)
- Repeatable Read(可重复读):MySQL的默认隔离级别
- Serializable(串行化)
2.1 隔离级别解决能力对比
不同的隔离级别可以解决不同的并发问题,隔离级别越高,数据越安全,但性能越低。
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| Read uncommitted | ✅ | ✅ | ✅ |
| Read committed | ❌ | ✅ | ✅ |
| Repeatable Read(默认) | ❌ | ❌ | ✅ |
| Serializable | ❌ | ❌ | ❌ |
✅ 表示存在该问题,❌ 表示解决了该问题
2.2 查看与设置隔离级别
查看当前隔离级别
-- MySQL 8.0及以上版本SELECT@@TRANSACTION_ISOLATION;-- MySQL 5.x版本SELECT@@tx_isolation;设置隔离级别
-- 设置当前会话的隔离级别(只对当前连接有效)SETSESSIONTRANSACTIONISOLATIONLEVEL{READUNCOMMITTED|READCOMMITTED|REPEATABLEREAD|SERIALIZABLE};-- 设置全局的隔离级别(对所有新连接有效,已存在的连接无效)SETGLOBALTRANSACTIONISOLATIONLEVEL{READUNCOMMITTED|READCOMMITTED|REPEATABLEREAD|SERIALIZABLE};⚠️ 重要注意:不要随便修改全局隔离级别,这会影响所有连接到数据库的应用。如果需要修改,应该只修改当前会话的隔离级别。
三、实战演示:不同隔离级别的效果验证
下面我们通过两个MySQL客户端窗口,分别模拟两个并发事务,验证不同隔离级别下的并发问题。
3.1 准备测试数据
-- 创建账户表CREATETABLEaccount(idINTPRIMARYKEYAUTO_INCREMENT,nameVARCHAR(20)NOTNULL,moneyDECIMAL(10,2)NOTNULLDEFAULT0);-- 插入测试数据INSERTINTOaccount(name,money)VALUES('张三',2000.00),('李四',2000.00);3.2 验证读未提交(Read uncommitted)
目标:验证脏读问题
| 步骤 | 客户端A(事务A) | 客户端B(事务B) |
|---|---|---|
| 1 | SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; | SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; |
| 2 | BEGIN; | BEGIN; |
| 3 | SELECT * FROM account WHERE name = '张三';结果:张三 2000.00 | |
| 4 | UPDATE account SET money = 1000 WHERE name = '张三'; | |
| 5 | SELECT * FROM account WHERE name = '张三';结果:张三 1000.00 (读到了事务B未提交的数据,脏读发生) | |
| 6 | ROLLBACK; | |
| 7 | SELECT * FROM account WHERE name = '张三';结果:张三 2000.00 |
3.3 验证读已提交(Read committed)
目标:验证脏读已解决,但不可重复读仍然存在
| 步骤 | 客户端A(事务A) | 客户端B(事务B) |
|---|---|---|
| 1 | SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; | SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; |
| 2 | BEGIN; | BEGIN; |
| 3 | SELECT * FROM account WHERE name = '张三';结果:张三 2000.00 | |
| 4 | UPDATE account SET money = 1000 WHERE name = '张三'; | |
| 5 | SELECT * FROM account WHERE name = '张三';结果:张三 2000.00 (没有读到未提交的数据,脏读已解决) | |
| 6 | COMMIT; | |
| 7 | SELECT * FROM account WHERE name = '张三';结果:张三 1000.00 (同一个事务内两次读取结果不同,不可重复读发生) |
3.4 验证可重复读(Repeatable Read)
目标:验证脏读和不可重复读已解决,但幻读仍然存在
| 步骤 | 客户端A(事务A) | 客户端B(事务B) |
|---|---|---|
| 1 | SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; | SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; |
| 2 | BEGIN; | BEGIN; |
| 3 | SELECT * FROM account WHERE id = 3;结果:空 | |
| 4 | INSERT INTO account(id, name, money) VALUES (3, '王五', 3000.00); | |
| 5 | COMMIT; | |
| 6 | SELECT * FROM account WHERE id = 3;结果:空 (可重复读保证了同一个事务内读取结果一致) | |
| 7 | INSERT INTO account(id, name, money) VALUES (3, '王五', 3000.00);报错:Duplicate entry ‘3’ for key ‘PRIMARY’ (插入失败,幻读发生) | |
| 8 | SELECT * FROM account WHERE id = 3;结果:仍然为空 |
3.5 验证串行化(Serializable)
目标:验证所有并发问题都已解决
| 步骤 | 客户端A(事务A) | 客户端B(事务B) |
|---|---|---|
| 1 | SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE; | SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE; |
| 2 | BEGIN; | BEGIN; |
| 3 | SELECT * FROM account WHERE id = 4;结果:空 | |
| 4 | INSERT INTO account(id, name, money) VALUES (4, '赵六', 4000.00);(阻塞,等待事务A结束) | |
| 5 | COMMIT; | 阻塞解除,插入成功 |
| 6 | SELECT * FROM account WHERE id = 4;结果:赵六 4000.00 (没有幻读问题) |
四、常见误区与最佳实践
- 全局与会话隔离级别的区别:
SESSION级别只对当前连接有效,GLOBAL级别对所有新连接有效,已存在的连接不受影响。生产环境中永远不要修改全局隔离级别。 - 默认隔离级别的选择:MySQL默认的
Repeatable Read隔离级别已经解决了脏读和不可重复读问题,能够满足绝大多数业务场景的需求,不需要修改。 - 串行化的性能问题:
Serializable隔离级别会将所有事务串行执行,性能极差,只有在对数据一致性要求极高的场景(如金融交易)才会使用。 - 幻读的本质:幻读不是"读不到",而是"插入失败"。MySQL的可重复读隔离级别通过间隙锁(Gap Lock)在很大程度上解决了幻读问题,但不能完全解决。
- 隔离级别与性能的权衡:隔离级别越高,数据越安全,但并发性能越低。应该根据业务对数据一致性的要求,选择最低的能够满足需求的隔离级别。
五、核心总结:事务并发问题与隔离级别速查表
为了方便后续开发时快速查阅,整理了并发问题与隔离级别的核心速查表:
并发问题总结
| 问题 | 定义 | 本质 |
|---|---|---|
| 脏读 | 读到未提交的数据 | 读取了临时数据 |
| 不可重复读 | 同一事务内两次读取同一条记录结果不同 | 数据被修改 |
| 幻读 | 查询不到但插入失败 | 数据被插入 |
隔离级别总结
| 隔离级别 | 解决的问题 | 存在的问题 | 性能 | 适用场景 |
|---|---|---|---|---|
| Read uncommitted | 无 | 脏读、不可重复读、幻读 | 最高 | 几乎不使用 |
| Read committed | 脏读 | 不可重复读、幻读 | 高 | Oracle、PostgreSQL默认 |
| Repeatable Read(默认) | 脏读、不可重复读 | 幻读 | 中 | MySQL默认,绝大多数业务场景 |
| Serializable | 所有问题 | 无 | 最低 | 对数据一致性要求极高的场景 |
常用操作语法
| 操作 | 语法 |
|---|---|
| 查看当前隔离级别 | SELECT @@TRANSACTION_ISOLATION; |
| 设置会话隔离级别 | SET SESSION TRANSACTION ISOLATION LEVEL 级别; |
| 设置全局隔离级别 | SET GLOBAL TRANSACTION ISOLATION LEVEL 级别; |
六、专栏订阅
- 专栏优点?《Python从入门到实战》,专栏内容涵盖:Python基础到高级编程、并发编程(进程/线程/协程)、网络编程(TCP/UDP/Socket)、核心内置/第三方模块、数据库核心实战、Web开发(Django/Flask/FastAPI框架)、数据库(MySQL/ORM/异步数据库)、网络爬虫(同步/异步/分布式)、AI实战、Linux部署运维等全栈核心知识,以项目驱动教学,构建清晰学习路径,适合零基础入门和进阶提升的同学,跟着一步步从入门到精通!专栏地址:https://blog.csdn.net/zsh_1314520/category_13108073.html
- 文章是永久吗?一次订阅后可永久免费查看专栏内所有文章,后续会持续更新全栈相关内容,第一时间获取最新教程!
- 有答疑交流群吗?订阅专栏后有专属的全栈学习答疑群,群内提供专业问题答疑、和众多学习者抱团取暖,一起沉淀技术、赋能成长!
- 进群方式?订阅专栏后可直接在专栏内申请加入答疑群,或私信博主沟通进群事宜:https://bbs.csdn.net/topics/620104702
- 更多干货?点赞+收藏+关注博主不迷路!博主博客链接:https://blog.csdn.net/zsh_1314520?spm=1000.2115.3001.5343,专注Python全栈技术分享,评论区留言问题会一一回复,助力大家轻松搞定Python全栈!
【原创声明】
除本文原文地址以外,如发现同款内容皆为盗版,本文已收录于《Python全栈:从入门到实战》,请勿购买盗版文章和专栏,如购买盗版内容不提供任何服务。原文地址:https://blog.csdn.net/zsh_1314520/article/details/163373625