错误和捕获或检查现有更好吗?

时间:2014-03-13 15:11:48

标签: sql sql-server-2005

我有两个表定义为

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

我的问题是,"适当的"没有将该错误传播给用户的方法,并且不会弄乱用户可能拥有的任何当前事务(可能具有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被删除。

使用的正确方法是什么?

1 个答案:

答案 0 :(得分:3)

  

使用的正确方法是什么?

检查是否存在,然后相应地插入或更新。

错误处理非常昂贵 - 一旦发生错误,就无法返回并做一些与众不同的事情。

修改

老实说,我可能错了#34;昂贵的"部分(并没有任何证据支持它),因为我不是SQL专家,但不能回头的原则适用。

Here's来自Aaron Bertrand的一篇文章,他是一个比我更好的SQL来源。乍一看似乎表明这两种方法都没有明显优于其他表现方式。