隔离级别且不跳过任何数据

时间:2018-08-14 12:36:19

标签: mysql innodb isolation-level

假设我们要从MySQL(InnoDB)表event中读取新事件。我们记得我们看到的最后一个(自动递增)ID,并查询WHERE id > @LastSeenId

如果ID 1到10可用但ID 4尚未提交,该怎么办?

很显然,重要的是我们不要跳过任何可能存在的行。如果我们要跳过ID 4,我们将永远不会再看到它,并且会永久丢失它,这是必须避免的。

我认为这取决于查询运行的隔离级别。

1。我是否正确理解Serializable(并且没有其他级别)会提供所需的行为?

也就是说,它将等待任何未提交的事务的提交/回退,这些事务会影响符合条件(id > @LastSeenId)的行?

2。更具体地说,如果我们没有显式使用数据库事务,结果是否由默认隔离级别决定-即使对于单个SELECT查询也是如此?

对于上下文,我们使用的是.NET的官方MySQL连接器。

1 个答案:

答案 0 :(得分:2)

这是您可以使用mysql客户端和两个窗口测试自己的东西。

打开窗口1,进入mysql客户端,创建一个表,并用将要提交的值填充它。

mysql1> use test;

mysql1> create table event (id serial primary key);

mysql1> insert into event values (1), (2), (3), (5);

现在开始交易。如示例所示,插入值4。

mysql1> begin;

mysql1> insert into event values (4);

请不要提交最后一次插入。

打开窗口2,进入mysql客户端,为会话设置事务隔离,并查询数据范围。

mysql2> use test;

mysql2> set tx_isolation = serializable;

mysql2> begin;

mysql2> select * from event where id >= 1;

选择项在此阶段挂起,等待。

这是因为在可序列化级别中,所有选择查询都隐式地尝试获取共享锁,就像您已将LOCK IN SHARE MODE(或MySQL 8.0语法中的FOR SHARE)子句添加到选择查询。这是locking read。它正在尝试获取ID值范围内的gap lock,但是它还无法获得该锁,因为窗口1创建了一个未提交的行,该行属于该范围。

现在进入窗口1,提交事务(确保在窗口2超时50秒后在窗口2之前执行此操作):

mysql1> commit;

窗口2立即返回,现在它可以看到全部数据值。

mysql2> select * from event where id >= 1;
+----+
| id |
+----+
|  1 |
|  2 |
|  3 |
|  4 |
|  5 |
+----+

现在尝试在窗口1中插入新行:

mysql1> begin;

mysql1> insert into event values (6);

现在窗口挂起。为什么?因为它试图锁定要插入的行,但是窗口2仍然在行where id > 1 range 上保留了一个间隙锁,其中包括新值6。

这是MySQL确保可重复读取的方式。它使用间隙锁来防止插入将进入其锁定范围的新行,因为新行会影响当前事务试图保留的行集。

最终,如果窗口2未完成其事务并释放其间隙锁定,则窗口1将超时:

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

所有这些都与自动增量无关。如您在示例插入中所看到的,无论如何,我都覆盖了自动增量。不管这些行中的值是如何生成的,它仅与行和间隙上的锁有关。

我还必须指出,您要解决的问题本质上是发布/订阅模型,它更适合message queue technology,而不是RDBMS技术。您应该考虑使用消息队列作为RDBMS的补充技术。