【问题标题】:MySQL Triggers - 5.7.23-0ubuntu0.16.04.1 - strange behavior not every time worksMySQL 触发器 - 5.7.23-0ubuntu0.16.04.1 - 奇怪的行为并非每次都有效
【发布时间】:2018-10-10 13:44:01
【问题描述】:

我已经使用 tornado 框架在 python 3 上开发了一个应用程序,该框架连接到一个巨大的 MySQL 数据库,里面有数百万行。

这个数据库是实时同步的,所以数据是插入、删除和更新的,所以创建了3个触发器插入、更新和删除。

但是触发器的行为很奇怪,有时它们工作,有时它们不工作,比如当他们决定获取数据并做他们必须做的事情时,其他时候他们只是忽略它。

MySQL版本是这个5.7.23-0ubuntu0.16.04.1

我还插入了数据库中的 3 个触发器,可能是我的触发器中的逻辑错误:

插入

BEGIN
  IF (SELECT count(*) FROM USERS_STATS WHERE LOGIN = NEW.LOGIN) = 0 THEN
    INSERT INTO USERS_STATS (LOGIN) values (NEW.LOGIN);
  END IF;
  UPDATE USERS_STATS SET
    TOTAL_DEPOSIT = TOTAL_DEPOSIT + IF(NEW.CMD = 6 AND NEW.COMMENT NOT LIKE '%internal%' AND NEW.COMMENT NOT LIKE '%Tr %' AND NEW.COMMENT NOT LIKE '%agent%' AND NEW.COMMENT NOT LIKE '%rebate%' AND NEW.COMMENT NOT LIKE '%MAM:CASH%' AND NEW.PROFIT > 0, NEW.PROFIT, 0),
    TOTAL_WITHDRAW = TOTAL_WITHDRAW + IF(NEW.CMD = 6 AND NEW.COMMENT NOT LIKE '%internal%' AND NEW.COMMENT NOT LIKE '%Tr %' AND NEW.COMMENT NOT LIKE '%agent%' AND NEW.COMMENT NOT LIKE '%rebate%' AND NEW.COMMENT NOT LIKE '%MAM:CASH%' AND NEW.PROFIT < 0, NEW.PROFIT, 0),
    TOTAL_VOLUME = TOTAL_VOLUME + IF(NEW.CMD IN(0, 1) AND NEW.CLOSE_TIME > '1970-01-01', NEW.VOLUME, 0),
    TOTAL_REBATE = TOTAL_REBATE + IF((NEW.COMMENT LIKE '%agent%' OR NEW.COMMENT LIKE '%rebate%' OR NEW.COMMENT LIKE '%MAM:CASH%'), ROUND(NEW.PROFIT, 2), 0)
  WHERE LOGIN = NEW.LOGIN;
END

更新

