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
,我们测试发现它仍然不起作用。再深入一点,我们发现了问题。
我正在为客户替换经典的 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
,我们测试发现它仍然不起作用。再深入一点,我们发现了问题。