当发生写入数据库的并发 API 调用(或服务器速度较慢时)时,防止出现竞争条件

Posted

技术标签:

【中文标题】当发生写入数据库的并发 API 调用(或服务器速度较慢时)时,防止出现竞争条件【英文标题】:Prevent race condition when concurrent API calls that write to database occurs (Or when the server is slow) 【发布时间】:2014-10-07 10:45:46 【问题描述】:

让我们想象一个场景,您将拥有一个用于创建用户的端点。这将在一个 RESTful 应用程序中,因此让我们假设一个富客户端调用此 API 端点。

exports.createUser = function(req,res)
    if(req.body)
        //Check if email has already been used
        db.User.find(where:email:req.body.email).success(function(user)
            if(user === null || user === undefined)
                //Create user
                res.send(201);
             else 
                res.json(409,error: 'User already exists');
            
        );
     else 
        res.send(400);
    
;

如果我以非常快的速度多次调用此端点,即使您查询用户表以确保没有重复,也可以在数据库中创建具有相同电子邮件的多条记录。

我确定这是一个常见问题,但是如何防止这个问题呢?我严格限制对特定端点的请求数量,但这似乎不是一个很好的解决方案。

有什么想法吗? 非常感谢!

【问题讨论】:

您可以对 db 表设置约束以保持电子邮件的唯一性。这不会阻止错误的批准,但它会保持数据库清洁,并且您可以在随后的 ajax 调用中对创建进行 dbl-check。 许多解决方案,主要取决于您使用的数据库和技术;就像一个约束,一个 upsert 后跟一个更新,一个插入 select 的结果的单个请求,当已经插入时不返回任何行,......你能更具体地说明你期望的答案类型吗? 【参考方案1】:

最简单的选择是LOCK TABLE "users" IN EXCLUSIVE MODE 在开始执行查找然后插入的事务。这确保了一次只能将一个事务写入表。

为了更好的并发,您可以:

email 上定义UNIQUE 约束,然后跳过find 步骤。尝试insert,如果失败,捕获错误并报告重复;或

使用insert-if-not-exists techniques known to be concurrency-safe之一

如果使用唯一约束,需要考虑的一件事是,您的应用可能会将用户标记为已禁用而不会删除它们,并且可能不希望强制禁用用户的电子邮件地址是唯一的。如果是这样,您可能需要一个部分唯一索引(请参阅文档)。

【讨论】:

以上是关于当发生写入数据库的并发 API 调用(或服务器速度较慢时)时,防止出现竞争条件的主要内容,如果未能解决你的问题,请参考以下文章

利用java concurrent 包实现日志写数据库的并发处理

多线程与计划任务

最后写入胜利(丢弃并发写入)

GO并发编程的数据竞争问题

一文了解异步编程基础

磁盘阵列