MySQL GET_LOCK()应该没有失败

时间:2017-04-05 15:11:10

标签: mysql stored-procedures

MySQL版本= 5.7.16

我需要自动处理数据库中的一些数据行。我有一个表的主键队列,以及应该一起工作的三段代码:

  1. 每十秒触发一次的事件,调用:
  2. 一个控制过程,从队列表中选择各行,并将它们传递给:
  3. 在数据的各行上执行业务逻辑的行级过程
  4. 通常,要完成的工作量需要十秒以上才能完成,因此事件将在它调用的过程完成之前再次触发。这使得进程争用相同的行,所以我不希望这种情况发生。

    我基本上用if(get_lock())语句包装了控制过程中的所有内容:

    drop procedure if exists schema.controlling_procedure;
    
    delimiter $$
    create procedure schema.controlling_procedure()
    begin
      declare lnRowsToProcess int default 0;
    
      declare continue handler for sqlexception
      begin
        do release_lock('controlling_procedure');
      end;
    
      if (get_lock('controlling_procedure',1)) then
    
        select count(*)
        into   lnRowsToProcess
        from   vcs_raw.sys_pfq_1;
    
        if (lnRowsToProcess > 0) then
          begin
            ...
            declare zzzzzz
            ...    
    
            read_loop: loop
              select min(primary_key)
              into   thePrimaryKey
              from   vcs_raw.sys_pfq_1;
    
              if (thePrimaryKey is null)then
                leave read_loop;
              end if;
    
              call schema.row_level_procedure(thePrimaryKey);
    
              delete
              from  vcs_raw.sys_pfq_1
              where job_id = thePrimaryKey;
    
              set thePrimaryKey = null;
            end loop;
          end;
        end if;
      end if;
    
      do release_lock('controlling_procedure');
    end$$
    DELIMITER ;
    

    我希望会发生的是,如果一个controls_procedure的实例已经在运行,那么同一过程的任何新实例都将无法获得锁定,并在不读取队列表或调用row_level_procedure的情况下退出。

    但是当我查看Workbench的客户端连接屏幕时,我可以看到越来越多的连接,所有这些连接都将其Info值设置为:

    call schema.row_level_procedure(thePrimaryKey);
    

    表格中出现了新的连接,其频率由事件决定(我已经尝试了事件安排)。

    看起来if(get_lock))测试总是在传递,即使同一控制过程的其他实例已在运行。

    我误解了什么,或做错了什么?

2 个答案:

答案 0 :(得分:0)

我无法用一个简单的例子重现问题:

mysql> SELECT VERSION();
+-----------+
| VERSION() |
+-----------+
| 5.7.17    |
+-----------+
1 row in set (0.00 sec)

mysql> SET GLOBAL event_scheduler = ON;
Query OK, 0 rows affected (0.00 sec)

mysql> DROP EVENT IF EXISTS `evt_test`;
Query OK, 0 rows affected (0.00 sec)

mysql> DROP PROCEDURE IF EXISTS `sp_test`;
Query OK, 0 rows affected (0.00 sec)

mysql> DROP TABLE IF EXISTS `tbl_test`, tbl_attempts;
Query OK, 0 rows affected (0.00 sec)

mysql> CREATE TABLE IF NOT EXISTS `tbl_attempts` (
    ->   `id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    ->   `connection_id` BIGINT UNSIGNED,
    ->   `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP()
    -> );
Query OK, 0 rows affected (0.00 sec)

mysql> CREATE TABLE IF NOT EXISTS `tbl_test` (
    ->   `id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    ->   `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP()
    -> );
Query OK, 0 rows affected (0.01 sec)

mysql> DELIMITER //

mysql> CREATE PROCEDURE `sp_test`()
    -> BEGIN
    ->   INSERT INTO `tbl_attempts` (`connection_id`) VALUES (CONNECTION_ID());
    ->   IF (GET_LOCK('controlling_procedure', 0)) THEN
    ->     DO BENCHMARK(35000000, AES_ENCRYPT('hello', 'goodbye'));
    ->     DO RELEASE_LOCK('controlling_procedure');
    ->     INSERT INTO `tbl_test` (`id`) VALUES (NULL);
    ->   END IF;
    -> END//
Query OK, 0 rows affected (0.00 sec)

mysql> DELIMITER ;

mysql> CREATE EVENT `evt_test` ON SCHEDULE EVERY 1 SECOND
    -> STARTS CURRENT_TIMESTAMP
    -> ENDS CURRENT_TIMESTAMP + INTERVAL 10 SECOND
    -> ON COMPLETION PRESERVE
    ->   DO CALL `sp_test`;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT `id`, `connection_id`, `created_at`
    -> FROM `tbl_attempts`;
+----+---------------+---------------------+
| id | connection_id | created_at          |
+----+---------------+---------------------+
|  1 |            62 | 2010-01-01 00:00:17 |
|  2 |            63 | 2010-01-01 00:00:18 |
|  3 |            64 | 2010-01-01 00:00:19 |
|  4 |            65 | 2010-01-01 00:00:20 |
|  5 |            66 | 2010-01-01 00:00:21 |
|  6 |            67 | 2010-01-01 00:00:22 |
|  7 |            68 | 2010-01-01 00:00:23 |
|  8 |            69 | 2010-01-01 00:00:24 |
|  9 |            70 | 2010-01-01 00:00:25 |
| 10 |            71 | 2010-01-01 00:00:26 |
| 11 |            72 | 2010-01-01 00:00:27 |
+----+---------------+---------------------+
11 rows in set (0.00 sec)

mysql> SELECT `id`, `created_at`
    -> FROM `tbl_test`;
+----+---------------------+
| id | created_at          |
+----+---------------------+
|  1 | 2010-01-01 00:00:26 |
|  2 | 2010-01-01 00:00:35 |
+----+---------------------+
2 rows in set (0.00 sec)

答案 1 :(得分:0)

问题是这段代码:

declare continue handler for sqlexception
  begin
    do release_lock('controlling_procedure');
  end;

行级过程的单个调用通常有效(超过95%的时间),但有些人抛出异常。上面的处理程序捕获了异常,释放了锁,然后继续。因为锁现在可用,所以下次触发事件时,它调用的控制过程可以获得锁,现在还有另一个控制过程正在运行。最终它也可能放弃锁定,允许另一个程序运行。

应该发生的事情是让处理程序捕获异常,释放锁定,终止,而不是继续,或者只是在运行时不释放锁。