优化MySQL并发事务:如何避免更新丢失问题?

背景描述

现在有两个事务,事务A和事务B,他们都需要修改同一行数据,这行数据原始值为100,事务A的操作是数据增加100,事务B的操作也是增加100,预期的最终结果是300,现在如何保证最终的数据是300的?什么时候会出现200的情况?

执行结果

最终数据是300的情况(可重复读) :

在可重复读(REPEATABLE READ)隔离级别下,这种情况较为容易实现。此隔离级别是MySQL默认级别,它可以有效避免脏读、不可重复读和幻读问题。

  • 执行流程:

    • 事务A开始,执行查询操作(SELECT),获取值100。
    • 事务A给这行数据加上读锁(共享锁)或排他锁(根据是否使用SELECT ... FOR UPDATESELECT ... LOCK IN SHARE MODE)。
    • 事务A在查询结果的基础上增加100,更新这行数据,然后提交事务,释放任何持有的锁。
    • 接着,事务B开始,执行查询操作,获取到更新后的值200(因为事务A已经提交)。
    • 事务B给这行数据加上读锁或排他锁。
    • 事务B在查询结果的基础上增加100,更新这行数据,然后提交事务,释放任何持有的锁。
  • 使用的锁:

    • 根据是否声明FOR UPDATELOCK IN SHARE MODE,可能会使用记录锁(行锁)或next-key锁(next-key锁是行锁和gap锁的组合,防止幻读)。
    • 如果没有使用上述子句,那么InnoDB存储引擎默认会在更新操作时自动给涉及的行加上排他锁(X锁)

最终数据是300的情况(读未提交) :

在读未提交(READ UNCOMMITTED)隔离级别下,事务可以读取到其他事务未提交的更改,也就是这个隔离级别允许发生“脏读”(Dirty Reads)

  • 执行流程:

    • 事务A在读未提交的隔离级别下启动。
    • 事务A执行 SELECT 查询操作,读取数据值为100。
    • 然后事务A增加这个值100,执行 UPDATE 操作,更新该数据行为200。此时,事务A还没有提交,所以更改是未提交的。
    • 在读未提交隔离级别下,事务B也启动。
    • 事务B执行 SELECT 操作,在这个隔离级别下,它可以读取到事务A未提交的更改,也就是它可能会读取到数据值为200。
    • 事务A提交事务后,事务A对数据行的更改变成持久的,这时候数据是200。
    • 基于事务B读到的200的数据值,事务B决定将这个数据增加100,更新操作将这个值更改为300。
    • 然后事务B提交,这时数据行的更新已经持久化为300。

出现200的情况(低级别的隔离级别) :

在较低级别的隔离级别,如读未提交(READ UNCOMMITTED)或读提交(READ COMMITTED),可能会出现最终结果是200的情况。

  • 执行流程:

    • 事务A开始,执行查询操作,获取值100。
    • 事务B几乎同时间开始,并执行查询操作,也读取到值100(尤其在READ UNCOMMITTED级别,或事务B在READ COMMITTED级别下且在事务A提交之前读取数据)。
    • 事务A增加100,更新数据库行为200,然后提交。
    • 事务B没有得到事务A的更新(因为它已经读取了数据),它也增加100,在其本地的值基础上(100),并更新数据库行为200,然后提交。
  • 使用的锁:

    • 在READ UNCOMMITTED级别下,不使用行级锁定,事务可以读取未提交的数据。
    • 在READ COMMITTED级别下,每次查询都会读取最新的已提交数据,但如果事务B在事务A提交之前读取数据,它不会再次查询数据库。
    • 在REPEATABLE READ级别下,第一次读取后,事务内的读操作会在提交前看到同样的数据,即事务B第一次查询后再次查询依然会是100,直到事务提交才会读到新的值。

