MySQL中的触发器和表锁

时间:2014-06-17 14:47:10

标签: mysql concurrency triggers locking

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

现在这些触发器将在多线程环境中运行,我可能可以同时访问表。 我希望我可以做这样的事情,但它被禁止(错误:错误代码:1314。存储过程中不允许LOCK ):

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)。
  3. 非常欢迎任何建议!

1 个答案:

答案 0 :(得分:9)

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

供参考:

  

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

     

这里,LOCK IN SHARE MODE不是一个好的解决方案,因为如果有两个用户   同时读取计数器,其中至少有一个最终进入   尝试更新计数器时死锁。

     

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

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

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

。 。

  

注意仅使用SELECT FOR UPDATE锁定要更新的行   禁用自动提交时(通过开始事务处理)   START TRANSACTION或将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;

类似

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,你会看到模式归结为什么,以及你需要做些什么来使你的环境工作。

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

希望这有帮助!