Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/sql/71.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
SQL隔离级别可序列化的锁定和引用计数_Sql_Hana_Isolation Level_Transaction Isolation - Fatal编程技术网

SQL隔离级别可序列化的锁定和引用计数

SQL隔离级别可序列化的锁定和引用计数,sql,hana,isolation-level,transaction-isolation,Sql,Hana,Isolation Level,Transaction Isolation,我有Java/JDBC应用程序维护两个SQL数据库表: MESSAGES (primary key MSG_ID) RECIPIENTS (primary key USER_ID, foreign key MSG_ID) 收件人中的记录指向MESSAGES.MSG\u ID。当收件人拒绝邮件时,应删除收件人中的(USER\u ID,MSG\u ID)记录,如果他是此MSG\u ID的最后一个剩余收件人,则还应从邮件中删除由RECIPIENTS.MSG\u ID指示的邮件记录

我有Java/JDBC应用程序维护两个SQL数据库表:

    MESSAGES (primary key MSG_ID) 
    RECIPIENTS (primary key USER_ID, foreign key MSG_ID)
收件人中的记录指向MESSAGES.MSG\u ID。当收件人拒绝邮件时,应删除收件人中的(USER\u ID,MSG\u ID)记录,如果他是此MSG\u ID的最后一个剩余收件人,则还应从邮件中删除由RECIPIENTS.MSG\u 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
出于应用程序的核心逻辑原因,数据库连接池设置为事务_可序列化模式

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

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

参考他们的快照,A和B都会看到他们自己从收件人删除的效果,但不会看到并发事务的效果。对于A和B,SELECT COUNT(*)将因此返回1,并且A和B都不会尝试执行从消息中删除

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

为了解决这个问题,我更愿意(如果可能的话)避免创建具有较低事务隔离级别的单独连接池

当事务隔离级别可序列化时,“同时”不会发生任何事情

我不太确定。它通常的工作方式是通过数据库引擎检测两次提交之间的写冲突,例如通过比较事务的before映像和DB存储之间的版本号,如果版本不匹配,则提交失败,让应用程序从一开始就重新尝试整个事务

然而,关键是在这种情况下没有写冲突。RECIPIENTS.A和RECIPIENTS.B的记录是不同的记录,并且(或者可以)独立地加盖版本戳(例如,如果加盖是基于行的;或者如果加盖是基于页面的,但记录属于不同的数据库页面)


应用程序根据只读选择数据访问做出决定(是否删除邮件),不会导致写入冲突,此决定在DB引擎外部做出,后者不知道。

此处要做的是基于一组元组(收件人)建立一致性规则。但是您的应用程序只锁定一个特定的元组

在这种情况下,任何隔离级别都不会为您提供正确的协议

或者,您可以在
RECIPIENTS
中锁定所有匹配
MSG_ID
的元组,运行
DELETE
命令,检查剩余的匹配元组数量,并在剩余总计数为零的情况下运行第二个
DELETE

协议的工作原理如下
Tx A:

1) 锁定属于MSG_ID的所有当前记录
2) 删除有问题的记录
3) 对剩余记录进行计数
4) 如果计数=0,则删除消息记录
5) 提交/回滚

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

 - 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
2) 删除有问题的记录
3) 对剩余记录进行计数
4) 如果计数=0,则删除消息记录
5) 提交/回滚

此架构涵盖收件人表的所有
DELETE
/
UPDATE
事务。
唯一可能的问题是,通过仅锁定与感兴趣的邮件ID相关的记录,我们不包括在执行
count(*)
之后插入新收件人的情况


为了避免这种情况,必须锁定整个表以避免插入
。但是,这也会将等待置于不在指定消息ID上工作的线程上。

当事务隔离级别可序列化时,“同时”不会发生任何事情。它们一个接一个地发生。只有一个进程将在
消息上获得更新锁,另一个进程将阻塞,直到另一个进程完成其事务。您在概述的过程中遇到过问题吗?您的数据库是什么?Oracle、PostgreSQL、MySql、SQLServer?每个数据库实现隔离级别略有不同,行为上存在细微差异,因为隔离级别的SQL标准非常通用,如果需要帮助,不会指定详细信息,了解您正在使用的数据库是此处的关键。请编辑问题的标记并向您的数据库添加标记点。当前目标数据库是SAP HANA。但是,我宁愿避免使用脆弱的引擎特定解决方案。这似乎是一个通用且基本的问题,因此如果没有通用的解决方案,我会有点惊讶。在提交之前不更改行的锁定(选择更新)是否会在MVCC快照中的锁定行上升级版本?如果没有,那么您建议的方案将不起作用,因为两个事务之间的冲突不会被记录/检测。由于显式锁定,因此不会有任何冲突版本。我扩展了解释,使之更清楚。任何在Tx A之后启动的事务只有在Tx A完成后才会看到有效状态。