2014-03-13 104 views
0

我有兩個表定義爲錯誤和抓住或檢查現有的更好嗎?

CREATE TABLE [dbo].[Foo](
    FOO_GUID uniqueidentifier NOT NULL PRIMARY KEY CLUSTERED, 
    SECOND_COLUMN nvarchar(10) NOT NULL, 
    THIRD_COLUMN nvarchar(10) NOT NULL) 

CREATE TABLE [dbo].[FooChanged](
    [FooGuid] uniqueidentifier NOT NULL PRIMARY KEY CLUSTERED, 
    [IsNewRecord] bit NOT NULL) 

和觸發

CREATE TRIGGER dbo.[trg_FooChanged] 
    ON [dbo].[Foo] 
    AFTER INSERT,UPDATE 
    NOT FOR REPLICATION 
AS 
BEGIN 
    SET NOCOUNT ON; 

    DECLARE @isNewRecord as bit; 

    IF EXISTS(SELECT * FROM DELETED) 
    BEGIN 
     --We only want to make a record if the guid or one other column in Foo changed. 
     IF NOT(UPDATE(FOO_GUID) OR UPDATE(SECOND_COLUMN)) 
      RETURN; 

     SET @isNewRecord = 0 
    END 
    ELSE 
     SET @isNewRecord = 1; 

    insert into FooChanged(FooGuid, IsNewRecord) SELECT FOO_GUID, @isNewRecord from INSERTED 

END 

下面的簡單的測試腳本與一個主鍵約束衝突的最後一批(預期)

INSERT INTO [dbo].[Foo] ([FOO_GUID],[SECOND_COLUMN],[THIRD_COLUMN]) VALUES (cast(0x1 as uniqueidentifier), '1', '1') 
GO 
INSERT INTO [dbo].[Foo]([FOO_GUID],[SECOND_COLUMN],[THIRD_COLUMN]) VALUES (cast(0x2 as uniqueidentifier), '2', '2') 
GO 
UPDATE Foo SET THIRD_COLUMN = '1a' WHERE FOO_GUID = cast(0x1 as uniqueidentifier) 
GO 
UPDATE Foo SET SECOND_COLUMN = '1a' WHERE FOO_GUID = cast(0x1 as uniqueidentifier) 
GO 
失敗

我的問題是,這是「正確」的方式,不要讓錯誤傳播給用戶,也不會搞亂用戶可能擁有的任何當前事務(w可能的設置如XACT_ABORT ON)。

兩個選項我看到的是插入

declare @guid uniqueidentifier 
    select @guid = FOO_GUID from INSERTED 

    BEGIN TRANSACTION  
    if NOT EXISTS(select * from FooChanged WITH (UPDLOCK, HOLDLOCK) where FooGuid = @guid) 
     insert into FooChanged(FooGuid, IsNewRecord) SELECT @guid, @isNewRecord from INSERTED 
    COMMIT TRANSACTION 

前兩種檢查,但我需要鎖在桌子上,以防止任何種族條件,我認爲將導致忙表

或性能問題捕獲錯誤在一個try/catch

BEGIN TRY 
     insert into FooChanged(FooGuid, IsNewRecord) SELECT @guid, @isNewRecord from INSERTED 
    END TRY 
    BEGIN CATCH 
     DECLARE @ErrorMessage NVARCHAR(4000); 
     DECLARE @ErrorSeverity INT; 
     DECLARE @ErrorState INT; 

     SELECT 
      @ErrorMessage = ERROR_MESSAGE(), 
      @ErrorSeverity = ERROR_SEVERITY(), 
      @ErrorState = ERROR_STATE(); 

     if(ERROR_NUMBER() != 2627) 
      RAISERROR (@ErrorMessage, -- Message text. 
         @ErrorSeverity, -- Severity. 
         @ErrorState -- State. 
      ); 
    END CATCH 

但我擔心代碼XACT_ABORT事務內部運行得到它的XACT_STATE與。

什麼是正確的使用方法?

回答

3

什麼是正確的使用方法?

檢查是否存在,然後插入或更新。

錯誤處理非常昂貴 - 一旦發生錯誤,無法去返回並做一些不同的事情。

編輯

老實說,我可能是錯的「昂貴」的一部分(而且沒有任何證據來支持它),因爲我不是一個SQL專家,但不是原則能夠回到基層適用。

Here's來自Aaron Bertrand的文章,他是一位比我更好的SQL源。乍一看,似乎表明這兩種方法都不如其他性能明顯好。

+0

我知道在C#中這是真的,但我不知道是否相同的規則適用於SQL。謝謝! –

+0

做了一些測試後,我發現我總是得到'在觸發器執行過程中出現錯誤。該批處理已被中止,並且用戶事務(如果有的話)已被回滾。「所以catch方法不是一個選項。在詢問之前應該多測試一下。 –