Java事务编程-弱隔离级别之写倾斜与幻读

MVCC深度解析:幻影读问题的解决之道 本文主要通过多线程并发读写数据库场景下产生的问题引出不同的事务隔离级别、相应的算法以及在Innodb中的实现方式,并穿插对于Java多线程下可见性、原子性、有序性的比较。 阅读详情

多个事务并发写相同对象时,会出现脏写和更新丢失两种竞争条件。为避免数据不一致,可:

  • 借助DB内置机制
  • 或通过显式加锁、执行原子写操作

但这还不算并发写可能引发的全部问题。

为医院写一个值班管理程序。医院通常会同时要求几个医生待命,前提是至少有一位医生在待命。医生可以放弃他们的班次(例如,如果他们自己生病了),只要至少有一个同事在这一班中继续工作。

Alice、Bob两位值班医生都不适,所以他们都决定请假。但他们恰在同一时刻点击调班按钮

每笔事务总先检查是否至少有两名医生目前在值班。若是,则有一名医生可安全离开去休班。由于DB使用快照隔离,两次检查都返回2 ,所以两个事务都进入下一阶段。Alice更新自己的记录为休班,Bob也更新自己的记录。两个事务都成功提交,最后结果没有医生值班,显然违反至少有一名医生值班的业务要求。

定义写倾斜

这种异常称为写倾斜,不是脏写,也不是丢失更新,这俩事务更新的是两个不同对象(Alice 和 Bob 各自值班记录)。这里发生的冲突不是那么明显,但很显然确实是竞争状态:若两个事务串行,则第二个医生就不能歇班。异常行为只有在事务并发时才可能。

可将写倾斜视为广义的丢失更新。即若两事务读取相同一组对象,然后更新其中一部分:

  • 不同事务可能更新不同对象,则可能发生写倾斜
  • 而若更新同一对象,则可能脏写或丢失更新

我们有很多方法防止丢失更新。但对写倾斜,方案更受限制:

  • 由于涉及多对象,单对象的原子操作无效
  • 基于快照隔离来实现自动检测丢失更新也有问题:PostgreSQL的可重复读,MySQL/InnoDB 的可重复读,Oracle可串行化或SQL Server快照隔离级别中,都不支持自动检测写倾斜。自动防止写倾斜要求真正的可串行化隔离
  • 某些DB支持自定义约束,然后由DB强制执行(如唯一性,外键约束或特定值限制)。但为指定至少有一名医生必须在线,涉及多个对象的约束,大多DB都未内置这种约束,但你可使用触发器或物化视图来实现类似约束
  • 若无法使用可串行化,则次优方案可能是显式锁定事务依赖的行:
BEGIN TRANSACTION;

SELECT * FROM doctors
  WHERE on_call = TRUE
  # 告诉DB锁定返回的所有结果行,以用于更新
  AND shift_id = 1234 FOR UPDATE;

UPDATE doctors
  SET on_call = FALSE
  WHERE name = 'Alice'
  AND shift_id = 1234;
  
COMMIT;

写倾斜案例

写倾斜乍看晦涩,但意识到本质后,很容易注意到更多case:

  • 会议室预订系统

    不能在同一时间对同一会议室进行多次预订。当有人想要预订时,首先检查是否存在相互冲突的预订(即预订时间范围重叠的同一房间),若无,则创建会议(参阅示例-2)

    例-2 会议室预订系统,避免重复预订(在快照级别隔离下不安全)

    BEGIN TRANSACTION;
    
    -- 检查所有现存的与 12:00~13:00 重叠的预定
    SELECT COUNT(*) FROM bookings
    WHERE room_id = 123 AND
      end_time > '2015-01-01 12:00' AND start_time < '2015-01-01 13:00';
    
    -- 若之前的查询返回 0
    INSERT INTO bookings(room_id, start_time, end_time, user_id)
      VALUES (123, '2015-01-01 12:00', '2015-01-01 13:00', 666);
    
    COMMIT;

    快照级别隔离无法防止并发用户预订同一会议室。为避免预订冲突,需可串行化隔离级别。

  • 多人游戏

    例-1中,使用一个锁来防止丢失更新(即两个玩家不能同时移动同一数字)。但锁不妨碍玩家将两个不同数字移动到棋盘的相同位置或其他违反游戏规则的行为。可能需更多约束,否则很容易发生写倾斜。

  • 抢注用户名

    在每个用户拥有唯一用户名的网站上,两个用户可能会尝试同时创建具有相同用户名的帐户。可采用事务检查名称是否被抢占,若无,则使用该名称创建账户。但和之前案例类似,快照隔离下不安全。但唯一约束是简单方案(第二个事务在提交时会因为违反用户名唯一约束而被中止)。

  • 防止双重开支

    支付或积分服务一般需检查用户的支付数额不超过余额。可通过在用户帐户中插入一个临时支出项目,列出帐户中的所有项目,并检查总和是否为正值。由于写倾斜,可能发生两个支出项目同时插入,两个交易都不超额,但一起会导致余额变为负值。

