0

我对 BOL 短语感到困惑:

“不能为插入、更新或删除操作修改的表指定 READUNCOMMITTED 和 NOLOCK。SQL Server 查询优化器忽略 FROM 子句中适用于 UPDATE 或 DELETE 语句的目标表的 READUNCOMMITTED 和 NOLOCK 提示”[1]

例如,如果我写

--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) 被忽略?
为什么要完全忽略它?它被忽略了吗?
或者,如果这是一个错误的问题,那么
为什么语法允许保证被忽略的提示?

再一次,要么不可能(或者是吗?)写出像文档中那样写的声明,或者我不理解“忽略”的含义(忽略它是什么意思?或根本没有它?) ...

UPDATE2:
答案表明 NOLOCK 在 UPDATE 语句的 FROM 子句中未被(更新)忽略,BOL 文档 [1] 断言。
好吧,这个问题的本质:
你能给我任何例子(上下文),在 UPDATE 语句的 FROM 子句中忽略 NOLOCK 是有意义的吗?

[ 1 ]
表提示 (Transact-SQL)
SQL Server 2008 R2
http://msdn.microsoft.com/en-us/library/ms187373.aspx

4

3 回答 3

4

无需猜测。

Sybase 和 MS SQL 服务器使用内部自动 2PL 资源锁定,但完全符合 ISO/IEC/ANSI SQL 标准。当您尝试理解所有可能的组合时,语法会变得很愚蠢,因为某些子句与每个命令都不相关。

手册试图说的但没有用简单的英语说的是:

  • 对于您正在执行的任何外部操作或事务中的单个查询,您可以SET ISOLATION LEVEL
  • 也可以使用UNCOMMITTED, NOLOCK, HOLDLOCK语法指定
  • 如果您在外部查询中有一个 IL,或者在事务中有一个查询,但希望对内部查询使用不同的 IL,则可以这样做(在内部查询中使用不同的调制器)
  • 因此您可以在 IL3 执行事务,并在其中SELECT执行一个在 IL0 或 IL1

分别地:

  • 无论您认为自己在做什么或想要做什么,因为锁定是自动的,并且ISOLATION LEVEL 3是and 所必需UPDATESDELETES其中READ UNCOMMITTEDandNOLOCK不适用,也不能使用,如果您使用过它们,服务器将忽略它们
于 2011-01-17T23:10:11.820 回答
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(以确保他们针对正确的行/columns),然后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
于 2010-12-01T08:36:50.060 回答
0

在一个会话(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;
于 2010-12-01T13:04:59.507 回答