首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >触发器正在计算错误的和,给出了意想不到的结果。

触发器正在计算错误的和,给出了意想不到的结果。
EN

Stack Overflow用户
提问于 2016-07-09 19:31:18
回答 2查看 405关注 0票数 1

我的扳机有个奇怪的问题。有两个表:Table ATable B

每当向Table A插入一行时,此表中列的和被插入到Table B中。

它一开始运行良好,但最近我注意到,当用户在确切的时间插入>1行时,触发器会以一种奇怪的方式返回sum。

代码语言:javascript
运行
复制
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之和(两者之间几乎没有延迟):

  1. 为他插入号码5
  2. 为他插入号码7

表B

在我们保存和的这个表上,这个用户的和是15

触发器以这种方式更新此表:

  1. 20
  2. 22 <--错,这应该是27

如您所见,没有插入任何数字2,由于某种原因,它添加了7-5 =2。

这怎么可能?为什么它从7中减去5,然后把2加到总和中,而不是通常加7呢?

编辑1:

警告:这不起作用,请检查已接受的答案

其中一个答案建议选择更新方法。

这个SELECT ... FOR UPDATE会对性能产生巨大的负面影响吗?

代码语言:javascript
运行
复制
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)),以减少同时插入的可能性。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2016-07-09 22:35:09

造成这种行为的原因是,插入的数据只有在触发器完成执行时才提交给数据库。因此,当两个insert操作(5和7)并行执行触发器时,它们读取其事务中的数据,即具有在自己事务中所做更改的已提交数据,而不是在任何其他正在进行的事务中所做的更改。

表A中提交的数据汇总了两个事务的20条数据,并将插入到它们自己事务中的记录添加到表A中。对于一个这是5,另一个是7,但是由于这些记录还没有提交,另一个事务没有看到这个值。

这就是为什么和为一个是20+5,另一个是20+7。然后,事务将一个接一个地更新表B (因为表B将在更新过程中被锁定,直到事务结束),以及最新的“胜利”表。

要解决这个问题,不要读取表A中的和,而是在表B中保留一个运行的和:

代码语言:javascript
运行
复制
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;
/

我想您已经有了deleteupdateTable_B上的触发器,否则就会有另一个不一致的来源。

因此,这些也需要(重新)编写:

代码语言:javascript
运行
复制
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;
/

这样可以防止在触发器中锁定潜在的许多行。

然后,在完成上述操作之后,您可以修复过去的问题,并进行一次性更新:

代码语言:javascript
运行
复制
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;
票数 3
EN

Stack Overflow用户

发布于 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的数据进行操作,就像三轮车所建议的那样。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/38285587

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档