MySql get_lock 用于并发安全更新插入

MySql get_lock for concurrency safe upsert

用 mysql 数据库在 node.js 中编写 API,我正在实施一个相当标准的模式:

If exists then update
else insert

这当然可以正常工作,直到对 api 发出多个同时请求,此时请求 2 上的 If exists 可以在插入请求 1 之前执行,从而导致两条记录的一个。

我知道处理这个问题的一种方法是确保数据库具有防止重复记录的约束或键,但在这种情况下,确定我们是否应该进行插入或更新的规则更加复杂,因此检查需要在代码中完成。

这听起来像是使用 mutex/lock 的好案例。我需要将其分发,因为 api 可能有多个实例 运行 作为 pool/farm 的一部分。

我想出了以下实施方案:

try {
     await this.databaseConnection.knexRaw().raw(`SELECT GET_LOCK('lock1',10);`);
     await this.databaseConnection.knexRaw().transaction(async (trx) => {
        const existing = await this.findExisting(id);
        if (existing) {
          await this.update(myThing);
        } else {
          await this.insert(myThing);
        }
     });

  } finally {
    await this.databaseConnection.knexRaw().raw(`SELECT RELEASE_LOCK('lock1');`);
}

这一切似乎工作正常,我的测试现在只生成一个插入。虽然看起来有点粗暴force/manual。作为 mysql 和节点的新手(我来自 c# 和 sql 服务器背景)这种方法是否明智?有没有更好的方法?

神志正常吗?主观。

技术上安全吗?它可能是——GET_LOCK() 是可靠的——但不像你写的那样。

您正在忽略 GET_LOCK() 的 return 值,如果您获得了锁,则为 1,如果超时已过且您未获得锁,则为 0,如果失败则为 NULL案例。

正如所写,您将等待 10 秒,然后再继续工作,所以,不安全。

这假设您只有一个 MySQL 主人。如果您有多个 master 或 Galera,它就不会工作,因为 Galera doesn't replicate GET_LOCK() 跨所有节点。 (Galera 集群是一个高可用性 MySQL/MariaDB/Percona 可写主集群,同步复制并且将在 (ceil(n/2) - 1)failure/isolation 后存活=31=] 共 n 个节点)。

最好使用 SELECT ... FOR UPDATE 查找并锁定相关行,这会锁定找到的行,或者在某些情况下锁定它们所在的间隙(如果它们存在),从而阻止其他正在尝试的事务捕获相同的锁,直到你回滚或提交......但如果这不切实际,使用 GET_LOCK() 是有效的,但要遵守上面关于 return 值的观点。