使用SQL ISOLATION LEVEL SERIALIZABLE锁定和引用计数

时间:2018-02-17 04:15:29

标签: sql hana isolation-level transaction-isolation

我有Java / JDBC应用程序维护,其中包括两个SQL数据库表:

    MESSAGES (primary key MSG_ID) 
    RECIPIENTS (primary key USER_ID, foreign key MSG_ID)

RECIPIENTS中的记录指向MESSAGES.MSG_ID。当收件人解除该邮件时,应删除其收件人中的(USER_ID,MSG_ID)记录,如果他是该MSG_ID的最后剩余收件人,则也应从MESSAGES中删除RECIPIENTS.MSG_ID指示的邮件记录。

以伪代码写下的简化逻辑基本上就是这样的:

    GET DB CONNECTION
    BEGIN TRANSACTION

    // interlock reference counting for this MSG_ID for SELECT COUNT(*) below
    SELECT * FROM MESSAGES WHERE MSG_ID='...' FOR UPDATE

    DELETE FROM RECIPIENTS WHERE USER_ID='...' AND MSG_ID='...'

    IF (SELECT COUNT(*) FROM RECIPIENTS WHERE MSG_ID='...') == 0
    THEN DELETE FROM MESSAGES WHERE MSG_ID='...'

    COMMIT

出于应用程序的核心逻辑原因,数据库连接池在TRANSACTION_SERIALIZABLE模式下设置。

问题是当两个用户试图同时解除消息时如何避免竞争条件。

用户A和B可能启动并发事务,这意味着(取决于确切的数据库引擎实现)两者都可以在事务开始时获得类似MVCC的数据库内容快照。如果是这样,那么A将在DELETE FROM RECIPIENTS之后相信它不是最后剩下的收件人(看到B仍然是A快照中的剩余收件人); B同样会认为它不是最后一个收件人(看到A仍然是B快照中的剩余收件人)。

参考他们的快照,A和B都会看到他们自己的DELETE FROM RECIPIENTS的影响,但不会看到并发事务的影响。对于A和B,SELECT COUNT(*)将返回1,A和B都不会尝试执行DELETE FROM MESSAGES。

这个问题的解决方案是否通用,即独立于特定的数据库引擎,而不依赖于数据库外部的锁定?

我更愿意(如果可能的话)避免必须创建一个事务隔离级别较低的单独连接池,以解决此问题。

2 个答案:

答案 0 :(得分:0)

  

没有任何事情发生在同一时间"当事务隔离级别是可序列化的

我不会那么肯定。它通常的工作方式是通过数据库引擎检测两个COMMITS之间的写冲突,例如通过比较事务的前映像和数据库存储之间的版本号,如果版本不匹配则失败COMMIT,使其保持不变应用程序从一开始就重新尝试整个事务。

然而,重点是在这种情况下没有写入冲突。 RECIPIENTS.A和RECIPIENTS.B的记录是不同的记录,并且(或可以)是独立的版本标记(例如,如果标记是基于行的;或者如果它是基于页面的,但记录属于不同的DB页面)。 / p>

应用程序根据只读SELECT数据访问做出决定(是否删除MESSAGE),不会发生写入冲突,这个决定是在数据库引擎之外进行的,后者不知道。

答案 1 :(得分:0)

您要在此处执行的操作是基于一组元组(RECIPIENTS)建立一致性规则。但是您的应用程序只锁定一个特定的元组。

在这种情况下,没有隔离级别会为您提供正确的协议。

或者,您可以在MSG_ID中锁定与RECIPIENTS匹配的所有元组,运行DELETE命令,检查剩余的匹配元组数量并运行第二个{如果总剩余计数为零,则为{1}}。

协议会像这样工作 Tx A:

1)锁定属于MSG_ID的所有当前记录   2)删除有问题的记录
  3)计算剩余记录
 4)如果count = 0则删除消息记录
 5)COMMIT / ROLLBACK

Tx B (在Tx A开始后的任何时间运行):
 1)锁定属于MSG_ID的所有当前记录

DELETE

2)删除有问题的记录
 3)计算剩余记录
 4)如果count = 0则删除消息记录
 5)COMMIT / ROLLBACK

此架构涵盖RECIPIENTS表的所有 - wait until Tx A released lock via COMMIT/ROLLBACK - once Tx B gets the lock, Tx A has finished all processing - Tx B does not see any record from before Tx A's end / DELETE个交易 唯一可能的问题是,通过仅锁定与感兴趣的消息ID相关的记录,我们不会涵盖在UPDATE执行后可以插入新收件人的情况。

为了避免这种情况,必须锁定整个表以避免count(*)。但是,这会使等待也无法处理对指定消息ID无效的线程。