我是靠谱客的博主 满意丝袜,最近开发中收集的这篇文章主要介绍大坑后记: sql事务关于select for update那点事,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

1.场景描述

这是开发中的一个项目,有以下场景:

  1. 对于一条消息m,有若干个接收者(r1, r2)。
  2. 接收者(假设为r1)收到消息后,发送确认消息。
  3. 收到r1的确认消息后,服务器从消息m的接收者中删除对应的接收者(r1)。
  4. 服务器检查消息m的接收者队列,如果为空,则删除消息M

在极偶然的情况下,竟然发现,当r1,r2都发出了消息确认,而消息M的数据库记录中的接收者队列也确实是空,但是消息M竟然没被删除!

2.伪代码

 服务器关于接收确认消息的处理伪代码大概如下:

// 开启事务
begin_transaction()
// 从db中找到消息m
// select * from M where id = some_id
m = find(some_id)
// 从db中找到消息m的接收者
receivers = find_receivers(m)
// 从db中删除消息m的接收者r
del_receiver(m, r) 
// 从receivers中删除该接收者r
receivers = remove(receivers, r)
if (receivers.empty()) {
    // 如果接收者队列为空,即没有接收者了,则删除消息m
    del_m(m)
}
commit_transaction()

3.疑问

如果简单的把事务当成锁的话,那上面的逻辑无懈可击,因为最后一个确认消息到达后,消息的接收者队列必然会清空,消息肯定会被删除。但残酷的现实表示,不一致状态还是出现了:消息的接收者队列是空的,但是消息没有被删除。

发生这种情况的唯一解释就是,r1, r2的确认消息同时到达,而该事务同时进行。在查找m的接收这队列时,两个事务返回的接收者队列都是[r1, r2]。然后r1的事务删除了r1从而留下了接收者队列为[r2],而r2的事务删除了r2留下了接收者队列为[r1]。两者都不为空,从而无法进入if语句删除消息m。

4.解决办法

发生这个问题的根本原因就是,就算是在事务里,先select再根据结果进行update这种行为也是不安全的。原因就是,当select数据后,此时另一个事务如果改变了该数据,对原事务来说,select的结果也是没有变化的。以这种数据进行后续处理,则整个业务逻辑就变得不安全了。

经过一系列的搜索,终于找到了解药,那便是select for update。从db中select消息m的时候,在select语句后面添加for update,即可对此条数据加一个“锁”

select * from M where id = some_id for update

回到场景中来,此时,r1,r2的确认消息同时到来,事务同时开始。r1开始执行select for update语句找到m,数据库会将m加锁。当r2也想select for update m的时候,它就会被阻塞,直到r1的事务完成后,锁被释放,r2的事务才开始。这样,消息m必然在最后一个确认者确认后进入if语句删除自己。

5.后记

这是笔者一点初步的个人理解,特此小结一下,以方便日后回看。

最后

以上就是满意丝袜为你收集整理的大坑后记: sql事务关于select for update那点事的全部内容,希望文章能够帮你解决大坑后记: sql事务关于select for update那点事所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(43)

评论列表共有 0 条评论

立即
投稿
返回
顶部