【发布时间】:2018-08-31 01:20:55
【问题描述】:
当使用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 观察到一个空结果集是否算作“效果”?
另外几个 cmets 来帮助解决这个问题:
- 我知道我可以通过首先使用
ON CONFLICT IGNORE进行插入,然后进行选择来在 MySQL 中实现所需的行为。我试图理解为什么等效的标准 SQL 在两个引擎中没有表现出相同的行为。 - 我知道我也可以通过在
name字段上设置唯一约束来修复它,无论如何这可以说是一个更好的数据模型。但核心问题仍然存在:为什么这些交易都成功了?
【问题讨论】:
-
你是如何进行测试的?您也不应该能够为 MySQL 获得 2 个 bob rosses。一个事务将在
insert-step 处等待(如果另一个事务的select在此之前发生),另一个事务将在尝试插入时因死锁而回滚(这对于“可序列化”来说很好) )。 MySQL 实际上与其他数据库的不同之处在于它不会在(第二个)select等待(这将产生更少的死锁但更多的等待 - 但这实际上并不违反“可序列化”)。旁注:表名和列名与查询不匹配。 -
我打开了两个连接,并运行了相关查询。我将两个连接都带到了指定点(在插入之前),然后插入 1,插入 2,提交 1,提交 2。MySQL 提交了两个事务,随后的查询找到了 2 个 Bob Ross 条目。
-
我会说 查询 不正确。仅当行不存在时才插入行的典型方法是
INSERT .. SELECT 'a','b' WHERE NOT EXISTS或类似的变体。使用 transaction 来实现相同的效果并不能很好地扩展 -
@MichaelEkstrand 仔细检查一下,您的 MySQL 表是使用 InnoDB 还是 MyISAM ?(MyISAM 表不是事务性的)
标签: mysql sql postgresql standards transaction-isolation