导致写倾斜的幻读

所有这些案例都遵循类似模式:

  1. 首先输入一些匹配条件,即 SELECT 查询所有符合条件的行并检查是否符合一些要求。如至少有两名医生在值班;不存在对该会议室同一时段的预订;棋盘某位置没有出现棋子;用户名还没被抢注;账户里还有余额等

  2. 根据查询结果,应用代码决定是否继续

  3. 若应用决定继续执行,就发起DB写入(插入、更新或删除),并提交事务

    而该写操作会改变步骤2做出决定的前提条件。即若提交写入后,再重复执行步骤1的 SELECT查询,将得到不同结果。因为刚才的写改变了符合搜索条件的行集(现在少了一个医生值班,那时的会议室现已被预订,棋盘上的这个位置已被占,用户名已被抢注,账户余额不够)。

上述步骤可能有不同执行顺序。如可先写,然后SELECT查询,最后根据查询结果决定是放弃还是提交。

医生值班案例,步骤3所修改的行恰好是步骤1查询结果的一部分,所以若通过锁定步骤 1 中的行( SELECT FOR UPDATE )再查询可保证事务安全,避免写倾斜。但其他四个案例不同:它们检查是否 不存在 某些满足条件的行,写入会 添加 一个匹配相同条件的行。若步骤1中的查询没有返回任何行,则 SELECT FOR UPDATE 锁不了任何东西。

这种效应:一个事务中的写入改变另一个事务的搜索查询结果,即幻读。快照隔离避免了只读查询中的幻读,但是在像我们讨论的例子那样的读写事务中,幻读会导致特别棘手的写倾斜。

物化冲突

若幻读的问题是没有对象可以加锁,也许可以考虑人为在DB引入一个锁对象?

如会议室预订案例,想象创建一个关于时间槽和房间的表。此表中的每行对应于特定时间段(如 15min)的特定房间。可提前插入房间和时间的所有可能组合行(例如接下来的六个月)。

现在,要创建预订的事务可以锁定( SELECT FOR UPDATE )表中与所需房间和时间段对应的行。锁定后,它可检查重叠预订并像以前一样插入新预订。该表不是用来存储预订相关信息的,它完全就是一组锁,以防止同时修改同一房间和时间范围内的预订。

这被称为物化冲突(materializing conflicts)方案,因为它将幻读变为DB中一组具体行上的锁冲突。但弄清楚如何物化冲突很难,也很易出错,而让并发控制机制泄漏到应用数据模型是很丑陋的做法。出于这些原因,若无其他办法可以实现,物化冲突应被视为最后手段。大多数情况下, 可串行化(Serializable) 隔离级别更可取。

 

精通Java事务编程(7)-可串行化隔离级别之两阶段锁定 会议室预订案例,若事务在查询某时间段内一个房间的预订情况,则另一个事务不能同时插入或更新同一时间段内该房间的预订 (可同时插入其他房间的预订或在不影响另一个预定的条件下预定同一房间的其他时间段)。若另一事务想插入、更新或删除同一房间和/或重叠时间段的预订,则须更新这些索引的相同部分,就一定会和共享锁冲突,将被迫等到共享锁被释放。索引范围锁并不像谓词锁精确(会锁定更大范围的对象,超出维持可串行化所必需的范围),但由于开销低得多,是很好的折衷方案。在写倾斜幻读中的幻读问题,即一个事务改变另一个事务的查询结果。. 阅读详情

相关推荐

MySQL索引、事务与锁:从原理到慢SQL优化

