AFTER INSERT 触发器调用调用 API 的存储过程导致 @@IDENTITY 出现问题

AFTER INSERT trigger calling stored procedure which calls API, causes problem with @@IDENTITY

我正在为客户替换经典的 ASP 网站和 VB6 后端进程。这是一个非常高流量的站点,其中各种进程将记录插入队列 table。几个进程是不同的 Web 服务,一个进程是经典 ASP 网站上的一种形式。对于新进程,我创建了一个调用存储过程的 AFTER INSERT 触发器,它对另一台服务器进行 API 调用。 API 调用将启动代码来处理新记录。这是 INSERT(免责声明:修改代码以掩盖客户身份......让我看起来尽可能聪明):

    -- =============================================
    -- Author:      Gary Jorgenson, RN
    -- Create date: 05/17/2021
    -- Description: notify system of New record
    -- =============================================

    CREATE TRIGGER [dbo].[tr_notify_new_record] 
    ON [dbo].[TableName] 
    AFTER INSERT
    AS 
    BEGIN
        DECLARE @id AS INT;
        SELECT @id = I.id FROM INSERTED I

        EXEC [dbo].[core_api_notify_new_record]
                    @record_id = @id
    END

存储过程如下:

    -- =============================================
    -- Author:      Gary Jorgenson, RN
    -- Create date: 5/16/2021
    -- Description: Notify system of new record
    -- =============================================

    CREATE PROCEDURE [dbo].[usp_api_newrecord_notify]
        @record_id AS INT 
    AS
    BEGIN
        DECLARE @URL NVARCHAR(128) = 'https://ourwebsite.com/api/newrecord';
        DECLARE @recID AS VARCHAR(50) = CONVERT(VARCHAR(50), @record_id);
        SET @URL = CONCAT(@url, '/',  @recID);

        DECLARE @Object AS INT;
        DECLARE @ResponseText AS VARCHAR(8000);

        EXEC sp_OACreate 'MSXML2.XMLHTTP', @Object OUT;
        EXEC sp_OAMethod @Object, 'open', NULL, 'post', @URL,'false'

        EXEC [sp_OAMethod] @Object , 'setRequestHeader' , NULL , 'Content-Type' , 'application/json'
        EXEC [sp_OAMethod] @Object , 'responseText' , @ResponseText OUTPUT

        SELECT @ResponseText AS [Details]

        EXEC [sp_OADestroy] @Object 
    END

为了进行初始测试,我让 API 控制器只是简单地插入到记录新创建记录的记录 ID 的日志 table 中。我使用 POSTMAN 在本地测试了 API 并且成功了。然后我锁定了 sp_OA... 方法以仅提供必要的权限,并通过使用与生产中使用的相同凭据在 SSMS 中执行它来测试存储过程,并且成功了。最后,我启用了 AFTER INSERT 触发器,非常有信心不会出错。

我错了。

启动触发器几分钟后,一些客户打电话报告说网站在尝试提交表单时崩溃了。与原始网站开发人员合作,我们确定他的代码正在执行多语句 SQL Insert where 最后一个语句调用 @@IDENTITY 以获取新记录 ID。不知何故,存储过程 and/or API 调用影响了 @@IDENTITY,它返回了空值或零值。

这是没有意义的,因为在此过程中进行的唯一其他插入是在 API 控制器的后面,该控制器在 SQL 服务器的不同实例中的不同机器上插入日志记录.

原来的网站开发人员正在将 @@IDENTITY 改为使用 SCOPE_IDENTITY()。我们将进行测试,看看是否可以缓解问题。整件事让我很紧张,因为我从未想过这个过程会对 @@IDENTITY 产生任何影响,因为我没有在本地机器上的任何 table 中插入任何记录。我想更好地了解这个过程中发生的事情。

最终我的问题是,由于我没有插入任何记录,我的过程如何影响 @@IDENTITY

非常感谢任何建议!

根据 docs

@@IDENTITY and SCOPE_IDENTITY return the last identity value generated in any table in the current session. However, SCOPE_IDENTITY returns the value only within the current scope; @@IDENTITY is not limited to a specific scope.

所以当使用 @@IDENTITY 时可能的情况是触发器也在执行插入(在你的情况下可能是系统 table)因此这个新 ID 在 [=10 中返回=] 返回给用户——这当然不是他们想要的 id。

我真的想不出你会想使用 @@IDENTITY 的情况,这些天你通常会使用 OUTPUT 子句来确保你得到准确的 id(s)你正在寻找。如果出于某种原因这不是一个选项,那么 SCOPE_IDENTITY() 是比 @@IDENTITY.

更好的选择

IDENT_CURRENT(TableName) 同样糟糕,因为返回的值跨越 ALL 会话和 ALL 范围...您以扩大范围为代价来限制 table。

注意:顺便说一句,你的触发器坏了,因为它假设 Inserted 将只包含一条记录,而实际上它可以包含 0-N。

原来是我自己的错。在存储过程中,我在返回 API 响应的过程末尾有一个 SELECT

   SELECT @ResponseText AS [Message]

之前的开发人员有他在 INSERT 中执行的代码,然后查询@@IDENTITY 以获得新的记录 ID。他没有获取整数记录 ID,而是从 API 调用中获取消息。他将代码更改为使用 SCOPE_IDENTITY() 而不是 @@IDENTITY,我们测试发现它仍然不起作用。再深入一点,我们发现了问题。