新闻中心
mysql如何防止不可重复读
MySQL在REPEATABLE READ下通过MVCC防止不可重复读,事务基于快照读数据,确保一致性;但使用SELECT ... FOR UPDATE或降低隔离级别至READ COMMITTED时需注意数据可见性变化,显式加锁会读最新数据,可能破坏重复读;此外DDL操作也可能影响事务视图。应对策略包括显式加锁、应用层缓存或重构事务逻辑,优先推荐保持默认隔离级别以利用MVCC优势。

MySQL在默认的REPEATABLE READ隔离级别下,通过其多版本并发控制(MVCC)机制,已经很好地防止了不可重复读的问题。这意味着,在一个事务内部,即使其他事务修改了数据,你再次读取同一行,看到的数据版本依然是你事务开始时的那个版本,保持了数据的一致性视图。
核心在于MVCC。当一个事务启动时,MySQL会为它创建一个“快照”或者说一个“读视图”。所有在该事务中进行的普通SELECT查询,都会基于这个快照来读取数据。如果其他事务在此期间修改了数据,对当前事务来说是不可见的,因为它只能看到快照创建时的数据版本。这种机制确保了在事务的整个生命周期内,对同一行的多次读取都返回相同的结果,从而有效地避免了不可重复读。
既然MySQL默认就防,那还有什么情况需要注意?
这确实是个好问题,因为“默认”不代表“万无一失”或者“可以随意操作”。在我看来,虽然REPEATABLE READ配合MVCC已经很强大了,但有几个场景还是值得我们留意的。
-
显式加锁的SELECT语句: 如果你使用了
SELECT ... FOR UPDATE或者SELECT ... LOCK IN SHARE MODE,这些语句会绕过MVCC的快照读机制,直接读取最新的数据并加锁。在这种情况下,如果你在一个事务里先普通SELECT,再SELECT ... FOR UPDATE同一行,可能会看到不同的数据。-
例子: 假设账户ID为1的余额是100。
- 事务A:
START TRANSACTION; - 事务A:
SELECT balance FROM accounts WHERE id = 1;(结果是100) - 事务B:
START TRANSACTION; UPDATE accounts SET balance = 200 WHERE id = 1; COMMIT; - 事务A:
SELECT balance FROM accounts WHERE id = 1 FOR UPDATE;(此时,事务A可能会读取到200,因为它需要获取最新的数据并加锁,这与第一次的快照读结果不同。)
- 事务A:
- 思考: 这不是不可重复读的“坏处”,而是显式加锁的“目的”——为了获取最新数据并锁定,以进行后续的更新操作。但如果你的业务逻辑不清楚这一点,可能会产生误解,以为是不可重复读。
-
例子: 假设账户ID为1的余额是100。
-
更改隔离级别: 如果你因为某些性能考量或者兼容性需求,将事务隔离级别设置成了
READ COMMITTED(比如SQL Server和Oracle的默认级别),那么不可重复读就会立刻浮现。在READ COMMITTED下,每次SELECT都会读取最新的已提交数据,所以同一事务内的两次查询可能会看到不同结果。-
个人建议: 除非你非常清楚你在做什么,并且已经有完善的应对策略,否则不要轻易将MySQL的隔离级别从
REPEATABLE READ降到READ COMMITTED。
-
个人建议: 除非你非常清楚你在做什么,并且已经有完善的应对策略,否则不要轻易将MySQL的隔离级别从
DDL操作的影响: 虽然不常见,但某些DDL操作(如
ALTER TABLE)可能会隐式地提交事务,或者对表的结构进行修改,这在极端情况下也可能影响到正在进行的事务的数据视图。不过这通常不是我们讨论不可重复读时的核心点,更多是数据库操作的原子性问题。
MVCC在REPEATABLE READ级别下是如何工作的?
要理解为什么MySQL能防止不可重复读,深入了解MVCC(Multi-Version Concurrency Control,多版本并发控制)是绕不开的。这玩意儿听起来有点玄乎,但其实核心思想并不复杂。
简单来说,MVCC就是通过保存数据行的多个版本,来让并发事务看到“各自”的数据视图。在REPEATABLE READ级别下,当一个事务启动时,它会获得一个唯一的事务ID(transaction_id),并且会记录下当前活跃的事务列表。这个列表,连同事务ID,共同构成了一个“读视图”(Read View)。
-
读视图的作用: 这个读视图就像给当前事务拍了一张照片。之后,无论这个事务进行多少次
SELECT查询,它都只会去读取那些在它“拍照”时已经存在,并且已经提交的数据版本。 -
数据行版本: 每当一行数据被修改时,MySQL(特指InnoDB存储引擎)并不会直接覆盖旧数据,而是会创建一个新版本的数据行,并把旧版本的数据放到
undo log(回滚日志)里。每个数据行版本都会带有创建它的事务ID(trx_id)和删除它的事务ID(roll_pointer,指向undo log)。 -
判断可见性: 当一个事务要读取一行数据时,它会拿着自己的读视图去判断这个数据行的哪个版本是可见的。
- 如果数据行的
trx_id小于当前事务的最小活跃事务ID(也就是在当前事务启动前就已经提交的),那这个版本就可见。 - 如果数据行的
trx_id等于当前事务的ID,那也是可见的(自己修改的数据自己能看到)。 - 如果数据行的
trx_id在当前事务的活跃事务列表里,说明这个数据是在当前事务启动后由其他活跃事务修改的,那就不可见,需要沿着undo log链条找更老的版本。 - 如果数据行的
trx_id大于当前事务的最大活跃事务ID(也就是在当前事务启动后才启动的事务),那这个版本也是不可见的。
- 如果数据行的
通过这种机制,一个事务在整个生命周期内,看到的都是它启动时的数据快照,从而有效地避免了不可重复读。即使其他事务在这期间修改并提交了数据,当前事务也“视而不见”,除非它自己也去修改同一行数据。
Flex3组件和框架的生命周期 中文WORD版
在整本书中我们所涉及许多的Flex框架源码,但为了简洁,我们不总是显示所指的代码。当你阅读这本书时,要求你打开Flex Builder,或能够访问Flex3框架的源码,跟随着我们所讨论源码是怎么工作及为什么这样做。 如果你跟着阅读源码,请注意,我们经常跳过功能或者具体的代码,以便我们可以对应当前的主题。这样能防止我们远离当前的主题,主要是讲解代码的微妙之处。这并不是说那些代码的作用不重要,而是那些代码处理特别的案例,防止潜在的错误或在生命周期的后面来处理,只是我们当前没有讨论它。有需要的朋友可以下载看看
0
查看详情
如果我真的需要更低的隔离级别,比如READ COMMITTED,又想避免不可重复读怎么办?
嗯,这是一个有点矛盾的需求,但现实中确实可能遇到。比如,你可能需要READ COMMITTED来减少锁冲突,或者因为应用程序设计习惯了这种行为。在这种情况下,我们不能指望数据库的默认隔离级别来解决不可重复读,就得自己想办法了。
坦白说,没有银弹。一旦你放弃了REPEATABLE READ的全局快照,你就得接受每次读都可能看到最新已提交数据的现实。但如果某些关键数据块你需要“重复读”一致性,可以考虑以下几种策略:
-
显式加锁: 这是最直接,也是最粗暴的方式。如果你知道某个SELECT操作的结果后续会被再次读取,并且需要保持一致,你可以直接使用SELECT ... FOR UPDATE或SELECT ... LOCK IN SHARE MODE。-
SELECT ... FOR UPDATE:会给选定的行加上排他锁(X锁),直到事务结束才释放。其他事务不能修改这些行,也不能加任何锁。 -
SELECT ... LOCK IN SHARE MODE:会给选定的行加上共享锁(S锁)。其他事务可以读取这些行(也可以加S锁),但不能修改(不能加X锁)。 - 缺点: 显式加锁会增加锁竞争,降低并发性能。你必须非常清楚哪些数据需要加锁,加什么锁,以及锁的粒度。过度使用可能导致死锁。
-
-
应用层缓存: 在事务开始时,将需要保持一致性的数据读取出来,存储在应用程序的内存中(或者事务级别的缓存)。后续对这些数据的读取都从缓存中获取,而不是再次查询数据库。
- 优点: 避免了数据库层面的锁竞争,性能较好。
- 缺点: 增加了应用程序的复杂性,需要自己管理缓存的生命周期和一致性。如果数据量大,内存开销也需要考虑。这本质上是将数据库的“快照”功能搬到了应用层。
-
事务拆分与逻辑重构: 重新审视你的业务逻辑,看是否真的需要在同一个长事务中多次读取同一份数据。有时候,不可重复读的问题是因为事务粒度过大造成的。
- 思考: 能不能把一些只读操作提前,或者将修改操作封装成更小的事务?这需要对业务流程有深入的理解。
悲观锁与乐观锁的结合(针对更新场景): 如果不可重复读导致的问题主要是后续更新基于旧数据,那么结合乐观锁(版本号或时间戳)或者在更新时再次确认数据状态(悲观锁思想)会更合适。但这已经超出了单纯防止不可重复读的范畴,更多是解决并发更新的问题。
总的来说,如果你在READ COMMITTED下遇到不可重复读,并且它确实是一个问题,那么你通常需要通过显式加锁或者应用层的数据管理来弥补。但话说回来,如果可以,还是尽量利用MySQL默认的REPEATABLE READ,它已经帮你做了很多了。
以上就是mysql如何防止不可重复读的详细内容,更多请关注其它相关文章!
# 应用层
# 新品牌家电营销推广
# 惠阳网络营销推广
# 山西抖音付费营销推广平台
# 扬州网站推广运营
# 揭阳百度网站推广
# 黄山seo优化排名方案
# seo建立反馈机制
# 泰州网站建设咨询电话
# 崇阳百度关键词排名
# 荔枝营销推广和内容模板
# 启动时
# mysql
# 数据丢失
# 操作流程
# 重构
# 你在
# 是在
# 如果你
# 离线
# 加锁
# 为什么
# oracle
# 防止不可重复读
相关栏目:
【
科技资讯46185 】
【
网络学院92790 】
相关推荐:
抖音隐秘迷城小游戏入口_ 抖音冒险解谜小游戏秒玩
J*a如何使用AtomicInteger控制计数_J*a无锁计数器性能分析
Golang如何使用context实现超时取消_Golang context超时取消模式实践
小米Civi 4录制视频过暗_小米Civi 4亮度优化
在J*a中如何使用Stream.map转换元素_Stream映射操作解析
解决 Express.js 中 PUT 请求密码修改失败的路由配置指南
处理Kafka消费者会话超时:深入理解消息处理语义与幂等性
使用Python高效删除Word宏并转换DOCM为DOCX格式
照顾宝贝2小游戏免费秒玩入口
QQ邮箱稳定登录入口_QQ邮箱官方网站网页版使用
苹果手机指南针不准怎么校准 传感器校准方法详解【建议收藏】
React/Next.js中实现列表项的动态移动与状态管理:兼论唯一键的重要性
抖音小游戏合成大西瓜免费秒玩入口链接 抖音小游戏热门合集秒玩网站
印象笔记如何设离线包出差查阅_印象笔记设离线包出差查阅【离线阅读】
Lar*el如何生成PDF或Excel文件_Lar*el文档导出工具与使用教程
React中useState与局部变量:理解组件状态管理与渲染机制
c++中的std::forward_list和std::list有什么不同_c++ forward_list与list区别分析
淘宝支付提示失败如何解决 淘宝支付流程优化方法
NetBeans Ant项目:自动化将资源文件复制到dist目录的教程
如何使用Rector自动化升级旧代码_通过Composer安装和配置Rector进行代码重构
QQ邮箱登录首页官网地址2026 QQ邮箱官方网页入口
漫蛙官网正版漫画入口 漫蛙2官方网页登录地址
mc.js官网登录入口 mc.js官方登录入口最新版
Django通过AJAX异步上传图片并保存至模型的完整指南
漫蛙2正版漫画站 漫蛙2网页版快速访问入口
动漫花园资源网使用步骤_动漫花园资源网下载流程
天眼查企业查询官网入口 天眼查官方网页版查询
学习通网页版官方登录 超星学习通电脑端入口指南
百度网盘网页版入口 百度网盘网页版官方登录网址
QQ邮箱官方邮箱登录入口 QQ邮箱网页版快速访问
怎样在Excel中做仪表盘_Excel仪表盘设计与关键指标展示方法
不会效仿卡普空!《铁拳》制作人澄清:不采取赛事付费|直播|
必由学官网入口 必由学教师登录入口
Win11怎么设置鼠标主按键_Win11鼠标左右键功能互换
J*aScript中localStorage数据的获取、清洗与格式化教程
不同用户不同价格! 索尼开启账户个性化定价测试
Surface怎么安装系统 微软Surface Pro U盘重装win11教程
解决J*aScript中重复选择项的确认对话框显示问题
sublime怎么覆盖插件的默认快捷键_sublime快捷键优先级与设置
Yandex浏览器官方网页版入口 Yandex浏览器最新版官网
windows10怎么关闭系统提示音_windows10彻底静音设置方法
Lar*el递归关系中排除子孙节点的策略
html怎么在cmd下运行php文件_cmd运行html中php文件方法【教程】
抖音网页版怎么|直播|_抖音网页版开播操作指南
菜鸟取件码是什么怎么查 最全查询渠道汇总
msn官网入口地址手机版 msn官方网站手机最新链接
Win11输入法不见了怎么办_Windows11恢复语言栏显示方法
AO3最新官网入口公告_2025AO3镜像站实时查询方法
怎样更改Windows系统的默认安装路径_避免C盘爆满的终极设置【技巧】
MongoDB聚合管道:正确匹配对象数组中_id的方法


2025-09-30
浏览次数:次
返回列表
显式加锁: 这是最直接,也是最粗暴的方式。如果你知道某个