20

MySQL 文档中并不完全清楚InnoDB 引擎是实现真正的可序列化隔离1还是快照隔离,这通常也被混淆地称为“可序列化”。哪一个?

如果 MySQL InnoDB 没有,是否有任何完全免费的、生产质量的 RDBMS 可以做到?

1其中“真正的可序列化隔离”意味着不仅没有按照 SQL 标准的读取异常,而且还没有写入倾斜异常,这里有更详细的解释。

4

5 回答 5

13

更新:

见评论,这似乎在 MySQL 5.5 中得到修复,通过这些示例,我们仍然有一个表锁,并且索引 next-key 锁不能被愚弄,AFAIK。

原来的:

昨天发现了你的问题,我也想知道 InnoDb 的 MVCC 可串行性模型。

所以我做了一些测试。MySQL 5.1.37。对可序列化问题的一个很好的测试是在postgrESQL 9.0 MVCC 文档中提供的测试,在这一章中可序列化隔离与真正的可序列化我们可以看到 MVCC 模型在不执行谓词锁定的情况下对可序列化的限制。

所以让我们在 MySQL 上测试一下:

CREATE TABLE t1 (
 class integer,
 value integer
) ENGINE=InnoDB;

INSERT INTO t1 (`class`,`value`) VALUES
  (1,10),
  (1,20),
  (2,100),
  (2,200);

现在我们将打开两个不同的连接以进行两个并行事务(T1 和 T2):

T1:

SET TRANSACTIOn ISOLATION LEVEL SERIALIZABLE;
BEGIN;
SELECT SUM(value) FROM t1 WHERE class = 1;

结果是 30。

T2:

 SET TRANSACTIOn ISOLATION LEVEL SERIALIZABLE;
 BEGIN;
 SELECT SUM(value) FROM t1 WHERE class = 2;

结果是 300。

现在是可序列化问题。如果 T1 插入一行,则导致来自 T2 的选择无效(此处 T2 也是如此)。

T1:

INSERT INTO t1 (`class`,`value`) VALUES (2,30);

==> 等待(有锁)

T2:

INSERT INTO t1 (`class`,`value`) VALUES (1,300);

==> ERROR 1213 (40001): 尝试获取锁时发现死锁;尝试重启事务

T1 现在插入成功, t2 有一个 ROLLBACK, good serializability

这在 PostgreSQL 9.0 上会失败(在 9.1 上情况正在发生变化,但这是另一个问题)。事实上,只有一个事务可以对表执行插入操作。即使我们尝试插入class=3与。

INSERT INTO t1 (`class`,`value`) VALUES (3,30);

我们会看到一个等待锁,并且在出现问题时会出现死锁。看起来我们在 MySQL 中有一个谓词锁定......但实际上它是 InnoDB 中的一个next-key 锁定实现。

Innodb 执行行锁,并在索引上锁定一些间隙。这里我们的表没有索引,看起来 MySQL 决定锁定表。

因此,让我们尝试测试下一个键锁定,看看这是否强制可串行化。首先回滚正在运行的事务 (T1)。然后创建索引。

CREATE index t1class ON t1 (class);

现在重做测试。成功,可序列化仍然强制执行。好消息。

但是有了索引,我认为下一个键锁定和行锁定是在索引上进行的。这意味着我们应该能够在不影响并行事务的情况下执行插入……这就是大问题

T1:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN;
SELECT SUM(value) FROM t1 WHERE class = 1;

结果是 30。

T2:

 SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
 BEGIN;
 SELECT SUM(value) FROM t1 WHERE class = 2;

结果是 300。

在这里,我们将在 T1 上进行不相关的插入,现在我们有一个索引,这将成功:

T1:

INSERT INTO t1 (`class`,`value`) VALUES (3,30);

两者都可以执行插入(这里我只做了一个),这很正常。未应用预测锁定,未对class=3. 如果我们给它好的索引(插入时没有表锁),看起来 next-key 锁定性能更好。

现在我们尝试在下一个键锁上插入,在 T2 (class=2) 的行匹配选择上:

T1:

INSERT INTO t1 (`class`,`value`) VALUES (2,30);

哎哟。它成功了

T2:

INSERT INTO t1 (`class`,`value`) VALUES (1,300);

==> 等待。那里还有一把锁。希望。

T1:

COMMIT;

T2:(锁消失的地方,插入)

SELECT SUM(value) FROM t1 WHERE class = 2;
COMMIT;

这里还有300。似乎可序列化已经消失了。

select * from t1;
+-------+-------+
| class | value |
+-------+-------+
|     1 |    10 | 
|     1 |    20 | 
|     2 |   100 | 
|     2 |   200 | 
|     3 |    30 | <-- test
|     2 |    30 | <-- from trans1
|     1 |   300 | <-- from trans2 ERROR!
+-------+-------+

结果:通过在插入影响并行事务查询的行之前插入新的不相关行,我们欺骗了下一个键锁定机制。或者至少这是我从测试中了解到的。所以我想说,不要相信真正的可序列化引擎。当您在事务中有聚合函数时,最好的办法是手动锁定表,将您的可序列化问题转换为真正的只有一个人的情况,这并不奇怪!示例中的其他可序列化问题是约束验证(检查操作后数量是否仍然为正),您是否也拥有这些情况的锁。

于 2011-11-13T16:17:50.263 回答
10

are there any completely free, production-quality RDBMS which do?

Postgres has support for true serializable isolation starting with version 9.1. It certainly qualifies both as "completely free" and "production-quality".

于 2011-12-12T18:01:23.620 回答
1

您确定您使用的是“可序列化”事务。可以肯定的是,你必须使用“SET session TRANSACTION ISOLATION LEVEL SERIALIZABLE;” 这样整个会话变得可序列化,而不仅仅是下一个事务。

我在 OSX 上使用 5.5.29 进行测试

当我尝试在 T1 中插入 (3,30) 时,在类上创建索引后,事务等待并在锁定等待超时后中止。(T2仍在进行中)

于 2013-04-21T17:02:24.340 回答
-2

在您提供的链接上阅读更多内容,它说使用“​​可重复读取”模式(innodb 的默认值)可以消除读取异常,正如您提到的要求之一。此外,阅读您的第二个链接,处理写入异常似乎已转移到最终用户。在文章中,他们提到了 Oracle 的 Select for Update,MySQL 也支持. 我不确定这是否满足您的要求,但它应该对您有所帮助。

于 2011-06-08T01:22:31.733 回答
-5

我不相信 MySQL 实现了可序列化的隔离,据我了解,这需要回滚的能力,而它绝对不支持。欲了解更多信息,请阅读此处

于 2011-06-07T18:22:46.567 回答