出现200的情况(并发导致更新丢失) :

  • 执行流程

    • 事务A在可重复读的隔离级别下启动。
    • 事务B也在可重复读的隔离级别下启动。
    • 事务A执行SELECT查询,并且读取到这行数据的初始值100。
    • 事务A在查询结果的基础上增加100,并执行UPDATE操作,将该行的值变为200。
    • 在UPDATE操作过程中,事务A为这行数据加上排他锁。
    • 事务A提交事务,行数据变为200,并释放排他锁。
    • 因为事务B也在可重复读隔离级别下,所以即便事务A已经提交更新,事务B的SELECT操作依然会看到事务B自己快照里的数据,即历史值100。这意味着事务B在这个阶段读取到的是100,而非事务A更新后的200。
    • 此时,事务B尝试基于它自己读取到的那个100的值增加100并且执行UPDATE。
    • 由于事务A已经提交,并释放了排他锁,事务B现在确实能对这行数据进行更新操作,但是基于自己快照里的原始值100,它更新行数为200,而非基于事务A的更新结果。
    • 事务B提交事务,数据的最新状态是200。

更新丢失问题

当两个或多个事务读取同一数据,并基于此数据进行更新时,其中一个事务的更新可能会由于另一个事务的更新而被覆盖,导致第一个事务的更改丢失。什么场景下会出现这种问题呢?

假设一个场景,在事务中涉及到先查询后更新的操作,那么使用排他锁时,是在事务开始时加锁,还是说在更新数据时加锁?

这有两种常见的策略,各有不同的问题:

1、 查询时不加锁,更新时加锁
在这种策略中,事务在查询数据时不会对数据行加锁。只有当事务执行更新操作时,才会对相关数据行加上排他锁。这样做的好处是可以减少锁的持有时间,从而提高并发性能。但是,这种策略可能会导致“丢失更新”(Lost Update)问题,即两个事务可能同时读取相同的数据,然后几乎同时更新,导致一个事务的更新覆盖另一个事务的更新。

2、查询时就加锁,保持到事务结束
在这种策略中,事务在查询数据时就会对数据行加上排他锁,并且保持锁直到事务结束(提交或回滚)。这样可以确保在事务执行期间,其他事务无法修改被锁定的数据行,从而避免了“丢失更新”问题。然而,这种策略会增加锁的持有时间,可能会降低系统的并发性能。

在MySQL的InnoDB存储引擎中,默认的隔离级别是可重复读(REPEATABLE READ)。在这个隔离级别下,InnoDB会使用以下策略,查询时不加锁,更新时加锁

  • 当执行普通的SELECT查询时,InnoDB不会加排他锁。
  • 当进行写操作,如UPDATE、DELETE、INSERT时,会对涉及的数据行加上排他锁。

这里是两种不同SELECT语句的例子:

  • 普通查询(不加锁) :

    SELECT * FROM your_table WHERE id = 1;
    
  • 查询并锁定(加排他锁) :

如果你希望在读取数据时立即加锁以确保数据不会被其他事务修改,你可以使用SELECT … FOR UPDATE语句。这会对选中的行加上排他锁,直到当前事务结束。

SELECT * FROM your_table WHERE id = 1 FOR UPDATE;

那么MySQL是不是存在“更新丢失”问题呢?它是怎么解决的呢?

如何解决“更新丢失”问题呢?

我们先来看看解决“更新丢失”有哪些办法:

  1. 使用合适的隔离级别:可重复读或串行化(SERIALIZABLE)隔离级别可以防止这种问题。
  2. 悲观锁定:通过在读取数据时立即加上SELECT ... FOR UPDATE,以获取对相关数据行的排他锁,防止其他事务并发修改。
  3. 乐观锁定:通过版本号或时间戳来检查在读取数据后和更新数据前是否有其他事务对数据作了修改,如果检测到了变化,可以拒绝更新或重新读取最新数据再尝试更新。乐观锁定一般需要在数据表中使用一个额外的字段来控制版本(version)或者保存时间戳(timestamp)。

