代码之家  ›  专栏  ›  技术社区  ›  Timo

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

  •  2
  • Timo  · 技术社区  · 8 年前

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

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

    显然,重要的是我们不能跳过任何可能存在的行。如果我们跳过ID4,我们就再也看不到它了,而且会永久地错过它,这是必须防止的。

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

    一。我明白吗 Serializable (没有其他级别)将提供所需的行为?

    也就是说,它将等待任何影响与条件匹配的行的未提交事务的提交/回滚( id > @LastSeenId )?

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

    对于上下文,我们使用官方的MySQL connector For.NET。

    1 回复  |  直到 8 年前
        1
  •  2
  •   Bill Karwin    8 年前

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

    打开window 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);
    

    不要提交最后一次插入。

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

    mysql2> use test;
    
    mysql2> set tx_isolation = serializable;
    
    mysql2> begin;
    
    mysql2> select * from event where id >= 1;
    

    这是因为在可序列化级别,所有select查询都隐式地尝试获取共享锁,就像您添加了 LOCK IN SHARE MODE (或 FOR SHARE 在MySQL8.0语法)子句中选择查询的末尾。这是一个 locking read . 它试图获得一个 gap lock 在id值的范围内,但它还不能获得该锁,因为window 1创建了一个未提交的行,该行在该范围内。

    现在在窗口1中,提交事务(确保在窗口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);
    

    范围 where id > 1 其中包括新值6。

    最后,窗口1将超时,如果窗口2没有完成其事务并释放其gap锁:

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

    所有这些都与自动增量无关。正如您在我的示例inserts中看到的,我无论如何都要重写自动增量。不管这些行中的值是如何生成的,它只与行和间隙上的锁有关。

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

    推荐文章