【问题标题】:What type of Transaction IsolationLevel should be used to ignore inserts but lock the selected row?应该使用什么类型的 Transaction IsolationLevel 来忽略插入但锁定选定的行?
【发布时间】:2010-08-04 11:00:17
【问题描述】:

我有一个进程启动事务,将记录插入 Table1,然后调用长时间运行的 Web 服务(最多 30 秒)。如果 Web 服务调用失败,则插入回滚(这是我们想要的)。这是插入的示例(实际上是对多个表的多次插入,但我正在简化这个问题):

INSERT INTO Table1 (UserId, StatusTypeId) VALUES (@UserId, 1)

我有第二个进程从第一步查询 Table1,如下所示:

SELECT TOP 1 * FROM Table1 WHERE StatusTypeId=2

然后为用户更新该行。当进程 1 运行时,Table1 被锁定,因此进程 2 将在进程 1 完成之前完成,这是一个问题,因为在进程 1 完成其 Web 服务调用时引入了长时间的延迟。

进程 1 只会插入 1 的 StatusTypeId,它也是唯一插入 Table1 的操作。进程 2 只会查询 StatusTypeId = 2。我想告诉进程 2 忽略对 Table1 的任何插入,但锁定它选择的行。 Process 2 的默认隔离级别等待太多,但我担心 IsolationLevel.ReadUncommitted 允许读取太多脏数据。我不希望两个用户运行 Process 2 然后意外地获得同一行。

除了 ReadUncommitted 之外,还有其他 IsolationLevel 可以使用,它表示忽略插入的行,但确保选择锁定选定的行?

【问题讨论】:

  • 这些锁不应该冲突。你有StatusTypeId 的索引吗? SELECT TOP 1 * FROM Table1 WHERE StatusTypeId=2ORDER BY 是什么
  • 您能否澄清“我不希望两个用户运行 Process2 然后意外获得同一行”的意思?因为这种情况在任何隔离级别都是完全可行的。
  • StatusTypeId 上没有索引,但 StatusTypeId 上有一个外键到 StatusType 表。不确定 StatusTypeId 上的外键是否也在该列上创建索引。

标签: sql sql-server transactions isolation-level


【解决方案1】:

关于 SELECT 被插入阻塞,这应该可以通过提供适当的索引来避免。

测试表。

CREATE TABLE Table1
(
UserId INT PRIMARY KEY,
StatusTypeId INT,
AnotherColumn varchar(50)
)
insert into Table1
SELECT number, (LEN(type)%2)+1, newid()
FROM master.dbo.spt_values
where type='p'

查询窗口一

BEGIN TRAN
INSERT INTO Table1 (UserId, StatusTypeId) VALUES (5000, 1)
WAITFOR DELAY '00:01';
ROLLBACK

查询窗口二(块)

SELECT TOP 1 * 
FROM Table1 
WHERE StatusTypeId=2 
ORDER BY AnotherColumn

但是如果你在添加索引后重试测试它不会阻塞CREATE NONCLUSTERED INDEX ix ON Table1 (StatusTypeId,AnotherColumn)

关于Process 2 的行锁定,您可以使用以下方法(READPAST 提示将允许 2 个并发 Process 2 事务开始处理不同的行,而不是一个阻塞另一个)。您可能会发现 Remus Rusanu 的 this article 相关

BEGIN TRAN

SELECT TOP 1 * 
FROM Table1  WITH (UPDLOCK, READPAST)
WHERE StatusTypeId=2
ORDER BY AnotherColumn

/*
Rest of Process Two's code here
*/
COMMIT

【讨论】:

  • 就是这样!在 StatusTypeId 列上添加索引允许进程二的查询立即完成。但为什么?为什么添加索引允许进程 2 查询完成?
  • @Jeff - 没有它,它将最终(可能)进行整个聚集索引扫描,并在此过程中尝试在刚刚插入的行上获取共享锁。 READPAST 提示可能会避免该问题,但听起来该查询无论如何都可以使用该索引。
【解决方案2】:

编辑:重新阅读问题后,任何insert 上的锁定都不应该影响READ COMMITTED 下的任何select 这可能是您的索引的问题。

但是,从您的 cmets 和其他问题来看,您似乎只希望一个事务能够一次读取一行,这不是隔离级别所阻止的。

他们阻止了

  • Dirty Read - 在可以回滚的事务中读取未提交的数据 - 在 READ UNCOMMITTED 中发生,在 READ COMMITTEDREPEATABLE READSERIALIZABLE 中阻止

  • Non Repeatable Reads - 在未提交的事务中读取行时更新,这意味着对特定行的相同读取可以在事务中发生两次并产生不同的结果 - 发生在 READ UNCOMMITTED、@987654331 @。在REPEATABLE READSERIALIZABLE中阻止了

  • phantom rows - 在未提交事务中读取时插入或删除行,这意味着对多行的相同读取可以在事务中发生两次并产生不同的结果,添加或丢失行 - 发生在 READ UNCOMMITTEDREAD COMMITTEDREPEATABLE READ,在SERIALIZABLE 中阻止

【讨论】:

  • 我正在寻找不可重复的读数。我希望这是在一个事务中: SET @OrderId = (SELECT TOP 1 OrderId FROM Table1 WHERE StatusTypeId=2); UPDATE Table1 SET UserId=@UserId WHERE OrderId=@OrderId; -- 并且不会被进程 1 阻塞。
  • @brian,这是我写它的方式令人困惑。我希望修改后的内容更易于阅读。
  • 谢谢克里斯!这是很好的信息,但 Martin Smith 的答案是我需要在 StatusTypeId 上添加一个索引。你知道为什么添加索引可以解决问题吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-11
  • 2011-05-05
  • 2022-01-18
  • 1970-01-01
  • 1970-01-01
  • 2010-10-02
相关资源
最近更新 更多