下面是如何在MySQL中使用乐观锁定的方法和步骤:

  1. 修改表结构: 在你的数据表中增加一个版本号字段或时间戳字段。例如:
ALTER TABLE your_table ADD COLUMN version INT DEFAULT 0;
  1. 读取数据: 在进行数据操作时,你需要读取数据和它当前的版本号。例如:
SELECT value, version FROM your_table WHERE id = 1;
  1. 更新数据: 更新数据时,除了更改数据之外,还需要增加版本号,并检查更新时的版本号是否跟读取时的一致。如果不一致,说明数据在读取之后有过其他更新,当前更新应该被拒绝。
UPDATE your_table 
SET value = value + 100, version = version + 1 
WHERE id = 1 AND version = read_version;
  1. 操作结果处理: 执行更新后,需要检查操作是否成功。你可以根据更新影响行数来判断,如果影响行数为0,则说明在读取数据和尝试更新之间,数据已经被其他地方更新过,此时可以选择重新读取再尝试更新或返回错误提示。
int affectedRows = preparedStatement.executeUpdate();
if (affectedRows == 0) {// Handle the optimistic lock failure (e.g., throw an exception or retry)
}

在Java代码中,可以通过检查数据库操作返回的受影响行数来实现乐观锁定逻辑。如果受影响行数为0,则说明更新并没有成功,很可能是因为版本号不一致导致的更新冲突。应用程序可以根据业务场景,选择是重新尝试、放弃操作,还是抛出异常来通知用户。

乐观锁定在并发不是特别高,冲突概率较低的系统中是非常有用的,并且相对于悲观锁,它提供了更好的性能和用户体验。但在高冲突环境下,乐观锁可能会导致大量的冲突和重试,从而可能影响系统的整体性能。

MySQL怎么解决“更新丢失”问题?

在MySQL默认的事务隔离级别(REPEATABLE READ)下,解决并发事务中的“更新丢失”的关键流程主要包括:

  1. Next-Key锁: 当事务对数据进行修改时,InnoDB会使用Next-Key锁锁定这些数据,并且锁定它们的索引记录以及索引记录之间的间隙,防止并发事务插入新的记录。
  2. 检查当前版本: 在更新数据前,事务会检查此行数据的当前版本,确保没有其他事务已经修改过这个数据。如果数据已经被修改,事务会等待或得到更新冲突的通知。
  3. 行锁定: 当执行UPDATE或DELETE操作时, InnoDB会对涉及的每行数据加上排它锁(X锁),这使得其他事务不能同时对这些行进行写操作。
  4. 使用Undo日志保持一致性读: 即便有事务正在对数据行进行修改,其他事务因为Undo日志的存在,仍然可以读取到修改前的数据快照,保证了在更新过程中可以进行一致性非锁定读取。
  5. 版本控制: InnoDB通过为每个事务维护一个版本链来实现MVCC。事务在开始时记录下系统版本号,只查询版本早于当前事务版本的数据记录,避免了读取到其他事务修改后的数据,从而防止了更新丢失。
  6. COMMIT前确认: 在事务提交前,系统会检查所有修改是否与其他事务有冲突。如果发现冲突,当前事务需要重新执行或等待其他事务完成。
  7. REDO日志: 为了保证即便数据库发生故障,已经提交的修改也不会丢失,所有的更改操作都会记录在REDO日志中。
  8. 隔离和序列化操作: 当需要修改数据时,InnoDB通过锁定机制对相关的数据行进行隔离,确保操作的序列化,防止更新丢失。

排他锁

排他锁(Exclusive Lock),通常简称为X锁,是数据库中用于控制并发访问的一种锁机制。当一个事务对数据行加上排他锁时,它可以确保在这个事务完成并释放锁之前,其他事务不能对该数据行进行任何读取或修改操作。

