我在我的 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 ;
重现步骤:
- 在上面的脚本中运行以创建一个测试数据库、两个表和一个存储过程。
在两个单独的连接中,尽可能接近同时运行存储过程(
SLEEP
如果需要更长的时间,可以增加时间):USE `testDB`; CALL sp_test ();
问题
当通过两个单独的连接同时执行时,该SELECT COUNT(*) FROM `tbl_test`;
语句为两个调用返回不同的值。
当我按照上述步骤操作时,我会1
从两个过程调用中的第一个和0
第二个返回。
我对事务行为和表锁定的理解是,当第一次调用到达INSERT
语句时,它将创建一个锁。第二个过程调用将到达同一行,但必须等到第一个调用的事务已提交。增加睡眠时间强化了这个想法,因为第二次通话需要两倍的时间才能完成。但是,如果是这种情况,那么第二个过程调用应该从第一个调用中提取插入,并且两个结果都应该等于1
.
TL; DR
我希望两者都相等1
请注意,我使用READ_COMMITTED
的是我的事务隔离级别。
我已经使用MYSQL server
和测试了这个MariaDB
进一步的怪异
所以在这一点上,我认为我的理解是不正确的。但是,我随后注意到,通过删除该行SELECT * FROM `tbl_test2`;
,结果突然产生了预期值!
我一直在尝试使用该脚本,但本质上,包括在该行导致意外结果SELECT
之前对数据库中任何表的语句。INSERT
我完全不知道为什么会这样。
问题
- 我对预期交易行为的理解是否正确?
- 为什么对不
SELECT
相关表的语句会导致事务锁定失败?
如果有人能对此有所了解,我将不胜感激!