【问题标题】:Do serializable transaction isolation level guarantee that non-DB code is also serialised?可序列化事务隔离级别是否保证非数据库代码也被序列化?
【发布时间】:2021-07-25 02:25:47
【问题描述】:

我需要运行一些数据库操作并为 Kafka 主题生成事件。但是,事件内容取决于从数据库中读取的内容和事件的顺序。考虑这样的操作:

  1. 开启交易
  2. 从表中读取
  3. 更新表
  4. 再次从表中读取
  5. 向主题发送事件
  6. 提交交易

现在我知道可序列化的隔离级别保证数据库中的数据是一致的。同时运行此操作的两个服务实例总是会读取有效数据,就好像事务是按顺序运行的一样。但是向 Kafka 主题发送事件也是如此吗?

这种情况是否可能:

  1. 实例 A 运行、读取数据并准备要发出的事件
  2. 实例 B 运行,读取数据,就像它在 A 之后运行一样,并准备要发出的事件
  3. 实例 B 发出事件
  4. 实例 A 发出事件

在上述场景中,事件的顺序是错误的。如果实例 A 在实例 B 引入更改之前根据数据准备了事件,则应首先发出其事件。

我认为这个问题归结为事务 B 何时会失败的问题?它是否在第 4 点失败 - 因为数据已被另一个事务更改?还是在提交时失败 - 在 Kafka 事件已经产生之后?

或者有没有可能根本没有失败?就像在这种情况下:

  1. A 读取
  2. 更新
  3. A 再次读取
  4. B 读取(A 已更新数据)
  5. B 更新
  6. B 再次读取 -- 没有冲突,因为 B 在 A 之后运行所有 DB 操作
  7. B 发出事件
  8. A 发出事件

没有抛出错误,但事件顺序错误

最好的解决方案应该是 DB-angnostic,但如果重要的话,我会使用 PostgreSQL 10.16

【问题讨论】:

    标签: sql database postgresql transactions isolation-level


    【解决方案1】:

    使用SERIALIZABLE 时,第 2 步“实例 B 发出事件...”将失败。

    让我们试一试。让我们创建一个表:

    create table t (id int, amount int);
    
    insert into t (id, amount) values (1, 500), (2, 1000);
    

    现在,让我们运行实例 #1:

    start transaction isolation level serializable not deferrable;
    
    select amount from t where id = 1; -- shows $500
    
    update t set amount = amount - 100 where id = 1;
    
    select amount from t where id = 1; -- shows $400
    
    -- wait here while instance #2 works...
    

    现在,让我们运行实例 #2:

        start transaction isolation level serializable not deferrable;
    
        select amount from t where id = 1; -- shows $500!
    
        update t set amount = amount - 150 where id = 1;
        -- the thread blocks...
    

    现在,回到实例 #1:

    commit; -- all good
    

    然后,回到实例 #2,它停止等待:

        Error: ERROR: could not serialize access due to concurrent update
        SQLState:  40001
        ErrorCode: 0
    

    如您所见,当您使用SERIALIZABLE 时,线程会尝试并行运行,只要它们不互相踩到脚趾。他们“一个获胜”并继续处理的那一刻,而另一个等待;希望另一个不会对数据状态造成太大损害。在这种情况下,实例 #1 实际上确实对实例 #2 造成了一些损害(它更改了数据),因此后者失败了。

    【讨论】:

    • 你确定这也适用于逻辑解码吗?
    • 谢谢@The_Impaler!在这个片段中:update t set amount = amount - 150 where id = 1; -- the thread blocks... - 如果线程没有阻塞怎么办?根据您的回答,我假设实例 #2 仅在 #1 提交时才会失败?如果没有,并且两个事务都提交(发送事件)怎么办?然后只有其中一个确实提交了?
    • 另一个事务回滚并抛出错误,但事件可能已经发出?
    • @amorfis 如果您使用 JMS(例如 ActiveMQ),则消息的发送和数据库事务将是一个原子单元,您在这里的整个问题都不是问题。但是,您使用的是不支持事务的 Kafka。新的“无事务”解决方案——顺便说一句,需要大量的编码/支持工作——是使用具有幂等消息的“事务发件箱”;例如,见microservices.io/patterns/data/transactional-outbox.html
    • @TheImpaler 我找到了使用数据库锁更好的解决方案 :) 感谢所有帮助!
    【解决方案2】:

    我想既然你提到了 Kafka,你的意思是当你说一个实例“发射”一些东西时,逻辑解码会产生数据。

    我认为SERIALIZABLE 隔离级别不能保证 WAL 的解码顺序。它只涉及使用 SQL 看到的对数据的影响。

    【讨论】:

    • 但这已经足够了。如果 SQL 很好,那么事件顺序也很好。这一切都在问题中描述。如果交易没有“踩到对方的脚趾” - 顺序无关紧要。如果是这样,则不会发出事件,因为事务将在发出之前抛出错误。
    • 如果你确信,那很好。我不相信。
    • 重要的是何时抛出错误。如果是在向 Kafka 生成事件之前 - 它不会被生成。如果它只是在提交时 - 事件已经产生并且顺序被破坏了。
    猜你喜欢
    • 1970-01-01
    • 2021-01-14
    • 2015-03-24
    • 1970-01-01
    • 2011-01-31
    • 1970-01-01
    • 1970-01-01
    • 2021-12-06
    • 2020-07-16
    相关资源
    最近更新 更多