V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
zxCoder
V2EX  ›  问与答

关于数据库丢失更新问题和已提交读的关系

  •  
  •   zxCoder · 2021-04-25 14:32:49 +08:00 · 417 次点击
    这是一个创建于 1300 天前的主题,其中的信息可能已经有所发展或是发生改变。

    重新学习事务,看到这部分有点迷糊,网上的资料又比较乱,不知道谁说得靠谱

    一开始看了《数据密集型应用系统设计》这本书

    里面说

    防止丢失更新 到目前为止已经讨论的读已提交和快照隔离级别,主要保证了只读事务在并发写入时可以看到什么。却忽略了两个事务并发写入的问题——我们只讨论了脏写,一种特定类型的写-写冲突是可能出现的。 并发的写入事务之间还有其他几种有趣的冲突。其中最着名的是丢失更新( lost update ) 问题,如图 7-1 所示,以两个并发计数器增量为例。

    这里我不太理解,读写都能在读已提交的隔离级别下解决,为什么写写还会产生冲突呢?读已提交是说只能读取一个已提交的数据,难道写不需要先读吗?读已提交下写可以覆盖一个未提交的数据?

    1 条回复    2021-04-25 15:25:48 +08:00
    timethinker
        1
    timethinker  
       2021-04-25 15:25:48 +08:00   ❤️ 2
    读已提交并不会覆盖未提交的数据,而是覆盖了另一个已提交的数据。

    一般来讲,在读已提交的隔离级别下,如果两个事务试图尝试对同一条记录进行更改,那么就会推迟第二个事务的写,直至第一个事务提交或终止,具体采用的就是行级锁。

    比如,两个并发的请求,尝试对一个计数器增加 1 的操作,很显然其中一个需要等待另一个事务提交或终止。
    A 读取了计数器,假设为 100,+1,写回。
    B 读取了计数器,假设为 100,+1,写回。
    两个事务在开始的时候都读取的 100,但是经过自己+1 之后,都是 101,在写回的时候就会出现问题(业务上正确的应该是 102 才对)。
    实际上这种操作属于覆盖,在读和写之间并不是原子的,也就是一个事务覆盖了另一个事务的写,破坏了数据的完整性,这就是丢失更新问题。就像是在 Java 多线程并发编程中,如果你不把一个静态变量声明为 volatile,且在加锁的代码块里面再次判断这个变量是否等于之前已知的状态,就有可能会发生覆盖(原子比较+设置),例如 Java 的单例双重检查。

    解决这个问题可以对行数据增加乐观锁版本号(也就是原子比较+设置),在事务中对写入结果进行判断,例如:
    UPDATE counter SET count = 101, version = 1 WHERE version = 0
    如果结果返回影响数据为 0 条(代码判断),则终止当前事务,防止发生覆盖。也可以用 SELECT FOR UPDATE,加独占锁,这样另一个事物在读取同一条数据的时候会被阻塞。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2653 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 04:33 · PVG 12:33 · LAX 20:33 · JFK 23:33
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.