当使用SERIALIZABLE
transactions来实现仅在值不存在时才将值插入数据库的模式时,我观察到MySQL和PostgreSQL在SERIALIZABLE
隔离级别的定义上存在显著差异。
请看下表:
CREATE TABLE person (
person_id INTEGER PRIMARY KEY AUTO_INCREMENT,
name VARCHAR NOT NULL
);
字符串
和下面的插入代码,在两个连接上并发运行:
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
START TRANSACTION;
SELECT person_id FROM person WHERE name = 'Bob Ross';
-- sync point: both transactions should run through here before proceeding to
-- demonstrate the effect
-- 0 results, so we will insert
INSERT INTO person (name) VALUES ('Bob Ross');
SELECT last_insert_id();
COMMIT;
型
在PostgreSQL中(经过适当的SQL翻译),效果正如我所料:只有一个事务可以成功提交。这与我对PostgreSQL描述的SERIALIZABLE的理解一致,以及其他引用ANSI标准的来源:存在会产生相同效果的事务的串行执行。不存在返回0结果的这两个事务的串行执行然后添加条目。
在MySQL 5.7中,两个事务都成功,表中有2个'Bob Ross'条目。MySQL文档定义了SERIALIZABLE
,禁止脏读,不可重复读和幻影读;它没有提到串行执行的存在。
SQLite还正确地防止了双重插入,至少在其默认模式下,由于其保守的锁定策略。
我的问题是:**MySQL在这种情况下的行为是正确的吗?还是说它违反了SQL标准,允许这些事务都成功?**我怀疑答案可能取决于“效果”的定义-从第一个SELECT
观察到一个空的结果集是否算作两个具有相同效果的串行执行的“效果”?
其他一些评论可以帮助解决这个问题:
- 我知道我可以在MySQL中实现所需的行为,首先用
ON CONFLICT IGNORE
执行插入,然后执行选择。我试图理解为什么等效的标准SQL在两个引擎中没有表现出相同的行为。 - 我知道我也可以通过在
name
字段上设置一个唯一的约束来解决这个问题,无论如何,这可能是一个更好的数据模型,但核心问题仍然存在:为什么这些事务都成功了?
2条答案
按热度按时间sshcrbum1#
SQL标准在第4.35.4节SQL事务的隔离级别(重点是我的)中说:
隔离级别SERIALIZABLE的并发SQL事务的执行保证是可串行化的。**可串行化的执行被定义为并发执行的SQL事务的操作的执行,其产生与这些相同SQL事务的某些串行执行相同的效果。串行执行是每个SQL事务在下一个SQL事务开始之前执行完成的执行。
再往下一点,它继续混淆这个问题:
隔离级别指定在执行并发SQL事务期间可能发生的现象类型。可能出现以下现象:
[跳过 P1(“脏读”)、P2(“不可重复读”)**和 P3(“幻影”)**的定义]
这四个隔离级别保证每个SQL事务将完全执行或根本不执行,并且不会丢失更新。隔离级别根据现象 P1、P2 和 P3 而不同。表8,“SQL事务隔离级别和三种现象”指定了给定隔离级别可能和不可能的现象。
字符串
注53 -对于在隔离级别SERIALIZABLE执行的SQL事务,排除这些现象是要求这些事务是可串行化的结果。
这种措辞产生了不幸的后果,许多实现者认为排除脏读、不可重复读和幻影读就足以正确实现
SERIALIZABLE
隔离级别,尽管注解澄清了这不是定义,而是定义的结果。所以我认为MySQL是错误的,但它并不孤单:Oracle数据库以同样的方式解释
SERIALIZABLE
。gdx19jrr2#
我不能在MySQL 5.7中重现这一点。其他事务总是得到一个错误:
错误1213(40001):试图获取锁时发现死锁;
原因是SELECT在SELECT部分中没有使用索引列,所以它将s锁设置为它找到的每一行,gap-s锁设置为找到的行之间的每一个间隙,而下一个键锁设置为找到的最后一行之后的正无穷大。因此在这种情况下,并发SELECT是不可能的。
你得到的结果的一个可能原因可能是这样的:
字符串
它只为下一个事务设置隔离级别。如果您在此之后执行了一个SELECT,隔离级别将更改回正常(REPEATABLE READ)。
最好使用
型