只更新单个 table 的事务是否总是隔离的?
Is a transaction that only updates a single table always isolated?
根据 UPDATE
documentation,一个 UPDATE
总是在整个 table 上获取排他锁。但是,我想知道排他锁是在确定要更新的行之前获取的,还是仅在实际更新之前获取的。
我的具体问题是我的 UPDATE
中有一个嵌套的 SELECT
,如下所示:
UPDATE Tasks
SET Status = 'Active'
WHERE Id = (SELECT TOP 1 Id
FROM Tasks
WHERE Type = 1
AND (SELECT COUNT(*)
FROM Tasks
WHERE Status = 'Active') = 0
ORDER BY Id)
现在我在想是否真的能保证恰好有一个
任务与 Status = 'Active'
之后如果可以并行执行相同的语句类型:
UPDATE Tasks
SET Status = 'Active'
WHERE Id = (SELECT TOP 1 Id
FROM Tasks
WHERE Type = 2 -- <== The only difference
AND (SELECT COUNT(*)
FROM Tasks
WHERE Status = 'Active') = 0
ORDER BY Id)
如果对于这两个语句,要更改的行将在获取锁之前确定,我可能会得到两个必须阻止的活动任务。
如果是这种情况,我该如何预防呢?我可以在不将事务级别设置为 SERIALIZABLE
或弄乱锁定提示的情况下阻止它吗?
从 Is a single SQL Server statement atomic and consistent? 的回答中我得知问题出现在嵌套的 SELECT
访问另一个 table 时。但是,如果只关注更新的 table,我不确定是否需要关心这个问题。
这不是您问题的答案...但是您的问题让我很痛苦:)
;WITH cte AS
(
SELECT *, RowNum = ROW_NUMBER() OVER (PARTITION BY [type] ORDER BY id)
FROM Tasks
)
UPDATE cte
SET [Status] = 'Active'
WHERE RowNum = 1
AND [type] = 1
AND NOT EXISTS(
SELECT 1
FROM Tasks
WHERE [Status] = 'Active'
)
如果您只想在一个任务中使用 static = active,则设置 table 以确保这是真的。使用过滤后的唯一索引:
create unique index unq_tasks_status_filter_active on tasks(status)
where status = 'Active';
第二个并发 update
可能会失败,但可以确保您的唯一性。您的应用程序代码可以处理此类失败的更新,并且 re-try.
依赖更新的实际执行计划可能很危险。这就是让数据库进行此类验证更安全的原因。基础实施细节可能会有所不同,具体取决于 SQL 服务器的环境和版本。例如,在单线程、单处理器环境中工作的东西可能在并行环境中不工作。适用于一种隔离级别的方法可能不适用于另一种隔离级别。
编辑:
而且,我无法抗拒。为了提高效率,考虑将查询写成:
UPDATE Tasks
SET Status = 'Active'
WHERE NOT EXISTS (SELECT 1
FROM Tasks
WHERE Status = 'Active'
) AND
Id = (SELECT TOP 1 Id
FROM Tasks
WHERE Type = 2 -- <== The only difference
ORDER BY Id
);
然后在 Tasks(Status)
和 Tasks(Type, Id)
上放置索引。事实上,使用正确的查询,您可能会发现查询速度非常快(尽管对索引进行了更新),从而大大减轻了您对当前更新的担忧。这不会解决竞争条件,但它至少可以让它变得罕见。
如果您正在捕获错误,那么使用唯一过滤索引,您可以这样做:
UPDATE Tasks
SET Status = 'Active'
WHERE Id = (SELECT TOP 1 Id
FROM Tasks
WHERE Type = 2 -- <== The only difference
ORDER BY Id
);
如果行已经处于活动状态,这将 return 出错。
注意:所有这些查询和概念都可以应用于 "one active per group"。这个答案是针对您提出的问题。如果您遇到 "one active per group" 问题,请考虑再问一个问题。
不行,至少嵌套的select语句可以在开始更新和获取锁之前处理。为确保没有其他查询干扰此更新,需要将事务隔离级别设置为 SERIALIZABLE
.
这篇文章(及其所属的系列文章)很好地解释了 SQL 服务器中并发的微妙之处:
http://sqlperformance.com/2014/02/t-sql-queries/confusion-caused-by-trusting-acid
根据 UPDATE
documentation,一个 UPDATE
总是在整个 table 上获取排他锁。但是,我想知道排他锁是在确定要更新的行之前获取的,还是仅在实际更新之前获取的。
我的具体问题是我的 UPDATE
中有一个嵌套的 SELECT
,如下所示:
UPDATE Tasks
SET Status = 'Active'
WHERE Id = (SELECT TOP 1 Id
FROM Tasks
WHERE Type = 1
AND (SELECT COUNT(*)
FROM Tasks
WHERE Status = 'Active') = 0
ORDER BY Id)
现在我在想是否真的能保证恰好有一个
任务与 Status = 'Active'
之后如果可以并行执行相同的语句类型:
UPDATE Tasks
SET Status = 'Active'
WHERE Id = (SELECT TOP 1 Id
FROM Tasks
WHERE Type = 2 -- <== The only difference
AND (SELECT COUNT(*)
FROM Tasks
WHERE Status = 'Active') = 0
ORDER BY Id)
如果对于这两个语句,要更改的行将在获取锁之前确定,我可能会得到两个必须阻止的活动任务。
如果是这种情况,我该如何预防呢?我可以在不将事务级别设置为 SERIALIZABLE
或弄乱锁定提示的情况下阻止它吗?
从 Is a single SQL Server statement atomic and consistent? 的回答中我得知问题出现在嵌套的 SELECT
访问另一个 table 时。但是,如果只关注更新的 table,我不确定是否需要关心这个问题。
这不是您问题的答案...但是您的问题让我很痛苦:)
;WITH cte AS
(
SELECT *, RowNum = ROW_NUMBER() OVER (PARTITION BY [type] ORDER BY id)
FROM Tasks
)
UPDATE cte
SET [Status] = 'Active'
WHERE RowNum = 1
AND [type] = 1
AND NOT EXISTS(
SELECT 1
FROM Tasks
WHERE [Status] = 'Active'
)
如果您只想在一个任务中使用 static = active,则设置 table 以确保这是真的。使用过滤后的唯一索引:
create unique index unq_tasks_status_filter_active on tasks(status)
where status = 'Active';
第二个并发 update
可能会失败,但可以确保您的唯一性。您的应用程序代码可以处理此类失败的更新,并且 re-try.
依赖更新的实际执行计划可能很危险。这就是让数据库进行此类验证更安全的原因。基础实施细节可能会有所不同,具体取决于 SQL 服务器的环境和版本。例如,在单线程、单处理器环境中工作的东西可能在并行环境中不工作。适用于一种隔离级别的方法可能不适用于另一种隔离级别。
编辑:
而且,我无法抗拒。为了提高效率,考虑将查询写成:
UPDATE Tasks
SET Status = 'Active'
WHERE NOT EXISTS (SELECT 1
FROM Tasks
WHERE Status = 'Active'
) AND
Id = (SELECT TOP 1 Id
FROM Tasks
WHERE Type = 2 -- <== The only difference
ORDER BY Id
);
然后在 Tasks(Status)
和 Tasks(Type, Id)
上放置索引。事实上,使用正确的查询,您可能会发现查询速度非常快(尽管对索引进行了更新),从而大大减轻了您对当前更新的担忧。这不会解决竞争条件,但它至少可以让它变得罕见。
如果您正在捕获错误,那么使用唯一过滤索引,您可以这样做:
UPDATE Tasks
SET Status = 'Active'
WHERE Id = (SELECT TOP 1 Id
FROM Tasks
WHERE Type = 2 -- <== The only difference
ORDER BY Id
);
如果行已经处于活动状态,这将 return 出错。
注意:所有这些查询和概念都可以应用于 "one active per group"。这个答案是针对您提出的问题。如果您遇到 "one active per group" 问题,请考虑再问一个问题。
不行,至少嵌套的select语句可以在开始更新和获取锁之前处理。为确保没有其他查询干扰此更新,需要将事务隔离级别设置为 SERIALIZABLE
.
这篇文章(及其所属的系列文章)很好地解释了 SQL 服务器中并发的微妙之处:
http://sqlperformance.com/2014/02/t-sql-queries/confusion-caused-by-trusting-acid