2011-12-19 44 views
2

以下sproc嘗試向表中插入一行並生成隨機ID,該ID用於相應表上的PK。在catch塊中處理隨機生成的ID的衝突,在該塊中再次重試/調用該過程。現在,這需要很長時間,並導致死鎖,因爲鎖定會保留很長一段時間。有沒有辦法在重試之前立即釋放死鎖,以便在其他線程可以成功鎖定PK索引時出現一個短窗口?重試INSERT引起的死鎖


CREATE PROCEDURE addPerson 
    (
     @FirstName nvarchar(100), 
     @LastName nvarchar(100) 
    ) 
AS 
BEGIN 
    SET NOCOUNT ON; 

    DECLARE @PersonId int 

    -- generate random PersonId 
    -- this sproc can generate ids that already exist in the table 
    EXEC generateRandomPersonId @[email protected] OUTPUT 

    BEGIN TRY  
     INSERT INTO [dbo].[Persons] 
     (
      PersonId,FirstName,LastName 
     ) 
     VALUES 
     (
      @PersonId,@FirstName,@LastName 
     ) 

    BEGIN CATCH 
     -- 
     -- HOW TO RELEASE LOCKS HERE that are still held 
     -- for the previous INSERT statement? 
     -- 

     DECLARE @ErrorNumber int, @ErrorMessage nvarchar(2048) 
     SELECT @ErrorNumber=ERROR_NUMBER(), 
       @ErrorMessage=ERROR_MESSAGE() 

     -- if a race condition happened and 
     -- PersonId happened to be picked already, retry all over again 
     IF (@ErrorNumber = 2601 OR @ErrorNumber = 2627 AND CHARINDEX(N'PK_Persons_PersonId', @ErrorMessage) > 0) 
      BEGIN 
       -- 
       -- RETRYING HERE participates in a high possibility and 
       -- occurrence of deadlocks 
       -- 
       EXEC addPerson @FirstName,@LastName 
      END 
      ELSE 
       -- some other error, rethrow it 
       EXEC rethrowError 
      END 
    END CATCH 
END 
GO 
+0

這是一個COMERCIAL或生產環境產品? – danihp 2011-12-19 20:49:38

+0

怎麼會有一個唯一的違反約束的錯誤代碼?我根本不知道SQL服務器,只是好奇而已 – fge 2011-12-19 20:50:21

+0

爲什麼不使用[transactions](http://beyondrelational.com/blogs/nakul/archive/2011/02/24/exception-handling-in-t-sql - 嘗試 - 追趕懷才不遇的功能 - 的 - 微軟SQL-server.aspx)?在'try'和rollback之前開始事務,你寫評論_如何發佈...。 – danihp 2011-12-19 20:57:21

回答

2

進程不會阻塞自己的鎖。由於對存儲過程的調用在相同的進程中運行,因此第二個insert不可能等待第一個insert的鎖定。

你能發佈死鎖圖嗎?這顯示了關於阻塞過程的大量信息。

作爲速戰速決,您可以搜索在一個循環中游離的ID,這將避免大多數(但不是全部)可能的衝突:

while 1=1 
    begin 
    EXEC generateRandomPersonId @[email protected] OUTPUT 
    if not exists (select * from Persons where PersonId = @PersonID) 
     break 
    end 
+0

+1 - 這絕對是操作者應該做的,搜索一個空閒的ID,然後執行他的INSERT' – Lamak 2011-12-19 21:15:55

+0

它本身不會阻塞,而是需要另一個需要獲得PK索引鎖定的過程。至於檢查生成的id是否不存在,它不能保證唯一性,因爲它不會是線程安全的。 – kateroh 2011-12-19 22:02:39

+0

添加了一個檢查來驗證該ID沒有被採用,並用循環和瞧代替了遞歸調用!謝謝 – kateroh 2012-01-05 18:32:18

1

首先,我希望這個程序只適用在學術環境中,而不是在商業產品或實際生產環境中。

這是一個方法:

  • 由循環替換遞歸
  • 使用交易

CREATE PROCEDURE addPerson 
    (
     @FirstName nvarchar(100), 
     @LastName nvarchar(100) 
    ) 
AS 

BEGIN 
    SET NOCOUNT ON; 

    DECLARE @doed bit 

    set @doed = 0 

    DECLARE @PersonId int 

    WHILE @doed = 0 

    BEGIN 
    -- generate random PersonId 
    -- this sproc can generate ids that already exist in the table 
    EXEC generateRandomPersonId @[email protected] OUTPUT 

    BEGIN TRANSACTION ExceptionHandling 
    BEGIN TRY  

      INSERT INTO [dbo].[Persons] 
      (
       PersonId,FirstName,LastName 
      ) 
      VALUES 
      (
       @PersonId,@FirstName,@LastName 
      ) 
      COMMIT TRANSACTION ExceptionHandling 
     BEGIN CATCH 

      ROLLBACK TRANSACTION ExceptionHandling 
      -- 
      -- HOW TO RELEASE LOCKS HERE that are still held 
      -- for the previous INSERT statement? 
      -- 

      DECLARE @ErrorNumber int, @ErrorMessage nvarchar(2048) 
      SELECT @ErrorNumber=ERROR_NUMBER(), 
        @ErrorMessage=ERROR_MESSAGE() 

         -- if a race condition happened and 
      -- PersonId happened to be picked already, retry all over again 
      IF !(@ErrorNumber = 2601 OR @ErrorNumber = 2627 AND CHARINDEX(N'PK_Persons_PersonId', @ErrorMessage) > 0) 
       BEGIN 
        -- 
        -- RETRYING HERE participates in a high possibility and 
        -- occurrence of deadlocks 
        set @doed = 0 
       END 
       ELSE 
        -- some other error, rethrow it 
        set @doed = 1 
        EXEC rethrowError 
       END 
     END CATCH 
    END --end while 
END 
GO​ 

免責聲明:沒有測試