数据库性能优化是后端工程师的核心技能之一,而索引、事务隔离级别和锁机制则是绕不开的三大基石。理解B+树的磁盘IO优势、最左前缀原则与索引下推,能帮助开发者设计出更高效的查询路径;掌握MVCC多版本并发控制与ReadView快照读机制,才能透彻理解可重复读和幻读的本质。锁机制作为并发控制的最后防线,记录锁、间隙锁与临键锁的协作方式,以及死锁的排查手段,更是高并发场景下保障系统稳定的关键。基于这些原理,结合执行计划EXPLAIN中的type、rows和Extra列分析,可以精准定位慢SQL的瓶颈所在,并通过覆盖

weixin_30617695的博客 348

精通Java事务编程(5)-写倾斜与幻读

这种异常称为写倾斜,不是脏写,也不是丢失更新,这俩事务更新的是两个不同对象(Alice和Bob各自值班记录)。这里发生的冲突不是那么明显,但很显然确实是竞争状态若两个事务串行,则第二个医生就不能歇班。异常行为只有在事务并发时才可能。可将写倾斜视为广义的丢失更新。不同事务可能更新不同对象,则可能发生写倾斜而若更新同一对象,则可能脏写或丢失更新我们有很多方法防止丢失更新。由于涉及多对象,单对象的原子操作无效基于快照隔离来实现自动检测丢失更新也有问题。......

JavaEdge全是干货的技术号 951

从FP16到FP8:我是如何让Stable Diffusion 3.5提速40%而不丢画质的

本文分享了StableDiffusion模型优化的实战经验。通过FP8精度转换、分层处理敏感模块,实现显存占用降低35%、推理速度提升40%。文章详细介绍了提升画质的五大技巧、本地/云端/移动端部署方案,以及二次元模型定制等垂直领域应用。同时总结了硬件兼容性、数据质量等七大常见陷阱,并强调持续监控和伦理考量。作者指出未来应关注MoE架构、注意力机制优化等方向,认为技术优化是持续追求性能与质量平衡的过程。

达则兼济天下 1187

tidb数据库隔离级别剖析

本文章来源于:https://github.com/Zeb-D/my-review ,请star 强力支持,你的支持,就是我的动力。 [TOC] 前言 在线应用业务中,数据库是一个非常重要的组成部分,特别是现在的微服务架构为了获得水平扩展能力,我们倾向于将状态都存储在数据库中,这要求数据库能够正确、高性能处理请求,但这是一个几乎不可能达到的要求,所以数据库的设计者们定义了隔离级别这一个概念,在高...

在此立一个IT职业上的flag:https://github.com/Zeb-D/my-review 1万+

NeRF必读:Mip-NeRF总结与公式推导

由于远景近景的分辨率不同,导致经典NeRF对于多尺度场景的表达存在明显瑕疵:NeRF对于近景的重建比较模糊而对于远景的重建出现锯齿。简单粗暴的策略是supersampling,但是费时费力。相较于NeRF使用的位置编码(PE)方式,Mip-NeRF提出了积分位置编码的方式(IPE).这种编码方式可以多尺度地描述空间中的信息分布,make sense.

i_head_no_back的博客 6079

AI Pika 生成进击的巨人动漫分镜案例

AI Pika 生成进击的巨人动漫分镜案例 Pika 是一个使用AI生成和编辑视频的平台。

独立开发,substack顶级编辑 2574

数据的迷幻之旅:探索Java事务中的脏读、幻读和不可重复读

当一个事务读取到了另一个事务已经修改但尚未提交的数据时,如果这个修改后的数据最终没有被提交,那么读取到的数据就是无效的或错误的。如果线程2最终回滚了事务,则线程1读取到的余额是错误的。这就是不可重复读的一个示例,它展示了在同一事务中,不同时间点对同一数据的多次读取返回了不同的结果。这就是脏读的一个示例,它展示了当一个事务读取到另一个事务尚未提交的数据时,可能导致错误的结果。脏读发生在并发事务中,其中一个事务读取到了另一个事务尚未提交的数据,因此我将向您提供一个简单的示例,以说明脏读产生的原因和后果。

青轩桃李能几何,流光欺人忽蹉跎 495

使用 C# 开发海康设备实时视频流的监控与报警系统

本文介绍了如何使用C#结合海康威视SDK(HCNetSDK)开发安防监控系统,涵盖设备登录、视频流获取、报警处理和云台控制等核心功能。内容包括开发环境配置、系统架构设计、关键代码实现(如设备登录、实时视频播放、报警设置)以及用户界面设计。通过Windows Forms/WPF,开发者可构建完整的监控应用,实现设备管理、视频监控、报警响应等功能。系统采用模块化设计,便于扩展和维护,适用于各类安防场景。