BEGIN
  DECLARE old_is_deposit integer;
  DECLARE new_is_deposit integer;
  DECLARE old_is_withdraw integer;
  DECLARE new_is_withdraw integer;
  DECLARE old_is_rebate integer;
  DECLARE new_is_rebate integer;
  SET old_is_deposit := OLD.CMD = 6 AND OLD.COMMENT NOT LIKE '%internal%' AND OLD.COMMENT NOT LIKE '%Tr %' AND OLD.COMMENT NOT LIKE '%agent%' AND OLD.COMMENT NOT LIKE '%rebate%' AND OLD.COMMENT NOT LIKE '%MAM:CASH%' AND OLD.PROFIT > 0;
  SET new_is_deposit := NEW.CMD = 6 AND NEW.COMMENT NOT LIKE '%internal%' AND NEW.COMMENT NOT LIKE '%Tr %' AND NEW.COMMENT NOT LIKE '%agent%' AND NEW.COMMENT NOT LIKE '%rebate%' AND NEW.COMMENT NOT LIKE '%MAM:CASH%' AND NEW.PROFIT > 0;
  SET old_is_withdraw := OLD.CMD = 6 AND OLD.COMMENT NOT LIKE '%internal%' AND OLD.COMMENT NOT LIKE '%Tr %' AND OLD.COMMENT NOT LIKE '%agent%' AND OLD.COMMENT NOT LIKE '%rebate%' AND OLD.COMMENT NOT LIKE '%MAM:CASH%' AND OLD.PROFIT < 0;
  SET new_is_withdraw := NEW.CMD = 6 AND NEW.COMMENT NOT LIKE '%internal%' AND NEW.COMMENT NOT LIKE '%Tr %' AND NEW.COMMENT NOT LIKE '%agent%' AND NEW.COMMENT NOT LIKE '%rebate%' AND NEW.COMMENT NOT LIKE '%MAM:CASH%' AND NEW.PROFIT < 0;
  SET old_is_rebate := OLD.COMMENT LIKE '%agent%' OR OLD.COMMENT LIKE '%rebate%' OR OLD.COMMENT LIKE '%MAM:CASH%';
  SET new_is_rebate := NEW.COMMENT LIKE '%agent%' OR NEW.COMMENT LIKE '%rebate%' OR NEW.COMMENT LIKE '%MAM:CASH%';
  IF (SELECT count(*) FROM USERS_STATS WHERE LOGIN = NEW.LOGIN) = 0 THEN
    INSERT INTO USERS_STATS (LOGIN) values (NEW.LOGIN);
  END IF;
  IF (old_is_deposit = 1 AND new_is_deposit = 1 AND OLD.PROFIT != NEW.PROFIT) THEN
    UPDATE USERS_STATS SET
      TOTAL_DEPOSIT = TOTAL_DEPOSIT + (NEW.PROFIT - OLD.PROFIT)
    WHERE LOGIN = OLD.LOGIN;
  ELSEIF (old_is_withdraw = 1 AND new_is_withdraw = 1 AND OLD.PROFIT != NEW.PROFIT) THEN
    UPDATE USERS_STATS SET
      TOTAL_WITHDRAW = TOTAL_WITHDRAW + (NEW.PROFIT - OLD.PROFIT)
    WHERE LOGIN = OLD.LOGIN;
  ELSEIF (old_is_deposit = 1 AND new_is_withdraw = 1) THEN
    UPDATE USERS_STATS SET
      TOTAL_DEPOSIT = TOTAL_DEPOSIT - OLD.PROFIT,
      TOTAL_WITHDRAW = TOTAL_WITHDRAW + NEW.PROFIT
    WHERE LOGIN = OLD.LOGIN;
  ELSEIF (old_is_withdraw = 1 AND new_is_deposit = 1) THEN
    UPDATE USERS_STATS SET
      TOTAL_DEPOSIT = TOTAL_DEPOSIT + NEW.PROFIT,
      TOTAL_WITHDRAW = TOTAL_WITHDRAW - OLD.PROFIT
    WHERE LOGIN = OLD.LOGIN;
  ELSEIF (old_is_deposit = 0 AND new_is_deposit = 1) THEN
    UPDATE USERS_STATS SET
      TOTAL_DEPOSIT = TOTAL_DEPOSIT + NEW.PROFIT
    WHERE LOGIN = OLD.LOGIN;
  ELSEIF (old_is_deposit = 1 AND new_is_deposit = 0) THEN
    UPDATE USERS_STATS SET
      TOTAL_DEPOSIT = TOTAL_DEPOSIT - OLD.PROFIT
    WHERE LOGIN = OLD.LOGIN;
  ELSEIF (old_is_withdraw = 0 AND new_is_withdraw = 1) THEN
    UPDATE USERS_STATS SET
      TOTAL_WITHDRAW = TOTAL_WITHDRAW + NEW.PROFIT
    WHERE LOGIN = OLD.LOGIN;
  ELSEIF (old_is_withdraw = 1 AND new_is_withdraw = 0) THEN
    UPDATE USERS_STATS SET
      TOTAL_WITHDRAW = TOTAL_WITHDRAW - OLD.PROFIT
    WHERE LOGIN = OLD.LOGIN;
  END IF;
  IF (OLD.VOLUME != NEW.VOLUME) THEN
    UPDATE USERS_STATS SET
      TOTAL_VOLUME = TOTAL_VOLUME + (NEW.VOLUME - OLD.VOLUME)
    WHERE LOGIN = OLD.LOGIN;
  END IF;
  IF (old_is_rebate = 1 AND new_is_rebate = 1 AND OLD.PROFIT != NEW.PROFIT) THEN
    UPDATE USERS_STATS SET
      TOTAL_REBATE = TOTAL_REBATE + (ROUND(NEW.PROFIT, 2) - ROUND(OLD.PROFIT, 2))
    WHERE LOGIN = OLD.LOGIN;
  ELSEIF (old_is_rebate = 0 AND new_is_rebate = 1) THEN
    UPDATE USERS_STATS SET
      TOTAL_REBATE = TOTAL_REBATE + ROUND(NEW.PROFIT, 2)
    WHERE LOGIN = OLD.LOGIN;
  ELSEIF (old_is_rebate = 1 AND new_is_rebate = 0) THEN
    UPDATE USERS_STATS SET
      TOTAL_REBATE = TOTAL_REBATE - ROUND(OLD.PROFIT, 2)
    WHERE LOGIN = OLD.LOGIN;
  END IF;
END

删除

BEGIN
  IF (SELECT count(*) FROM USERS_STATS WHERE LOGIN = OLD.LOGIN) = 0 THEN
    INSERT INTO USERS_STATS (LOGIN) values (OLD.LOGIN);
  END IF;
  UPDATE USERS_STATS SET
    TOTAL_DEPOSIT = TOTAL_DEPOSIT - IF(OLD.CMD = 6 AND OLD.COMMENT NOT LIKE '%internal%' AND OLD.COMMENT NOT LIKE '%Tr %' AND OLD.COMMENT NOT LIKE '%agent%' AND OLD.COMMENT NOT LIKE '%rebate%' AND OLD.COMMENT NOT LIKE '%MAM:CASH%' AND OLD.PROFIT > 0, OLD.PROFIT, 0),
    TOTAL_WITHDRAW = TOTAL_WITHDRAW - IF(OLD.CMD = 6 AND OLD.COMMENT NOT LIKE '%internal%' AND OLD.COMMENT NOT LIKE '%Tr %' AND OLD.COMMENT NOT LIKE '%agent%' AND OLD.COMMENT NOT LIKE '%rebate%' AND OLD.COMMENT NOT LIKE '%MAM:CASH%' AND OLD.PROFIT < 0, OLD.PROFIT, 0),
    TOTAL_VOLUME = TOTAL_VOLUME - IF(OLD.CMD IN(0, 1) AND OLD.CLOSE_TIME > '1970-01-01', OLD.VOLUME, 0),
    TOTAL_REBATE = TOTAL_REBATE - IF((OLD.COMMENT LIKE '%agent%' OR OLD.COMMENT LIKE '%rebate%' OR OLD.COMMENT LIKE '%MAM:CASH%'), ROUND(OLD.PROFIT, 2), 0)
  WHERE LOGIN = OLD.LOGIN;
END

我还附上了触发器的位置截图:

需要帮助,为什么我的触发器无法正常工作。 提前谢谢你。

【问题讨论】:

  • 触发器的问题是它们很难调试。无论如何,您很可能遇到事务死锁问题,并且由于“超出等待时间”错误(不忽略)而导致触发器失败。
  • @MadhurBhaiya 在测试手册时是的,它们可以正常工作,但不能在生产中工作,你的意思是事务死锁问题?又该如何解决?
  • 触发器基本上锁定了它们正在处理的表。因此,假设您有表 A、B、C。从 A 到 B 到 C 的触发器 1 锁。从 C 到 B 到 A 的触发器 2 锁。两者都同时尝试从任一侧锁定同一个表,因此存在死锁.
  • 不容易解决。您将不得不重新设计触发器的逻辑。请阅读:dev.mysql.com/doc/refman/8.0/en/innodb-deadlocks.html
  • @MadhurBhaiya 我认为这可能会发生,因为我们谈论的是大量数据,并且它们同时同步至少 1001 行,因此 MySQL 中会同时出现 1001 行。但我想这一定是一些技巧?

标签: mysql triggers


【解决方案1】:

所以最后我找到了一种不同的逻辑,在我的情况下无法摆脱死锁,因此我能够在触发器执行的一个时间间隔内更新所有必要的数据,而不是使用 cronjob 脚本进行上述触发器。

【讨论】:

    猜你喜欢
    • 2012-07-13
    • 1970-01-01
    • 2014-01-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多