3

概括

给定一个调用存储过程的事件,确保一次只运行该过程的一个实例的最佳实践是什么?特别是在程序运行时间有时可能比事件结束时间更长的情况下。


例子

让我们看下面这个虚构的例子,一个需要1秒才能结束的事件和一个需要5秒才能执行的过程:

程序:

DELIMITER ;;
CREATE PROCEDURE `P_wait`()
BEGIN
    SELECT SLEEP(5);
END;;
DELIMITER ;

事件:

DROP EVENT IF EXISTS `E_wait`;
DELIMITER ;;
CREATE EVENT `E_wait`
    ON SCHEDULE
        EVERY 1 SECOND
    DO
        BEGIN
            CALL `P_wait`();   //proc_call
        END;;
DELIMITER ;

如您所料,当事件运行时,您将SLEEP()在 中看到 5 个实例PROCESSLIST

mysql> SHOW PROCESSLIST;
+-------+------+-----------+-------+---------+------+-------------+------------------+
| Id    | User | Host      | db    | Command | Time | State       | Info             |
+-------+------+-----------+-------+---------+------+-------------+------------------+
| 27045 | root | localhost | temp  | Query   |    0 | NULL        | SHOW PROCESSLIST |
| 27069 | root | localhost | temp  | Connect |    4 | User sleep  | SELECT SLEEP(5)  |
| 27070 | root | localhost | temp  | Connect |    3 | User sleep  | SELECT SLEEP(5)  |
| 27072 | root | localhost | temp  | Connect |    2 | User sleep  | SELECT SLEEP(5)  |
| 27073 | root | localhost | temp  | Connect |    1 | User sleep  | SELECT SLEEP(5)  |
| 27074 | root | localhost | temp  | Connect |    0 | User sleep  | SELECT SLEEP(5)  |
+-------+------+-----------+-------+---------+------+-------------+------------------+

虽然在此示例中并非如此,但您可以看到如果过程被阻塞(例如,如果它包含带有SELECT ... FOR UPDATE语句的事务),这将迅速升级为问题。


问题

确保当E_wait每秒滴答声时,如果一个实例P_wait仍在运行,它不会再次被调用的最佳方法是什么?我一次最多只想要一个P_wait运行实例。这里的最佳实践是什么?

/*PROCEDURE_NOT_RUNNING*/基本上,如果我按如下方式修改事件,我需要代替什么:

    ...
        BEGIN
            IF /*PROCEDURE_NOT_RUNNING*/ THEN
                CALL wait_test();   //proc_call
            END IF;
        END;;
    ...
  • 从过程中更改事件E_wait(在开始时禁用P_wait并在结束时重新启用)不是一种选择,因为如果服务器在禁用时重新启动,这可能导致事件根本不运行。
  • 我可以尝试将一个值存储在一个表中并使用它来确定该过程是否仍在运行,但这似乎存在相同的问题,即如果服务器在错误的时间停止运行,则可能会锁定。
  • 本次讨论的重点是该过程可能需要任意长的时间才能执行,因此仅将事件时间表更改为 5 秒是不可接受的:P

期望的结果

要运行上述事件,并且最多只能看到一个SELECT SLEEP(5)in实例SHOW PROCESSLIST

4

2 回答 2

5

有人建议使用一些内置的锁定功能来解决这个问题。我会试一试,如果我设法解决了这个问题,我会更新。

编辑:

在修改事件以包含锁后,我达到了预期的结果:

DROP EVENT IF EXISTS `E_wait`;
DELIMITER ;;
CREATE EVENT `E_wait`
    ON SCHEDULE
        EVERY 1 SECOND
    DO
        BEGIN

            SELECT GET_LOCK('temp.E_wait', 0) INTO @got_lock;
            IF @got_lock = 1 THEN

                CALL `P_wait`();

                SELECT RELEASE_LOCK('temp.E_wait') INTO @discard;
            END IF;

        END;;
DELIMITER ;

这个stackoverflow问题的答案有所帮助。

于 2013-10-08T21:32:38.523 回答
0

在我的情况下(使用 MariaDB),我必须使用表(running)来存储程序何时开始运行。

CREATE TABLE `running` (
  `process` varchar(100) NOT NULL,
  `started_at` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`process`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

并且在运行程序时,您可以检查它是否已(由任何人)启动。在我的情况下超时是 15 分钟。

    set @already_running = (
        select process from running
        where process = "my_proc_name" and started_at > (now() - interval 15 minute)
    );

    if (not isnull(@already_running)) then
        select 'my_proc_name already running...';
        leave `my_proc`;
    end if;

    update running set started_at = now() where process = "my_proc_name";

但是您必须命名您的程序才能进行leave `my_proc`;退出程序。

CREATE PROCEDURE `some_other_name`()
`my_proc`:
BEGIN
...
-- before you exit you can put any old time that is more than your interval to check
update running set started_at = (now() - interval 1 hour) where process = "my_proc_name";
END
于 2021-07-12T04:25:53.413 回答