【问题标题】:Trigger optimisation触发优化
【发布时间】:2019-06-13 23:11:04
【问题描述】:

如何优化此触发器?它工作得很好,但我认为它可以改进:

CREATE TRIGGER `update_request_missions_after_insert` BEFORE INSERT ON `request_missions`
 FOR EACH ROW
IF ((SELECT id FROM request_missions ORDER BY id DESC LIMIT 1)+1) >= 1  AND 
   ((SELECT id FROM request_missions ORDER BY id DESC LIMIT 1)+1) <= 9 THEN
        SET New.rm_number = CONCAT(DATE_FORMAT(CURDATE(),'%y'), '-',
            CONCAT('00000',(SELECT id FROM request_missions ORDER BY id DESC LIMIT 1)+1));
    END IF

结果将在rm_number 列中,例如:

对于 id >=1 和 id

19-000001
19-000002
...
19-000009

【问题讨论】:

  • 欢迎来到 StackOverflow!请注意,关于代码改进的问题——当代码运行时——更适合在CodeReview 上提问。
  • 我不相信这会起作用 - id = 1 将获取 null 因为 before 触发器不可能知道 id 1 将被分配给第一个插入,因为它是在 before 触发器之后分配的。任何 id > 8 的东西都将获得 null。 2 到 8(含)之间的任何值都将分配一个与 id 匹配的数字(即 19-000002 到 19-000008)。 imo,唯一安全的方法是在插入之后使用单独的更新语句来执行此操作,这可能是事务的一部分。
  • 谢谢 P.Salmon ,好的,我已经更新了上面的代码,我关心的是如何用另一个更短的表达式 (SELECT id FROM request_missions ORDER BY ID DESC LIMIT 1) +1) 替换例如一个变量。

标签: mysql triggers mariadb


【解决方案1】:

感谢 M. Bill Karwin,代码现在更干净了 对于今年的问题,我将其更改为 '%Y' 。 现在的代码是:

CREATE TRIGGER `update_request_missions_after_insert` BEFORE INSERT ON `request_missions`
FOR EACH ROW
BEGIN
  DECLARE next_id INT;
  SELECT COALESCE(MAX(id),0) + 1 INTO next_id FROM request_missions;
  IF next_id BETWEEN 1 AND 9 THEN
    SET NEW.rm_number = CONCAT(DATE_FORMAT(CURDATE(),'%Y'), '-', '00000', next_id);
  ELSEIF next_id BETWEEN 10 AND 99 THEN
    SET NEW.rm_number = CONCAT(DATE_FORMAT(CURDATE(),'%Y'), '-', '0000', next_id);
  ELSEIF next_id BETWEEN 100 AND 999 THEN
    SET NEW.rm_number = CONCAT(DATE_FORMAT(CURDATE(),'%Y'), '-', '000', next_id);
  ELSEIF next_id BETWEEN 1000 AND 9999 THEN
    SET NEW.rm_number = CONCAT(DATE_FORMAT(CURDATE(),'%Y'), '-', '00', next_id);
  ELSEIF next_id BETWEEN 10000 AND 99999 THEN
    SET NEW.rm_number = CONCAT(DATE_FORMAT(CURDATE(),'%Y'), '-', '0', next_id);
  ELSEIF next_id BETWEEN 100000 AND 999999 THEN
    SET NEW.rm_number = CONCAT(DATE_FORMAT(CURDATE(),'%Y'), '-', next_id);
  END IF;
END

警告:这会受到竞争条件的影响。例如,如果两个 会话同时插入到表中,它们可能都 读取相同的 next_id。

知道此触发器的目的是为添加的每一行添加一个格式为“YYYY-ID”的数字,是否有解决此问题的方法。 YYYY:是当前年份 ID : 是当前行的id,格式如触发器所示

【讨论】:

  • 竞争条件的一种解决方案是在插入之前锁定表,因此不能有两个并发会话尝试插入。
  • 另一种解决方案是使用另一个表或数据库外部的东西来产生递增的数字。
【解决方案2】:

这是使用局部变量而不是重复子查询三次的建议。你说那是你关心的问题。

CREATE TRIGGER `update_request_missions_after_insert` BEFORE INSERT ON `request_missions`
FOR EACH ROW
BEGIN
  DECLARE next_id INT;
  SELECT COALESCE(MAX(id),0) + 1 INTO next_id FROM request_missions;
  IF next_id BETWEEN 1 AND 9 THEN
    SET NEW.rm_number = CONCAT(DATE_FORMAT(CURDATE(),'%y'), '-', '00000', next_id);
  END IF;
END

警告:这会受到竞争条件的影响。例如,如果两个会话同时插入到表中,它们可能都读取相同的next_id

正如@P.Salmon 上面评论的那样,如果next_id > 9,则结果未定义。我不确定您要做什么。

您可能希望允许更长的数字,并用零将它们填充为固定长度:

LPAD(next_id, 6, '0')

请参阅LPAD() 函数的手册。

我使用COALESCE(MAX(id),0) 确保至少返回一个非 NULL 值,以防表有零行。

这也涉及到:

DATE_FORMAT(CURDATE(),'%y')

两位数的年份将在 81 年后滚动到 00。这正是千年虫问题的产生方式!我知道到那时你不会处理代码,所以你可能不在乎。但是,您应该始终像最终维护您的代码的人是一个知道您住在哪里的暴力精神病患者一样编写代码。


在另一个答案中发布您的新代码:

这是处理高达 999,999 的 next_id 数字的更好解决方案。

CREATE TRIGGER `update_request_missions_after_insert` BEFORE INSERT ON `request_missions`
FOR EACH ROW
BEGIN
  DECLARE next_id INT;
  SELECT COALESCE(MAX(id),0) + 1 INTO next_id FROM request_missions;
  IF next_id < 1000000 THEN
    SET NEW.rm_number = CONCAT(DATE_FORMAT(CURDATE(),'%y'), '-', LPAD(next_id, 6, '0'));
  ELSE
    SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'RM Number has reached capacity'
  END IF;
END

竞态问题不同,当您使用MAX(id) 查找最后一个id 时没有解决方案。问题是您可能有多个客户端同时插入到表中。每个客户端的会话都会为MAX(id) 读取相同的值并使用它。

你应该阅读Race Conditions

您真正需要的是某种使用AUTO_INCREMENT 值的方法,该值以线程安全的方式生成新的唯一值,并将该值复制到您的rm_number 中。

很遗憾,无法在触发器中执行此操作。在 BEFORE INSERT 触发期间,尚未生成新的 AUTO_INCREMENT 值。在 AFTER INSERT 触发期间,您无法修改 NEW.rm_number,因为插入已经完成。我过去写过这个:

【讨论】:

  • 谢谢比尔·卡尔文先生
  • 感谢 M. Bill Karwin 的触发器 完美工作 我只是在 `SIGNAL '45000' SET MESSAGE_TEXT = 'RM Number has reached capacity' 中添加了SQLSTATE 我认为没有错!!
  • 是的,对不起,我忘了SQLSTATE。我已经更正了答案中的示例。
  • 但是,我仍然觉得您不了解竞态条件问题。我的例子没有解决这个问题。
  • 我想我已经理解了竞态条件,但你说没有触发器可以解决这个问题。也许在插入数据后更新 rm_number 的值是解决方案?你觉得呢?
猜你喜欢
  • 2011-11-13
  • 1970-01-01
  • 1970-01-01
  • 2021-08-24
  • 2020-06-09
  • 1970-01-01
  • 2014-09-16
  • 1970-01-01
  • 2020-01-08
相关资源
最近更新 更多