我应该只依靠外键约束来处理错误吗?

SQL*_*ero 6 foreign-key sql-server stored-procedures

我一直在使用以下模式来验证执行 INSERT 的存储过程的 FOREIGN KEY (FK) 参数:

CREATE PROC spCreateChildFoo
    @ForeignKey_Id int,
    @Attribute varchar(50)
AS

IF NOT EXISTS (SELECT ForeignKey_Id FROM ForeignTable WHERE ForeignKey_Id = @ForeignKey_Id)
    BEGIN
        RAISERROR ('The input ForeignKey_Id does not exist', 16, 1)
        RETURN;
    END

INSERT INTO ChildTable (ForeignKey_Id, Attribute)
VALUES (@ForeignKey_Id, @Attribute)
Run Code Online (Sandbox Code Playgroud)

我这样做是因为我想在任何数据被 INSERT 之前在存储过程的执行早期捕获错误的 FK 条目,尤其是对于执行多个 INSERT 的过程。我还认为这是返回准确错误消息以简化调试的好方法。这种模式非常适合实现这两个目标。

问题

  • 这是浪费时间吗?

  • 更具体地说,我是否应该仅依靠 FK 约束来验证 FK 参数并在失败时回滚?

  • 随着数据库的增长,它最终会损害我的应用程序的性能吗?

And*_*y M 10

是的,至少当您的存储过程只是可能导致外键冲突的 INSERT 语句时,这是浪费时间。在有外键的情况下,数据库引擎本身根据引用表对任何尝试的 FK 值进行验证。

如果引用表中不存在该值,您将收到异常并中止 INSERT 语句。

所以,这已经足够了:

CREATE PROC spCreateChildFoo
    @ForeignKey_Id int,
    @Attribute varchar(50)
AS

INSERT INTO ChildTable (ForeignKey_Id, Attribute)
VALUES (@ForeignKey_Id, @Attribute);
Run Code Online (Sandbox Code Playgroud)

另一方面,如果您的过程不止一个 INSERT 语句,并且您想在其开头的无效插入中中止整个过程,那么初步检查 FK 值可能并非完全没有意义。然而,有效的 FK 值仍然会导致双重查找,这既不好也不必要。为了达到中止整个过程的相同效果,您可以将过程的主体放入 TRY/CATCH 块中:

CREATE PROC spCreateChildFoo
    @ForeignKey_Id int,
    @Attribute varchar(50)
AS

BEGIN TRY
    INSERT INTO ChildTable (ForeignKey_Id, Attribute)
    VALUES (@ForeignKey_Id, @Attribute);

    ... -- other statements
END TRY
BEGIN CATCH
    RAISERROR ('The input ForeignKey_Id does not exist', 16, 1);
    -- or you could catch ERROR_NUMBER(), ERROR_MESSAGE() etc.
    -- and re-raise the original exception
END CATCH;
Run Code Online (Sandbox Code Playgroud)

更多信息: