并发MYSQL过程调用使用不相关的SELECT语句返回不同的结果

时间:2016-09-15 14:26:51

标签: mysql stored-procedures transactions mariadb table-locking

我在MYSQL应用程序中遇到了一些非常奇怪的事务行为。

我已经设法将问题减少到一个小的孤立测试用例,我在下面包含的代码:

-- Setup a new environment
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
DROP DATABASE IF EXISTS `testDB`;
CREATE DATABASE `testDB`;
USE `testDB`;

-- Create a table I want two procedure calls to interact with
CREATE TABLE `tbl_test` (
    `id` INT(10) UNSIGNED NOT NULL
    , PRIMARY KEY (`id`)
);

-- A second table purely to demonstrate the issue
CREATE TABLE `tbl_test2` (
    `id` INT(10) UNSIGNED NOT NULL
);


DELIMITER $$

DROP PROCEDURE IF EXISTS `sp_test` $$
CREATE PROCEDURE `sp_test` ()
BEGIN

    START TRANSACTION;

        -- CRAZY LINE
        SELECT * FROM `tbl_test2`;

        -- Insert ignore so both calls don’t try to insert the same row
        INSERT IGNORE INTO `tbl_test` (`id`) VALUES (1);

        -- Sleep added to make it possible to run concurrently manually
        SELECT SLEEP(1) INTO @rubbish; 

        -- The result I am interested in
        SELECT COUNT(*) FROM `tbl_test`;

    COMMIT;

END $$

DELIMITER ;

重现步骤:

  1. 运行上述脚本以创建测试数据库,两个表和一个存储过程。
  2. 在两个单独的连接中,尽可能接近同时运行存储过程(如果需要更长时间,可以增加SLEEP时间):

    USE `testDB`;
    CALL sp_test ();
    
  3. 问题

    当在两个单独的连接上并发执行时,SELECT COUNT(*) FROM `tbl_test`;语句为两个调用返回不同的值。

    当我按照上述步骤操作时,我会从两个过程调用中的第一个回复1,从第二个过程回到0

    我对事务行为和表锁定的理解是,当第一个调用到达INSERT语句时,它将创建一个锁。第二个过程调用将到达同一行,但必须等到第一次调用的事务已提交。增加睡眠时间强化了这一想法,因为第二次呼叫需要两倍的时间才能完成。但是,如果是这种情况,那么第二个过程调用应该从第一个调用中获取插入,并且两个结果应该等于1

    TL; DR 我期待两者都等于1

    请注意,我使用READ_COMMITTED作为我的事务隔离级别。

    我使用MYSQL serverMariaDB

    对此进行了测试

    更奇怪

    所以在这一点上我认为我的理解是错误的。但是,我注意到,通过删除行SELECT * FROM `tbl_test2`;,结果突然产生了预期值!

    我一直在尝试使用脚本,但实际上,在SELECT行导致意外结果之前,包括INSERT语句到数据库中的任何表。我完全不知道为什么会这样。

    问题

    1. 我对预期交易行为的理解是否正确?
    2. 为什么对不相关的表的SELECT语句导致事务锁定失败?
    3. 如果有人能对此有所了解,我将非常感激!

0 个答案:

没有答案