【问题标题】:Triggers and table lock in MySQLMySQL中的触发器和表锁
【发布时间】:2014-08-07 15:28:02
【问题描述】:

场景:我有一些触发器可以跟踪一个表的记录数以及其他有用信息。这些触发器在此表上添加/删除/更新时触发,并负责将此信息写入另一个补充表中。

现在这些触发器将在多线程环境中运行,在该环境中我可能可以同时访问表。 我希望我能做这样的事情,但这是被禁止的(ERROR: Error Code: 1314. LOCK is not allowed in stored procedures):

DELIMITER $$
DROP TRIGGER IF EXISTS del_alarmCount$$
CREATE TRIGGER del_alarmCount AFTER DELETE ON Alarm
FOR EACH ROW
BEGIN
SET autocommit=0;
LOCK TABLES AlarmCount WRITE, AlarmMembership READ;
  UPDATE AlarmCount SET num = num - 1 
  WHERE RuleId = OLD.RuleId AND
      MemberId = 0 AND
      IsResolved = OLD.IsResolved;

  UPDATE AlarmCount SET num = num - 1 
  WHERE RuleId = OLD.RuleId AND
      IsResolved = OLD.IsResolved AND
      MemberId IN (SELECT MemberId FROM AlarmMembership WHERE AlarmId=OLD.Id);
COMMIT;
UNLOCK TABLES;
END $$
DELIMITER ;

使用这些 LOCKS(或替代结构)实现的目标是:

  1. 避免两个触发器同时运行在 AlarmCount 表上写入并更新相关记录(我想我可能有两个触发器为 Alarm 表的不同记录运行更新同一条 AlarmCount 记录)
  2. 确保 AlarmMembership 表不会同时被修改(例如目标 MemberId 被同时删除)。

非常欢迎任何建议!

【问题讨论】:

    标签: mysql concurrency triggers locking


    【解决方案1】:

    我认为处理此问题的最佳方法是使用此处描述的 SELECT ... FOR UPDATE 模式:http://dev.mysql.com/doc/refman/5.0/en/innodb-locking-reads.html

    供参考:

    让我们看另一个例子:我们有一个整数计数器字段 我们使用表 child_codes 为每个分配一个唯一标识符 孩子添加到表孩子。使用任何一个都不是一个好主意 一致读取或共享模式读取以读取当前值 计数器,因为数据库的两个用户可能会看到相同的值 对于计数器,如果两个用户尝试,则会发生重复键错误 将具有相同标识符的子项添加到表中。

    在这里,锁定共享模式不是一个好的解决方案,因为如果两个用户 同时读取计数器,其中至少有一个最终出现在 尝试更新计数器时发生死锁。

    要实现计数器的读取和递增,首先执行一个 使用 FOR UPDATE 锁定计数器的读取,然后递增 柜台。例如:

     SELECT counter_field FROM child_codes FOR UPDATE; UPDATE child_codes
     SET counter_field = counter_field + 1; 
    

    A SELECT ... FOR UPDATE 读取最新的可用数据,在它读取的每一行上设置排他锁。因此,它设置的锁与搜索的 SQL UPDATE 在行上设置的锁相同。

    。 . .

    注意仅适用于使用 SELECT FOR UPDATE 锁定行以进行更新 当自动提交被禁用时(通过开始事务 START TRANSACTION 或通过将 autocommit 设置为 0。如果 autocommit 是 启用,匹配规范的行不被锁定。

    所以在你的情况下,你会替换

    LOCK TABLES AlarmCount WRITE, AlarmMembership READ;
      UPDATE AlarmCount SET num = num - 1 
      WHERE RuleId = OLD.RuleId AND
          MemberId = 0 AND
          IsResolved = OLD.IsResolved;
    

    类似的东西

    SELECT num FROM AlarmCount WHERE RuleId = OLD.RuleId AND
              MemberId = 0 AND
              IsResolved = OLD.IsResolved FOR UPDATE;
    UPDATE AlarmCount SET num = num - 1;
    

    我说“类似”是因为我并不完全清楚 OLD.RuleId 和 OLD.IsResolved 引用的是什么。 http://dev.mysql.com/doc/refman/5.0/en/innodb-locking-reads.html 还值得注意的是:

    前面的描述只是 SELECT ... FOR 的一个例子 更新有效。在 MySQL 中,生成唯一的特定任务 标识符实际上可以只使用一次访问来完成 表:

    UPDATE child_codes SET counter_field = LAST_INSERT_ID(counter_field +
    1); 
    SELECT LAST_INSERT_ID();
    

    SELECT 语句仅检索标识符信息(特定于当前 联系)。它不访问任何表。

    换句话说,您可以通过仅访问表一次来进一步优化此模式...但同样有一些关于您的架构的详细信息我不太了解,我不确定我是否可以提供实际的你需要的声明。不过,我确实认为,如果您看一下 SELECT ... FOR UPDATE,您会看到该模式归结为什么,以及您需要做什么才能使其在您的环境中工作。

    我还应该提到,您需要考虑一些存储引擎环境和事务隔离级别。这里有关于这个主题的非常非常好的讨论:When to use SELECT ... FOR UPDATE?

    希望这会有所帮助!

    【讨论】:

    • 我最终锁定了调用方代码,从而防止了潜在的冲突。但是,非常好的解决方案谢谢!下次遇到同样问题时,我会考虑这个。
    • 在尝试使用“选择更新”锁定表时,我得到“不允许从触发器返回结果集”。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多