这里只有干货:从 Modbus 通信到 MES 对接,从 YOLO 训练到工控机部署,带你搞定工业软件开发全流程。 1357

编程式事务 (Java)

编程式事务可以支持编程式事务和声明式事务。提供的最原始的事务管理方式是基于编程式事务。而的编程式事务管理是使用模板方法设计模式对原始事务管理方式的封装。编程式事务是一种在数据库操作中使用的事务处理方式,通过编写程序来控制事务的提交或回滚。与声明式事务相比,编程式事务需要手动指定事务的启动、提交和回滚等操作,具有更精细的控制能力。编程式事务可以根据业务逻辑和数据操作的复杂度进行灵活的控制和处理,保证数据的一致性和完整性;编程式事务代码可以直接显示了事务的边界和执行顺序,方便理解和调试;

AKALXH的博客 5404

工业视觉检测实战指南:从硬件配置到AI算法部署

本文提供了一套完整的工业视觉检测系统搭建实战指南。从硬件选型(相机、光源、镜头、工控机)到AI算法(深度学习模型)部署,详细解析了如何为生产线配置一双“会思考的眼睛”。重点探讨了深度学习算法在复杂缺陷识别中的应用,并分享了模型训练、优化及云边协同部署的实战心得,旨在帮助工程师实现从实验室Demo到7x24小时稳定运行的工业落地。

weixin_29051193的博客 458

Claude Code教程(二) | Claude Code 核心信息与使用指南

Claude Code 核心信息与使用指南

qq_20236937的博客 2185

关于幻读,该捋清楚了

小伙伴们都知道,MySQL 有四种不同的隔离级别,四种不同的隔离级别会带来三种不同的问题,今天我想再和大家捋一捋这个问题。

mxt51220的博客 1337

【高并发基础】MySQL 不同事务隔离级别下的并发隐患及解决方案

本项目聚焦高并发的基础知识,至底向上得研究:github 仓库 (分支 master / isolation / propagation / multithreading) 理念:禁用了gap锁,减少了锁的作用范围,即减少了死锁风险使用了"半一致读"降低了锁等待,即减少了死锁风险,也提高了读效率项目isolation分支 : 3.1.2 RC 解决不可重复读,使用 加锁 项目isolation分支 :项目isolation分支 : 3.2.1 解决幻读 RC 禁用了GAP锁(外键约束除外)理论上是

chenghan_yang的博客 1293

Python全栈入门到实战【进阶篇 1】类与对象:面向对象编程的基石

Python中定义类使用**class关键字**,创建对象使用**类名()**(类似调用函数),语法简洁,我们从最基础的空类开始,逐步拆解。class# 定义空的学生类,pass表示占位,无实际内容 class Student : pass编码规范:类名遵循大驼峰命名法(每个单词首字母大写,无下划线),如StudentCarPhone,这是Python官方推荐的PEP8规范,便于区分类和其他标识符。实例方法必须定义在类内部;实例方法的第一个参数必须是self(名称可自定义,如this。

那些看似不起波澜的日复一日,会在某天让你看到坚持的意义 3404

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

隔离级别的幻影:MySQL 事务隔离与分布式一致性协议的工程边界

事务隔离级别的选择是正确性与性能的权衡。单机场景下,REPEATABLE READ 通过 MVCC + Next-Key Lock 提供了足够强的一致性保证,但无法阻止写倾斜异常。分布式场景下,Seata AT 模式通过全局锁实现写隔离,但默认不提供读隔离;TCC 模式通过资源冻结实现更精细的控制,但开发复杂度显著增加。分布式事务的核心认知是:它只能保证写操作的原子性,无法提供跨分片的一致性读快照。

guoyizhongxing的博客 3239

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

事务隔离级别全面剖析:MVCC如何消除幻影读

本文主要通过多线程并发读写数据库场景下产生的问题引出不同的事务隔离级别、相应的算法以及在Innodb中的实现方式,并穿插对于Java多线程下可见性、原子性、有序性的比较。

资料免费分享,点击名片 159

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

精通Java事务编程-弱隔离级别之快照隔离和可重复读

若她在提交转账请求后、银行DB系统执行转账的过程中间,查看两个账户的余额,她可能看到账号2在收到转账前的余额(500),和账户1在完成转账之后的余额(400)。这种使用追加的B树,每个写入事务(或一批事务)都会创建一个新的B树,当创建时,从该特定树根生长的树就是该时刻DB的一致性快照。而定义了可重复读,表面看起来接近快照隔离。图-7中,当事务12从账户2读时,会看到500余额,因为500余额的删除是由事务13完成的(根据规则3,事务12看不到事务13执行的删除),同理400美元记录的创建也不可见。...

