MySQL中的触发器和表锁

Doc*_*Dbg 13 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 ;
Run Code Online (Sandbox Code Playgroud)

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

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

任何建议都非常欢迎!

eva*_*anv 10

我认为处理这个最好的办法是使用SELECT ... FOR这里描述的更新模式:http://dev.mysql.com/doc/refman/5.0/en/innodb-locking-reads.html

以供参考:

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

在这里,LOCK IN SHARE MODE不是一个好的解决方案,因为如果两个用户同时读取计数器,则当它们尝试更新计数器时,其中至少有一个会陷入死锁.

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

 SELECT counter_field FROM child_codes FOR UPDATE; UPDATE child_codes
 SET counter_field = counter_field + 1; 
Run Code Online (Sandbox Code Playgroud)

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

...

注意使用SELECT FOR UPDATE锁定要更新的行仅在禁用自动提交时应用(通过使用START TRANSACTION开始事务或将自动提交设置为0.如果启用了自动提交,则不会锁定与规范匹配的行.

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

LOCK TABLES AlarmCount WRITE, AlarmMembership READ;
  UPDATE AlarmCount SET num = num - 1 
  WHERE RuleId = OLD.RuleId AND
      MemberId = 0 AND
      IsResolved = OLD.IsResolved;
Run Code Online (Sandbox Code Playgroud)

有类似的东西

SELECT num FROM AlarmCount WHERE RuleId = OLD.RuleId AND
          MemberId = 0 AND
          IsResolved = OLD.IsResolved FOR UPDATE;
UPDATE AlarmCount SET num = num - 1;
Run Code Online (Sandbox Code Playgroud)

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

前面的描述仅仅是SELECT ... FOR UPDATE如何工作的一个例子.在MySQL中,生成唯一标识符的具体任务实际上只需对表进行一次访问即可完成:

UPDATE child_codes SET counter_field = LAST_INSERT_ID(counter_field +
1); 
SELECT LAST_INSERT_ID();
Run Code Online (Sandbox Code Playgroud)

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

换句话说,你可以通过只访问一次表来进一步优化这种模式......但是还有一些关于你的模式的细节,我不太关注,我不确定我能提供你的实际陈述'需要.我确实认为,如果你看看SELECT ... FOR UPDATE,你会看到模式归结为什么,以及你需要做些什么才能使你的环境工作.

我还要提一下,您需要考虑一些存储引擎环境和事务隔离级别.关于这个主题的SO有一个非常非常好的讨论:何时使用SELECT ... FOR UPDATE?

希望这可以帮助!