排他锁的主要特点如下:

  1. 写操作保护:排他锁主要用于写操作,确保在事务执行更新、删除等操作时,数据不会被其他并发事务干扰。
  2. 互斥性:当一个事务持有排他锁时,其他事务必须等待直到锁被释放。这种互斥性保证了数据的一致性和完整性。
  3. 防止死锁:通过合理的锁策略,排他锁可以帮助防止死锁的发生。例如,事务可以按照一致的顺序获取锁,避免循环等待。
  4. 锁的释放:一旦事务完成了对数据的修改并提交,排他锁就会被释放,其他事务可以开始对数据行进行操作。

在MySQL中,当执行INSERT、UPDATE或DELETE操作时,InnoDB存储引擎会自动为涉及的数据行加上排他锁。这些锁在事务提交或回滚后自动释放。排他锁是实现事务隔离的关键机制之一,它有助于维护事务的原子性和一致性。

需要注意的是,排他锁可能会导致性能问题,特别是在高并发的场景下。如果事务长时间持有锁,可能会导致其他事务等待,从而降低系统的并发性能。因此,在设计数据库操作时,应该尽量减少锁的持有时间,合理地使用锁,以及考虑使用其他并发控制机制,如乐观锁等,来平衡性能和数据一致性的需求。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/31648.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【C++进阶学习】第三弹——菱形继承和虚拟继承——菱形继承的二义性和数据冗余问题

继承(上):【C进阶学习】第一弹——继承(上)——探索代码复用的乐趣-CSDN博客 继承(下):【C进阶学习】第二弹——继承(下)——挖掘继承深处的奥秘-CSDN博客 …

教育培训系统(FastAdmin+ThinkPHP+Unipp)

引领学习新风尚 📚 引言:教育新篇章 随着科技的不断发展,教育形式也在不断创新与变革。教育培训系统作为这一变革的重要载体,正逐渐改变着我们的学习方式。今天,就让我们一起探索教育培训系统的魅力,看看它…

雷池社区版自动SSL

正常安装雷池,并配置站点,暂时不配置ssl 不使用雷池自带的证书申请。 安装(acme.sh),使用域名验证方式生成证书 先安装git yum install git 或者 apt-get install git 安装完成后使用 git clone https://gitee.com/n…

[Linux内核驱动]导出符号

导出符号 更多详细内容可以查看我的github 在Linux内核中,导出符号(Exported symbols)是指内核模块可以访问的符号,这些符号通常是函数或变量。当内核模块需要调用内核中定义的函数或访问内核中定义的变量时,这些函数或…

【代码仓库提交大文件,用Git LFS!】

开始前 Git LFS:请注意,你的远程仓库需要支持Git LFS。GitHub、GitLab和Bitbucket都支持Git LFS,但可能需要额外的配置或开启特定的支持选项。 介绍 Git LFS (Large File Storage) 是一个 Git 扩展,用于处理和存储大文件。通常…

DVWA 靶场 Authorisation Bypass 通关解析

前言 DVWA代表Damn Vulnerable Web Application,是一个用于学习和练习Web应用程序漏洞的开源漏洞应用程序。它被设计成一个易于安装和配置的漏洞应用程序,旨在帮助安全专业人员和爱好者了解和熟悉不同类型的Web应用程序漏洞。 DVWA提供了一系列的漏洞场…

在寻找电子名片在线制作免费生成?5个软件帮助你快速制作电子名片

在寻找电子名片在线制作免费生成?5个软件帮助你快速制作电子名片 当你需要快速制作电子名片时,有几款免费在线工具可以帮助你实现这个目标。这些工具提供了丰富的设计模板和元素,让你可以轻松地创建个性化、专业水平的电子名片。 1.一键logo…

python 学习积累

持续更新中 感受python的强大之case列举: 1. 生成的map list要经过json格式化写入文件,请用python实现这一需求 import json map{"name": "张三", "age": 18, "address": "北京"} list[] for i in …

个人知识库(python/AI/机器学习/数据分析/神经网络/机器视觉/共享文档)