JAVAQXQ的博客 237

精通Java事务编程(3)-弱隔离级别之快照隔离和可重复读

表面看,RC已满足事务所需的一切特征支持中止(原子性),防止读取不完整的事务结果,并防止并发写的混乱。这点很关键!为我们的开发省去一大堆麻烦。但此隔离级别仍有很多地方可能产生并发错误。如图-6说明RC可能发生的问题。Alice在银行有1000存款,分为两个账户,每个500。现有一笔转账交易从账户1转移100到账户2。若她在提交转账请求后、银行DB系统执行转账的过程中间,查看两个账户的余额,她可能看到账号2在收到转账前的余额(500),和账户1在完成转账之后的余额(400)。这词有些滥用。...

JavaEdge全是干货的技术号 598

精通Java事务编程

因为刚才的写改变了符合搜索条件的行集(现在少了一个医生值班,那时的会议室现已被预订,棋盘上的这个位置已被占,用户名已被抢注,账户余额不够)。这种异常称为写倾斜,不是脏写,也不是丢失更新,这俩事务更新的是两个不同对象(Alice和Bob各自值班记录)。快照隔离避免了只读查询中的幻读,但是在像我们讨论的例子那样的读写事务中,幻读会导致特别棘手的写倾斜。当有人想要预订时,首先检查是否存在相互冲突的预订(即预订时间范围重叠的同一房间),若无,则创建会议(参阅示例-2))再查询可保证事务安全,避免写倾斜。...

weixin_62421895的博客 175

数据库并发问题

据库并发问题,本质上是:多个事务同时读写同一份数据,彼此干扰,导致结果不符合业务预期。下面按「会遇到什么问题 → 怎么解决 → 项目里怎么做」展开。

weixin_46228563的博客 295

精通Java事务编程(8)-可串行化隔离级别之可串行化的快照隔离

串行化的隔离级别和高性能就是相互矛盾的吗?也许不是,一个称为可串行化快照隔离(SSI, serializable snapshot isolation)算法很有前途。提供完整的可串行化保证,而性能与快照隔离相比只有很小性能损失。SSI在 2008 年首次被提出,如今既用于单节点DB(PostgreSQL9.1后的可串行化)和分布式DB(FoundationDB)。由于 SSI 与其他并发控制机制相比还很年轻,还在实践中证明自己。

2401_87878486的博客 774

精通Java事务编程(9)-总结

如没有事务,各种错误情况(进程崩溃,网络中断,停电,磁盘已满,意外并发)意味着数据可能各种不一致。仅当事务提交时,才检查可能的冲突,若发现违背串行化,则中止事务。事务读取某些符合查询条件的对象,同时另一客户端写,改变了先前查询结果。快照隔离可防简单的幻读,但写倾斜的幻读需特殊处理,如采用索引范围锁定。但写时,该决定的前提不再true。弱隔离级别可防止上述的一些异常,但还得应用程序开发人员手动处理其它复杂case,如显式加锁。若每个事务的执行很快,且单CPU核即可满足事务吞吐要求,这是简单有效的选择。...

JavaEdge全是干货的技术号 438

精通Java事务编程-可串行化隔离级别之两阶段锁定时间

近30年,DB只有一种广泛使用的串行化算法:两阶段加锁

guanshengg的博客 156

Java-知识点梳理

本文摘要:文章系统梳理了Java开发核心技术栈,涵盖数据库(MySQL索引原理及优化、事务隔离级别)、Java基础(并发编程、集合源码)、主流框架(Spring全家桶、MyBatis)、设计模式、中间件(Redis、Kafka、Elasticsearch)以及运维部署(Docker、K8s、Linux命令)。重点解析了MySQL索引的B-Tree/Hash结构、回表与索引覆盖优化、联合索引左匹配原则,并详细说明了事务隔离级别的实现机制。内容结构清晰,可作为Java全栈开发的系统性技术参考手册。

m0_61345620的博客 97
上一篇: 看完这篇,所有JAVA并发问题你都能应对自如
下一篇: 互联网扫地僧精心总结,39W字上千道Java一线大厂面试题手册
JavaShark
博客等级 码龄4年 184粉丝 438原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值