为啥“在适用于 UPDATE 或 DELETE 语句的目标表的 FROM 子句中”忽略 NOLOCK?
Posted
技术标签:
【中文标题】为啥“在适用于 UPDATE 或 DELETE 语句的目标表的 FROM 子句中”忽略 NOLOCK?【英文标题】:Why NOLOCK is ignored "in the FROM clause that apply to the target table of an UPDATE or DELETE statement"?为什么“在适用于 UPDATE 或 DELETE 语句的目标表的 FROM 子句中”忽略 NOLOCK? 【发布时间】:2010-12-01 08:14:16 【问题描述】:我对 BOL 短语感到困惑:
“不能为插入、更新或删除操作修改的表指定 READUNCOMMITTED 和 NOLOCK。SQL Server 查询优化器忽略 FROM 子句中适用于 UPDATE 或 DELETE 语句的目标表的 READUNCOMMITTED 和 NOLOCK 提示” [一]
例如,如果我写
--script 1)
UPDATE Test SET Txt=(Select Txt from TEST WITH(NOLOCK) where ID=1)
WHERE ID=1
它运行时没有错误(或警告),可能相当于
--script 2)
set transaction isolation level SERIALIZABLE;
begin tran
Declare @nvarm nvarchar(max);
Select @nvarm=Txt from Test where ID=1;
--Select @nvarm;
UPDATE Test SET Txt=@nvarm WHERE ID=1;
commit;
它也可以在没有错误或警告的情况下运行。 是否等效?
表是相同的,但在 FROM 中它在逻辑上是源表而不是目标表 我可以用不同的源表重写 1) 作为另一个(物理)表:
--script 3)
select *
into testDup
from TEST;
GO;
UPDATE Test SET Txt=(SELECT Txt FROM TestDUP WITH(NOLOCK) where ID=1)
WHERE ID=1
为什么要在另一个表上忽略 NOLOCK? 或者,如果错了,那就提问 如何编写具有“适用于 UPDATE 或 DELETE 语句的目标表的 FROM 子句中的 NOLOCK 提示”的 UPDATE,因为即使在 1)和 2)中,物理表也是相同的,但在逻辑上是源(在 SELECT 中)表和目标(在 UPDATE 中)表是不同的。
如何编写一个 UPDATE 语句来证明 WITH(NOLOCK) 被忽略? 为什么要完全忽略它?会被忽略吗? 或者,如果这是一个错误的问题,那么 为什么语法允许保证被忽略的提示?
再一次,要么不可能(或者是吗?)写出文档中写的这样的声明,要么我不理解“忽略”的含义(忽略它的意义是什么?或让它在全部?)...
更新2: 答案表明,NOLOCK 在 UPDATE 语句的 FROM 子句中未被(更新)忽略,BOL 文档 [1] 断言。 嗯,这个问题的本质: 你能给我任何例子(上下文),在 UPDATE 语句的 FROM 子句中忽略 NOLOCK 是有意义的吗?
[1] 表提示 (Transact-SQL) SQL Server 2008 R2http://msdn.microsoft.com/en-us/library/ms187373.aspx
【问题讨论】:
【参考方案1】:无需猜测。
Sybase 和 MS SQL 服务器使用内部自动 2PL 资源锁定,但完全符合 ISO/IEC/ANSI SQL 标准。当您尝试理解所有可能的组合时,语法会变得很愚蠢,因为有些子句与每个命令都不相关。
手册试图说的,但没有用简单的英语说的是:
对于您正在执行的任何外部操作或事务中的单个查询,您可以SET ISOLATION LEVEL
也可以使用UNCOMMITTED, NOLOCK, HOLDLOCK
syntax 指定
如果您在外部查询中有一个 IL,或者在事务中只有一个查询,但希望对内部查询使用不同的 IL,则可以这样做(在内部查询中使用不同的调节器)
因此您可以在 IL3 处执行事务,并在其中有一个 SELECT
在 IL0 或 IL1 处执行
分别:
不管你认为你在做什么,或者想要做什么,因为锁定是自动的,ISOLATION LEVEL 3
对于UPDATES
和DELETES
是必需,其中READ UNCOMMITTED
和NOLOCK
不适用,也不能使用,如果你用过服务器会忽略的
【讨论】:
如果你经常这样回答的话……+1【参考方案2】:UPDATE 或 DELETE 语句的 FROM 子句在您的任何示例中都不明显。您在子查询中有 from 子句,但它们不是一回事。
这是一个 UPDATE 的 FROM 子句:
UPDATE t
SET Col = u.Val
FROM /* <-- Start of FROM clause */
Table t WITH (NOLOCK)
inner join
Table2 u
on
t.ID = u.ID
/* End of FROM clause */
WHERE
u.Colx = 19
而且,正如文档所指出的,在这种情况下,WITH (NOLOCK)
将被忽略。至于为什么如果要忽略它是允许的,一种猜测是这样的提示在“相同”查询的SELECT
版本中是有效的,并且人们确实经常编写 SELECTs(以确保他们的目标是正确的行/列),然后将 SELECT
子句替换为 UPDATE
/SET
子句对,并且可以保持查询的其余部分保持不变。
根据来自 vgv8 的评论/“答案”更新:
您的示例更新仍然没有查看 UPDATE 语句的 FROM 子句
以下工作正常,即使 TABLOCKX() 在另一个连接上打开:
UPDATE T SET Txt= td.Txt
FROM TEST t inner join TESTDUP td WITH (NOLOCK) on t.ID = td.ID
where t.ID = 1
【讨论】:
“Table2 u WITH(NOLOCK)”中的NOLOCK会被忽略吗? @vgv8 - 我相信除了要更新的表之外的任何表都会遵守 NOLOCK 提示(因此在文档中引用了目标表)。 是的,这是常识性的希望,但事实不同。看我的回答***.com/questions/4322368/… @vgv8 - 我已经编辑过,并再次指出您的示例没有处理 UPDATE 语句的 FROM 子句。【参考方案3】:在一个会话(SSMS 的窗口)中创建并填充了 2 个相同的表 Test 和 TestDUP [1],我执行
--2)
begin tran
Select Txt from TestDUP with(TABLOCKX)
WHERE ID=1
--rollback
在同一张表上阻止来自另一个会话(SSMS 窗口)的 SELECT,例如:
--3.1)
select * from TestDUP
但不是
--3.2)
select * from TestDUP WITH(NOLOCK)
请注意,3.1) 被阻止,但 3.2) 未被阻止。
不过,使用来自 TestDUP 的 SELECT 更新另一个表 TEST
--4)WITH(NOLOCK) is not honored until completing
-- (commit/roollback)-ing transaction 2)
UPDATE Test SET Txt=
(Select Txt from TESTDUP WITH(NOLOCK) where ID=1)
WHERE ID=1;
被阻塞,因为另一个源表上的 WITH(NOLOCK) 在 UPDATE 语句的 FROM 子句中被忽略了。
更新:
--4.1)WITH(NOLOCK) is honored
-- in FROM clause of UPDATE statement
UPDATE Test SET Txt= td.Txt
FROM TESTDUP td WITH (NOLOCK)
where test.ID = 1
--4.2) Note that without NOLOCK this script is blocked
-- until first transaction 2) completes (rollbacks or commits)
UPDATE Test SET Txt= td.Txt
FROM TESTDUP td WITH (NOLOCK)
where test.ID = 1
所以,它现在是有道理的,但它与文档相矛盾,因为 UPDATE 语句的 FROM 子句中的 NOLOCK 不会被忽略,不是吗?
[1] 创建 2 个相同填充的表 Test 和 testDUP:
if object_id('Test') IS not NULL
drop table Test;
CREATE TABLE Test (
ID int IDENTITY PRIMARY KEY,
Txt nvarchar(max) NOT NULL
)
GO
-----------
INSERT INTO Test
SELECT REPLICATE(CONVERT(nvarchar(max),
CHAR(65+ABS(CHECKSUM(NEWID()))%26)),100000)
GO 10
--COPYING TEST into TESTDUP with creating of the latter
select *
into testDup
from TEST;
【讨论】:
以上是关于为啥“在适用于 UPDATE 或 DELETE 语句的目标表的 FROM 子句中”忽略 NOLOCK?的主要内容,如果未能解决你的问题,请参考以下文章
为啥 DataGridView 上的 DoubleBuffered 属性默认为 false,为啥它受到保护?