1

我正在学习更复杂的 SQL Server 2008 技术,所以如果我问了一个太明显的问题,我提前道歉。

我有这样创建的下表:

CREATE TABLE [dbo].[t_Log_2] 
(
   [id] INT NOT NULL IDENTITY(1,1) PRIMARY KEY, 
   [oid] INT, 
   [idtm] DATETIME2, 
   [odtm] DATETIME2, 
   [type] TINYINT, 
   [state] TINYINT, 
   [huid] UNIQUEIDENTIFIER, 
   [cnm] NVARCHAR(256), 
   [cmdl] NVARCHAR(256), 
   [batt] TINYINT, 
   [dvtp0] SMALLINT, 
   [dvtp1] SMALLINT
);

CREATE INDEX idx_idt 
          ON [dbo].[t_Log_2]([idtm]);

CREATE INDEX idx_odt 
          ON [dbo].[t_Log_2]([odtm]);

CREATE INDEX idx_huid 
          ON [dbo].[t_Log_2]([huid]);

CREATE INDEX idx_cnm 
          ON [dbo].[t_Log_2]([cnm]);

然后可以从 ASP.NET Web 应用程序的多个同时线程运行以下查询。请注意,整个查询需要以原子方式运行:

SET XACT_ABORT ON;
BEGIN TRANSACTION;

DELETE FROM [dbo].[t_Log_2] 
      WHERE [idtm]<'2011-03-12 08:41:57';

WITH ctx AS(
     SELECT MIN([idtm]) AS mdIn, 
            MAX([odtm]) AS mdOut 
           FROM [dbo].[t_Log_2] 
          WHERE [type] = 0 
            AND [state] = 0 
            AND [huid] = N'18ef4d56-6ef3-906a-a711-88d1bd6ab2d4' 
            AND [odtm] >= '2013-03-11 06:33:32' 
            AND [idtm] <= '2013-03-11 06:43:12' 
           ) 
INSERT INTO [dbo].[t_Log_2] 
([oid],[idtm],[odtm],[type],[state],[huid],
 [cnm],[cmdl],[batt],[dvtp0],[dvtp1]) 
SELECT 
    2, 
    CASE WHEN mdIn IS NOT NULL 
          AND mdIn < '2013-03-11 06:33:32' 
         THEN mdIn 
         ELSE '2013-03-11 06:33:32' 
         END,
    CASE WHEN mdOut IS NOT NULL 
          AND mdOut > '2013-03-11 06:43:12' 
         THEN mdOut 
         ELSE '2013-03-11 06:43:12' 
         END,
    0,
    0,
    N'18ef4d56-6ef3-906a-a711-88d1bd6ab2d4',
    null,
    null,
    0,
    1,
    null 
FROM ctx 

SELECT ROWCOUNT_BIG()

DELETE FROM [dbo].[t_Log_2] 
      WHERE [type] = 0 
        AND [state] = 0 
        AND [huid] = N'18ef4d56-6ef3-906a-a711-88d1bd6ab2d4' 
        AND [odtm] >= '2013-03-11 06:33:32' 
        AND [idtm] <= '2013-03-11 06:43:12' 
        AND [id] <> SCOPE_IDENTITY()

DELETE FROM [dbo].[t_Log_2] 
      WHERE [type] = 0 
        AND [huid] = N'18ef4d56-6ef3-906a-a711-88d1bd6ab2d4' 
        AND [idtm] >= (SELECT [idtm] FROM [dbo].[t_Log_2] 
                                    WHERE [id] = SCOPE_IDENTITY()) 
        AND [odtm] <= (SELECT [odtm] FROM [dbo].[t_Log_2] 
                                    WHERE [id] = SCOPE_IDENTITY()) 
        AND [id] <> SCOPE_IDENTITY() 

;WITH ctx1 AS( 
     SELECT [idtm] AS dI 
       FROM [dbo].[t_Log_2] 
      WHERE [id] = SCOPE_IDENTITY() 
             )
UPDATE [dbo].[t_Log_2] 
        SET [odtm] = ctx1.dI 
       FROM ctx1 
      WHERE [id] <> SCOPE_IDENTITY() 
        AND [type] = 0 
        AND [huid] = N'18ef4d56-6ef3-906a-a711-88d1bd6ab2d4' 
        AND [idtm] < ctx1.dI 
        AND [odtm] > ctx1.dI 

;WITH ctx2 AS(
     SELECT [odtm] AS dO 
       FROM [dbo].[t_Log_2] 
      WHERE [id] = SCOPE_IDENTITY() 
             ) 
UPDATE [dbo].[t_Log_2] 
        SET [idtm] = ctx2.dO 
       FROM ctx2 
      WHERE [id] <> SCOPE_IDENTITY() 
        AND [type] = 0 
        AND [huid] = N'18ef4d56-6ef3-906a-a711-88d1bd6ab2d4' 
        AND [idtm] < ctx2.dO 
        AND [odtm] > ctx2.dO 

COMMIT TRANSACTION;
SET XACT_ABORT OFF

请注意,上面的查询是从动态组合它的 C# 代码中一对一复制的。实际上,它的参数不是如上所示的硬编码。

此查询在大多数情况下都有效,但有时我会在日志中收到以下错误:

事务(进程 ID 80)与另一个进程在锁资源上死锁,并被选为死锁牺牲品。重新运行事务。

知道我该怎么做才能防止这种僵局吗?

4

1 回答 1

0

您需要持有更多或更少的锁。

最简单的答案是选择NOLOCK(最佳性能)或TABLOCKX(无需考虑的一致性)。

如果with (nolock)因为一致性要求不能使用,可以添加with (tablockx). 这实际上意味着一次只有一个线程可以执行类似的语句——不会有并发。

另一种方法是更详细地分析您的需求,如果不了解更新表的原因、数据的用途等,就无法做到这一点。

例如,这个语句真的需要在事务中吗?闻起来像家务:

  DELETE FROM [dbo].[t_Log_2] 
  WHERE [idtm]<'2011-03-12 08:41:57';

如果您将其从事务中取出,并将其放在单独的批次中,您可能会发现问题消失了。

于 2013-03-12T10:22:44.710 回答