个人工作学习中总结的知识技巧,欢迎大家阅读学习,地址:语雀阅读地址 后期也会把资源在csdn上公布出来,方便同行借鉴.

探索C嘎嘎的奇妙世界:第十七关---STL(vector的模拟实现)

vector是一种动态数组,可以动态调整大小并按照索引访问元素。由于很多接口在string中都有所重复,所以这次来讲一些有所区别的接口 1. 迭代器 Vector中的迭代器是一种用于遍历vector中元素的对象。迭代器提供了一种访问vector中元素的统一方式&#xff0…

vue2.7支持组合式API,但是对应的vue-router3并不支持useRoute、useRouter。

最近在做一个项目,因为目标用户浏览器版本并不确定,可能会有较旧版本,于是采用vue2.7而不是vue3,最近一年多使用vue3开发的项目都碰到了很多chrome 63-73版本,而对应UI 库 element plus又问题很多。 为了不碰到这些问…

TCP 协议详解:三次握手与四次挥手

在网络通信中,确保数据准确无误地传递是至关重要的。TCP(Transmission Control Protocol,传输控制协议)作为一种面向连接的、可靠的、基于字节流的通信协议,在网络数据传输中起到了核心作用。本文将详细解析 TCP 的基本…

神经网络学习5-非线性激活

非线性激活,即 这是最常用的 inplaceTrue 原位操作 改变变量本身的值,就是是否输入时若原本有值,是否更换 该函数就是表示:输入小于零时输出0,大于零时保持不变 代码如下: import torch from torch imp…

Python初体验

# Java基础知识学的差不多了,项目上又没什么事,学学py,方便以后对接 1、打包flask应用(好痛苦,在什么平台打包就只在那个平台可用想在linux用只能参考方法2了) pyinstaller --onefile app.py -n myapp 2…

Databend 开源周报第 149 期

Databend 是一款现代云数仓。专为弹性和高效设计,为您的大规模分析需求保驾护航。自由且开源。即刻体验云服务:https://app.databend.cn 。 Whats On In Databend 探索 Databend 本周新进展,遇到更贴近你心意的 Databend 。 支持递归公共表…

(项目实战)RocketMQ5.0延迟消息在聚合支付系统中的应用

1 基于业务场景掌握RocketMQ5.0 本篇文章主要结合聚合支付系统中的业务场景来落地RocketMQ中间件的应用,聚合支付系统主要在支付系统超时订单和商户支付结果异步通知场景中会使用到RocketMQ消息中间件。本文使用到了RocketMQ中的延迟消息知识点,RocketM…

数据库精选题(一)(关系数据库设计)

🌈 个人主页:十二月的猫-CSDN博客 🔥 系列专栏: 🏀数据库 💪🏻 十二月的寒冬阻挡不了春天的脚步,十二点的黑夜遮蔽不住黎明的曙光 目录 前言 练习题 题型一:判断关系…

百度地图使用任意图片旋转任意角度作为地面贴图

公司项目有个需求是要在地图上贴个航拍的照片做出类似卫星地图的效果,但是只有一张图片而且可以随时替换,也不好做瓦片地图,而且照片的角度可以任意旋转。 要实现这个功能需要解决以下问题: 百度地图怎么贴图片图片角度如何旋转 不卖关子,我先放出实现的效果,为了不涉及侵…

ansible file模块

file模块 管理文件和目录的属性,如状态(是否存在)、权限、所有权等。 1、创建文件: 使用file模块的state参数设置为touch可以创建文件。例如: ansible 组名 -m file -a "path/csdn/jingyu statetouch" …

期末考后怎样发成绩?

老师们,下周可就是期末考啦,又到了头疼发成绩的时候了。每当这个时候,家长们总是急切地咨询孩子的考试表现,向老师们询问成绩。这种场景几乎成了每学期结束时的常态。 别担心,我来安利一个超棒的工具——“易查分小程序…