【问题标题】: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 R2
http://msdn.microsoft.com/en-us/library/ms187373.aspx

【问题讨论】:

    标签: sql sql-server tsql database-design query-hints


    【解决方案1】:

    无需猜测。

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

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

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

    分别:

    • 不管你认为你在做什么,或者想要做什么,因为锁定是自动的,ISOLATION LEVEL 3对于UPDATESDELETES必需,其中READ UNCOMMITTEDNOLOCK不适用,也不能使用,如果你用过服务器会忽略的

    【讨论】:

    • 如果你经常这样回答的话……+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 提示(因此在文档中引用了目标表)。
    • 是的,这是常识性的希望,但事实不同。看我的回答stackoverflow.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;
    

    【讨论】:

      猜你喜欢
      • 2011-05-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-02-25
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多