MySQL/InnoDB是否实现了真正的可序列化隔离?

Rom*_*kov 18 rdbms transactions acid transaction-isolation

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

如果MySQL InnoDB没有,那么有没有完全免费的,生产质量的RDBMS呢?

1其中"真正的可序列化隔离"意味着不仅缺少根据SQL标准的读取异常,而且还存在写入偏斜异常,这里将进一步详细解释.

reg*_*ero 12

更新:

看到评论,这似乎是在MySQL 5.5中修复的,用这些例子我们仍然有一个表锁并且索引的下一键锁不能被愚弄,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);
Run Code Online (Sandbox Code Playgroud)

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

T1:

SET TRANSACTIOn ISOLATION LEVEL SERIALIZABLE;
BEGIN;
SELECT SUM(value) FROM t1 WHERE class = 1;
Run Code Online (Sandbox Code Playgroud)

结果是30.

T2:

 SET TRANSACTIOn ISOLATION LEVEL SERIALIZABLE;
 BEGIN;
 SELECT SUM(value) FROM t1 WHERE class = 2;
Run Code Online (Sandbox Code Playgroud)

结果是300.

现在出现了可串行化问题.如果T1插入一行,则表示T2中的选择无效(此处T2执行相同操作).

T1:

INSERT INTO t1 (`class`,`value`) VALUES (2,30);
Run Code Online (Sandbox Code Playgroud)

==>等待(锁定到位)

T2:

INSERT INTO t1 (`class`,`value`) VALUES (1,300);
Run Code Online (Sandbox Code Playgroud)

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

T1现在插入成功,t2有一个ROLLBACK,良好的可串行性.

这将在PostgreSQL 9.0上失败(事情正在改变9.1,但这是另一个问题).实际上,只有一个事务可以在表上执行插入.即使我们尝试插入class=3.

INSERT INTO t1 (`class`,`value`) VALUES (3,30);
Run Code Online (Sandbox Code Playgroud)

我们会看到一个等待锁,以及遇到问题时的死锁.看起来我们在MySQL中有一个谓词锁定......但实际上它是InnoDB中的下一键锁定实现.

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

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

CREATE index t1class ON t1 (class);
Run Code Online (Sandbox Code Playgroud)

现在重做测试.成功,可序列化仍然得到执行.好消息.

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

T1:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN;
SELECT SUM(value) FROM t1 WHERE class = 1;
Run Code Online (Sandbox Code Playgroud)

结果是30.

T2:

 SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
 BEGIN;
 SELECT SUM(value) FROM t1 WHERE class = 2;
Run Code Online (Sandbox Code Playgroud)

结果是300.

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

T1:

INSERT INTO t1 (`class`,`value`) VALUES (3,30);
Run Code Online (Sandbox Code Playgroud)

两者都可以执行插入(这里我只做了一个),这是正常的.未应用预测锁定,未进行SELECT查询class=3.如果我们给它提供良好的索引(在插入时没有表锁定),看起来下一键锁定会更好.

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

T1:

INSERT INTO t1 (`class`,`value`) VALUES (2,30);
Run Code Online (Sandbox Code Playgroud)

哎哟.它成功了!

T2:

INSERT INTO t1 (`class`,`value`) VALUES (1,300);
Run Code Online (Sandbox Code Playgroud)

==>等待.那里还有锁.希望.

T1:

COMMIT;
Run Code Online (Sandbox Code Playgroud)

T2 :(锁已经去了,插入了)

SELECT SUM(value) FROM t1 WHERE class = 2;
COMMIT;
Run Code Online (Sandbox Code Playgroud)

这里还有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!
+-------+-------+
Run Code Online (Sandbox Code Playgroud)

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

  • 我不明白.在MySQL 5.5中它仍然以这种方式工作吗? (2认同)

Rom*_*kov 9

有没有完全免费的,生产质量的RDBMS呢?

从版本9.1开始, Postgres支持真正的可序列化隔离.它当然有资格作为"完全免费"和"生产质量".