我的扳机有个奇怪的问题。有两个表:Table A
和Table B
。
每当向Table A
插入一行时,此表中列的和被插入到Table B
中。
它一开始运行良好,但最近我注意到,当用户在确切的时间插入>1行时,触发器会以一种奇怪的方式返回sum。
CREATE TRIGGER `update_something` AFTER INSERT ON `Table_A`
FOR EACH ROW BEGIN
DECLARE sum BIGINT(20);
SELECT IFNULL(SUM(number), 0) INTO sum FROM Table_A WHERE `user` = NEW.user;
UPDATE Table_B SET sum_number = sum WHERE id = NEW.id;
END
示例:
表A
用户X当前有一个15
之和(两者之间几乎没有延迟):
5
7
表B
在我们保存和的这个表上,这个用户的和是15
触发器以这种方式更新此表:
20
22
<--错,这应该是27
如您所见,没有插入任何数字2
,由于某种原因,它添加了7-5 =2。
这怎么可能?为什么它从7中减去5,然后把2加到总和中,而不是通常加7呢?
编辑1:
警告:这不起作用,请检查已接受的答案
其中一个答案建议选择更新方法。
这个SELECT ... FOR UPDATE
会对性能产生巨大的负面影响吗?
CREATE TRIGGER `update_something` AFTER INSERT ON `Table_A`
FOR EACH ROW BEGIN
DECLARE sum BIGINT(20);
SELECT IFNULL(SUM(number), 0) INTO sum FROM Table_A WHERE `user` = NEW.user FOR UPDATE;
UPDATE Table_B SET sum_number = sum WHERE id = NEW.id;
END
基本上,我们只将FOR UPDATE
添加到这样的SELECT行的末尾,它将执行InnoDB中的行锁来修复这个问题?
SELECT IFNULL(SUM(number), 0) INTO sum FROM Table_A WHERE user = NEW.user FOR UPDATE;
编辑2(临时修复):
如果有人在执行实际和逻辑建议的修复之前需要非常快速的临时修复:我所做的是在PHP查询之前放置一个随机的usleep(rand(1,500000))
,以减少同时插入的可能性。
发布于 2016-07-09 22:35:09
造成这种行为的原因是,插入的数据只有在触发器完成执行时才提交给数据库。因此,当两个insert操作(5和7)并行执行触发器时,它们读取其事务中的数据,即具有在自己事务中所做更改的已提交数据,而不是在任何其他正在进行的事务中所做的更改。
表A中提交的数据汇总了两个事务的20条数据,并将插入到它们自己事务中的记录添加到表A中。对于一个这是5,另一个是7,但是由于这些记录还没有提交,另一个事务没有看到这个值。
这就是为什么和为一个是20+5,另一个是20+7。然后,事务将一个接一个地更新表B (因为表B将在更新过程中被锁定,直到事务结束),以及最新的“胜利”表。
要解决这个问题,不要读取表A中的和,而是在表B中保留一个运行的和:
CREATE TRIGGER `update_something` AFTER INSERT ON `Table_A`
FOR EACH ROW BEGIN
UPDATE Table_B SET sum_number = sum_number + NEW.number WHERE id = NEW.id;
END;
/
我想您已经有了delete
和update
在Table_B
上的触发器,否则就会有另一个不一致的来源。
因此,这些也需要(重新)编写:
CREATE TRIGGER `delete_something` AFTER DELETE ON `Table_A`
FOR EACH ROW BEGIN
UPDATE Table_B SET sum_number = sum_number - OLD.number WHERE id = OLD.id;
END;
/
CREATE TRIGGER `update_something` AFTER UPDATE ON `Table_A`
FOR EACH ROW BEGIN
UPDATE Table_B SET sum_number = sum_number - OLD.number WHERE id = OLD.id;
UPDATE Table_B SET sum_number = sum_number + NEW.number WHERE id = NEW.id;
END;
/
这样可以防止在触发器中锁定潜在的许多行。
然后,在完成上述操作之后,您可以修复过去的问题,并进行一次性更新:
update Table_B
join (select id, user, ifnull(sum(number),0) sum_number
from Table_A
group by id, user) A
on Table_B.id = A.id
and Table_B.sum_number <> A.sum_number
set Table_B.sum_number = A.sum_number;
发布于 2016-07-09 22:33:33
这是因为触发器中的种族状况。两个触发器同时触发,因此SELECT
对它们都返回相同的值-- 15。然后首先触发更新tha值添加5并导致20,然后以15 +7= 22运行第二个更新。
您应该做的是使用SELECT ... FOR UPDATE
代替。这样,如果第一个触发器发出select,那么第二个触发器将不得不等待第一个触发器完成。
编辑:
你的问题让我思考,也许使用FOR UPDATE
不是最好的解决方案。根据文档
对于索引记录搜索遭遇,选择..。对于UPDATE锁定行和任何关联的索引项,就像您为这些行发出了UPDATE语句一样。
而且,由于您正在从Table A
中选择条目之和,它将锁定这些条目,但仍然允许插入新条目,因此问题不会得到解决。
最好只对触发器内来自Table B
的数据进行操作,就像三轮车所建议的那样。
https://stackoverflow.com/questions/38285587
复制相似问题