MySQL 比 PostgreSQL 更轻松地解释 SERIALIZABLE。正确吗?

问题描述 投票:0回答:2

当使用

SERIALIZABLE
事务实现仅在值尚不存在时才将值插入数据库的模式时,我观察到 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
算作“效果”吗?

还有一些其他评论可以帮助确定这个问题的范围:

  • 我知道我可以通过首先使用
    ON CONFLICT IGNORE
    进行插入,然后进行选择来实现 MySQL 中所需的行为。我试图理解为什么等效的标准 SQL 在两个引擎中没有表现出相同的行为。
  • 我知道我也可以通过对
    name
    字段施加唯一的约束来修复它,无论如何,这可以说是一个更好的数据模型。但核心问题仍然存在:为什么这些交易都成功了?
mysql sql postgresql standards transaction-isolation
2个回答
8
投票

SQL 标准在第 4.35.4 SQL 事务的隔离级别(强调我的)中说:

在隔离级别 SERIALIZABLE 下执行并发 SQL 事务保证是可序列化的。 可序列化执行被定义为并发执行 SQL 事务的操作的执行,其产生与相同 SQL 事务的某些串行执行相同的效果。 串行执行是每个 SQL 事务执行完成的执行在下一个 SQL 事务开始之前。

再往下一点,它继续使问题变得混乱:

隔离级别指定了并发 SQL 事务执行期间可能发生的现象类型。可能出现以下现象:

[跳过P1(“脏读”)P2(“不可重复读”)P3(“幻影”)]

的定义

四个隔离级别保证每个SQL事务都将被完全执行或根本不执行,并且不会丢失任何更新。现象 P1P2P3 的隔离级别不同。表 8“SQL 事务隔离级别和三种现象”指定了可能和不可能的现象 对于给定的隔离级别是可能的。

+------------------+--------------+--------------+--------------+ 
| Level            | P1           | P2           | P3           |
+------------------+--------------+--------------+--------------+
| READ UNCOMMITTED | Possible     | Possible     | Possible     |
+------------------+--------------+--------------+--------------+
| READ COMMITTED   | Not Possible | Possible     | Possible     |
+------------------+--------------+--------------+--------------+
| REPEATABLE READ  | Not Possible | Not Possible | Possible     |
+------------------+--------------+--------------+--------------+
| SERIALIZABLE     | Not Possible | Not Possible | Not Possible |
+------------------+--------------+--------------+--------------+

注释 53 — 在隔离级别 SERIALIZABLE 下执行的 SQL 事务排除这些现象是要求此类事务可序列化的结果。

这种措辞带来了不幸的后果,许多实现者认为排除脏读、不可重复读和幻像读就足以正确实现

SERIALIZABLE
隔离级别,尽管注释澄清这不是定义 ,而是定义的结果

所以我认为 MySQL 是错误的,但它并不孤单:Oracle 数据库以同样的方式解释

SERIALIZABLE


2
投票

我无法在 MySQL 5.7 中重现这一点。其他交易总是出错:

错误1213(40001):尝试获取锁时发现死锁;

原因是 SELECT 不使用 WHERE 部分中的索引列,因此它将 s-locks 设置为它找到的每一行,gap-s-lock 设置为找到的行之间的每个间隙,并将 next-key 锁定为正无穷大找到的最后一行。因此在这种情况下并发插入是不可能的。

您得到的结果的一个可能原因可能是这样的:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

它仅为下一个事务设置隔离级别。如果此后执行了单个 SELECT,隔离级别就会变回正常(可重复读取)。

更好用

SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
© www.soinside.com 2019 - 2024. All rights reserved.