【问题标题】:MySQL : Can I use one SELECT ... FOR UPDATE to "protect" multiple tables? ( LOCKING )MySQL:我可以使用一个 SELECT ... FOR UPDATE 来“保护”多个表吗? (锁定)
【发布时间】:2016-07-23 17:43:20
【问题描述】:

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

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

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

上面的最后一个 SELECT 语句要么从 tableb 返回一行(并锁定该行以进行更新),要么证明 myid 在 tableb 中不存在。 当tableb 中存在 myid 时,我只需要更新该行中的一些值,这很简单: 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 是不必要的,因为持有在这种情况下,锁定tablea 已经“保护”tableb 免受同时更新???感谢我开始交易的方式。

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

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

【问题讨论】:

    标签: mysql select transactions locking innodb


    【解决方案1】:

    我会这样做:

    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 上都有行锁如果行存在。如果 SELECT 没有返回任何内容,则您知道该 id 不存在于 tablea 中。如果 SELECT 返回的行具有 a_id 的值,但 b_id 的值为 NULL,那么您知道它存在于 tablea 中而不是 tableb 中。

    如果两个表中都存在该行,则这会同时锁定两个表中的行。如果您分两步执行此操作,您可能会面临竞争条件和死锁的风险。

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

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

    如果具有所需 id 值的行不存在,这将插入它。如果该行存在,这将更新该行。而且您肯定可以访问现有行,因为您的 SELECT FOR UPDATE 之前已将其锁定。

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


    你的cmets:

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

    当您使用 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

    【讨论】:

    • 谢谢,比尔!我分两步做事是有原因的(我提到这只是一个简化的例子)。 tableb 中的 myid 不是唯一的,而是在 tableb 中这两个字段是唯一的(这是 tableb 中的 PRIMARY 索引,那里没有自动增量):id,date。因此,在 tableb 中,我每天只有一个(或零个)id。在这种情况下,我还能使用单个 SELECT FOR ... UPDATE 吗?想一想,我大概可以做到,但是我需要为外连接添加一个WHERE子句:AND tableb.date=DATE(NOW()) 你觉得呢?
    • 我也不想使用 ON DUPLICATE KEY UPDATE 因为:当 myid 存在于 tableb 中时(对于今天的日期),我只需要更新 tableb 中该行中的单个字段。但是,当 myid 不在 tableb 中时(对于今天的日期),我需要插入总共 6 个字段值,并为额外的 5 个字段收集这些值在我的程序中是一项昂贵的操作。因此,如果可能,我只想更新 tableb 中的一个字段,而不是全部 6 个。 (简化的例子不起作用!;))
    • 这行得通吗?选择 a.id AS a_id, b.id AS b_id FROM tablea 作为左外连接 tableb AS b ON a.id=b.id AND b.date=DATE(NOW()) WHERE aid='myid' ?
    • 谢谢,比尔!你摇滚!我想我可以自己从这里出发!只是为了澄清我的第二条评论:我使用 ON DUPLICATE KEY UPDATE 的问题是:我的程序中没有现成的其他字段的值,我只有一个值,例如col4。如果我真的需要插入一个新行,那么我必须收集其他字段的数据,这在我的程序中是一项计算量很大的操作。因此,只要有可能(当 tableb 中已经存在行时)我只想更新那个字段。同样,因为我没有其他字段的值
    • 我明白了。好吧,这是一个正当的理由。 :)
    猜你喜欢
    • 2014-03-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多