MySQL :我可以使用一个 SELECT ... FOR UPDATE 到 "protect" 多个表吗? (锁定)

MySQL : Can I use one SELECT ... FOR UPDATE to "protect" multiple tables? ( LOCKING )

我正在阅读 MySQL 文档几个小时,但我仍然无法回答自己几个非常简单的问题...:(

这是我的(简化的)场景:我在数据库中有两个 table:tableatableb,两个 table 都使用 InnoDB 存储引擎. tablea(这是我的主要 table)有一个自动增量的主索引(id)。现在这是我想要实现的目标,请记住以下业务逻辑可以并且将同时 运行:

我开始交易: START TRANSACTION BEGIN 然后我检查 tablea 中是否存在 id 如果是,我 SELECT 更新行,让我们调用我正在寻找的 id myid : SELECT `id` FROM `tablea` WHERE `id`='myid' FOR UPDATE; 如果上面 SELECT returns 没有行,我只是回滚事务并退出我的函数。换句话说,当 tablea 中不存在 myid 时,我就完成了。 另一方面,当 myid 存在时,我首先需要更新 tablea 中的一些值: UPDATE `tablea` SET `somefield`='somevalue' WHERE `id`='myid'; 然后我需要检查 myid 是否也存在于 tableb 中: SELECT * FROM `tableb` WHERE `id`='myid' FOR UPDATE; 我的第一个问题是关于上面的 SELECT 声明:可以在这里(在 tableb 上)做另一个 SELECT FOR UPDATE 吗???或者在处理tableb时这里不需要"FOR UPDATE",因为我已经启动了一个事务并且还根据tablea中的一行获取了锁???有人可以回答这个问题吗?

上面的最后一个 SELECT 语句 returns 来自 tableb 的一行(并锁定该行以进行更新)或者结果是 [=14= 中不存在 myid ]. 当 myid 出现在 tableb 中时,我只需要更新该行中的一些值,这很简单: UPDATE `tableb` SET `somefieldintableb`='somevaluefortableb' WHERE `id`='myid'; 另一方面,当 myid 不在 tableb 中时,我需要插入它,这是我的第二个问题:我应该在发出 INSERT INTO 语句之前锁定 tableb ,如下所示: LOCK TABLES `tableb` WRITE; INSERT INTO `tableb` (`id`,`somefieldintableb`) VALUES ('myid','somevaluefortableb'); UNLOCK TABLES `tableb`; 最后,我这样做: COMMIT

我的目标是:由于上述函数(使用 MySQL 事务)将 运行 在许多实例中并行,我想防止这些实例中的任何一个更新同一行同时 tableatableb。我还想防止将 myid 重复插入 tableb,因此我考虑在 tableb.

中找不到 myid 时使用 LOCK TABLES

所以我有两个问题:当我想更新 tableb 或使用 [= 锁定 tableb 时,我是否应该在我已经开始的事务中执行 SELECT ... FOR UPDATE 68=] ... FOR UPDATE 是不必要的,因为在这种情况下从同步更新中也已经 "protects" tableb 锁定了 tablea ???感谢我开始交易的方式,我是说。

第二个问题:当我需要在 tableb 中插入一个新行时,我应该为该插入锁定整个 table 吗?还是在这种情况下完全没有必要? (我需要 LOCK TABLES tableb 还是不需要?)

如果有专家能为我解答这两个问题,我将不胜感激,因为在线阅读各种文档和示例根本无法帮助我回答这些问题。 :(

我会这样做:

BEGIN;

SELECT a.`id` AS a_id, b.`id` AS b_id 
FROM `tablea` AS a LEFT OUTER JOIN `tableb` AS b ON a.id=b.id
WHERE a`id`='myid' 
FOR UPDATE;

现在 tablea 和 tableb if 行都存在行锁。如果 SELECT returns 什么都没有,您就知道该 id 不存在于 tablea 中。如果 SELECT returns 行的值为 a_id,但 b_id 的值为 NULL,那么您知道它存在于 tablea 而不是 tableb.

如果该行同时存在于两个 table 中,这将同时锁定两个 table 中的行。如果分两步执行,则可能会出现竞争条件和死锁。

尝试插入并使用 ON DUPLICATE KEY UPDATE:

INSERT INTO `tableb` (id, somefieldintableb) VALUES ('myid', 'somevaluefortableb') 
ON DUPLICATE KEY UPDATE `somefieldintableb`='somevaluefortableb';

如果不存在具有所需 ID 值的行,则会将其插入。如果该行存在,这将更新该行。而且您肯定可以访问现有行,因为您的 SELECT FOR UPDATE 早些时候锁定了它。

如果可以避免,请不要使用 table 锁。这肯定会在您的应用程序中造成瓶颈。


回复您的评论:

是的,您可以对日期列使用额外的连接条件。

当您使用 ON DUPLICATE KEY UPDATE 时,您不必更新 所有 列。如果该行存在,您可以保留其中的大部分,只更新一个或几个或其他任何内容。

您也可以参考您尝试插入的值。

INSERT INTO `tableb` (id, date, col1, col2, col3, col4, col5, col6) 
  VALUES ('myid', $a_date, ?, ?, ?, ?, ?, ?) 
ON DUPLICATE KEY UPDATE col4=VALUES(col4);

更多细节,推荐阅读http://dev.mysql.com/doc/refman/5.7/en/insert-on-duplicate.html