如果我们谈论 InnoDB 中具有唯一索引的 table ,事务会相互等待还是并行工作?

Will transactions wait for each other or work parallel if we talk about a table with a unique index in InnoDB?

我在 InnoDB 引擎中有一个 table 看起来像这样:

CREATE TABLE `sample` (
   `id` int NOT NULL AUTO_INCREMENT PRIMARY KEY,
   `unique_str` varchar(255) NOT NULL
);
ALTER TABLE `sample`
   ADD UNIQUE `unique_str_index` (`unique_str`);

如您所见,此 table 在列 "unique_str" 上有一个唯一索引。

例如,我想 运行 两个带有交易的脚本。

//First script, i use pdo for connection
$pdo->beginTransaction();
$pdo->exec("INSERT INTO sample (unique_str) VALUES('first')");
$pdo->exec("INSERT INTO sample (unique_str) VALUES('violate_str')");
sleep(50); //I'll start second script here
$pdo->commit();

当第一个脚本在 50 秒内休眠时,我将启动第二个脚本。

//Second script
$pdo->beginTransaction();
$pdo->exec("INSERT INTO sample (unique_str) VALUES('third')");
$pdo->exec("INSERT INTO sample (unique_str) VALUES('violate_str')"); 
$pdo->commit();

在第二个脚本中,PHP 将使用 "insert" 命令卡在第一个字符串上,等待第一个脚本中的事务提交。当第一个脚本从睡眠中醒来并提交事务时,将在 table 中创建两个新字符串('first' 和 'violate_str'),第二个脚本将再次开始工作:第二个脚本会经历两次插入,但在第二次插入 ('violate_str') 时会生成异常。

我的问题是“为什么 PHP 一直在等待第一个事务完成并且这次不工作?MySQL 是否阻止了第二个事务?为什么?”。我认为我对交易工作原理有一些误解。我以为交易会一个一个创建,第二个交易不会被阻塞。

您的第二个脚本正在等待 第二个 插入,而不是第一个插入。

如果我用 echo 语句测试你的代码:

//First script, i use pdo for connection
$pdo->beginTransaction();
echo "begin done\n";
$pdo->exec("INSERT INTO sample (unique_str) VALUES('first')");
echo "insert('first') done\n";
$pdo->exec("INSERT INTO sample (unique_str) VALUES('violate_str')");
echo "insert('violate_str') done\n";
sleep(50); //I'll start second script here
echo "sleep done\n";
$pdo->commit();
echo "commit done\n";

//Second script
$pdo->beginTransaction();
echo "begin done\n";
$pdo->exec("INSERT INTO sample (unique_str) VALUES('third')");
echo "insert('third') done\n";
$pdo->exec("INSERT INTO sample (unique_str) VALUES('violate_str')");
echo "insert('violate_str') done\n";
$pdo->commit();
echo "commit done\n";

我看到第一个脚本的输出:

begin done
insert('first') done
insert('violate_str') done

现在它已经开始休眠,所以我 运行 第二个脚本立即看到这个输出:

begin done
insert('third') done

此时等待。所以我们知道它无需等待就完成了一次插入。它正在等待第二个插入,与 'violate_str' 的插入冲突的插入。那是因为第一个脚本已经在唯一索引中创建了该条目,即使它还没有提交。它仍然持有索引中该值的锁,因此第二个脚本必须等待该锁。

睡眠结束后,第一个脚本提交并输出:

sleep done
commit done

然后第二个脚本可以获取锁,并尝试插入该行。但不幸的是,第一个脚本已经插入并提交了该值,因此我们遇到了重复键违规。

Fatal error: Uncaught PDOException: SQLSTATE[23000]: Integrity constraint violation: 
1062 Duplicate entry 'violate_str' for key 'unique_str'

如果第一个脚本执行 rollback() 而不是 commit() 那么它会释放它的锁并撤消插入。这允许第二个脚本完成 自由插入有争议的值。