停止触发器更改 scope_identity

Posted

技术标签:

【中文标题】停止触发器更改 scope_identity【英文标题】:Stop trigger changing scope_identity 【发布时间】:2013-04-08 15:37:15 【问题描述】:

我的应用程序将一些数据插入到表中。

insert into this_Table (id, value, value)

然后,我创建了一个触发器,该触发器使用主键将简单插入到不同的表中。

insert into temp_wc_triggertest values ('test', GETDATE())

我的问题是,应用程序尝试从第一次插入中查找scope_identity。但是,它会被触发器覆盖,从而将作用域标识更改为temp_wc_triggertest 的主键。

如何停止触发器覆盖scope_identity

我意识到这里没有太多可以提供帮助的代码,这通常会被归类为一个不好的问题,但我目前无权访问完整的应用程序代码,所以我希望这是可以回答的是。

这是在 SQL Server 2008 R2 上

编辑:我查看了代码,它确实使用了 scope_identity

【问题讨论】:

如果您使用了@@identity,那么情况确实如此,但由于您使用的是 scope_identity,所以不应该发生这种情况。 我被告知它是scope_identity,但尚未确认。不管它是什么,我确信它正在改变。 @MikaelEriksson 做到了。当您说“应用程序尝试查找scope_identity”时,这是什么意思?它如何/在哪里/何时获得范围标识? 正如我上面所说,我没有查看代码的访问权限,目前,我被告知的只是它使用范围标识。 你可以使用 SQL Profiler 看看客户端做了什么。 【参考方案1】:

您的客户肯定会使用@@IDENTITY 而不是SCOPY_IDENTITY()

这是一个 SQL Fiddle,其中包含一些您可以测试的代码。

SQL Fiddle

MS SQL Server 2008 架构设置

create table T1(ID int identity(1,1));
create table T2(ID int identity(1000, 1));

go

create trigger tr_T1 on T1 for insert
as
insert into T2 default values;

查询

insert into T1 default values

select @@identity as "@@identity", 
       scope_identity() as "scope_identity()"

Results

| @@IDENTITY | SCOPE_IDENTITY() |
---------------------------------
|       1000 |                1 |

【讨论】:

谢谢。这很有用,我不知道您可以在不同的点选择身份。我这样做了,身份总是以预期的身份返回(第一个表中的 id)。我认为这将与上面提到的错误有关【参考方案2】:

如果您正确使用 SCOPE_IDENTIY,您可能还会遇到一个已知错误 - http://connect.microsoft.com/SQLServer/feedback/details/328811

MS 已在 2012 年永久修复它,并为 2008 和 2008R2 提供补丁。

【讨论】:

【参考方案3】:

它覆盖范围标识的原因尚不清楚,可能与提到的错误有关。但是找到了一个修复:

创建了一个临时表“temp_wc”

然后在触发器结束时,为该表打开了标识插入,并为触发器触发后我们想要保留的 ID 完成了插入。这种方法可以认为是再次覆盖了被覆盖的作用域标识。

SET IDENTITY_INSERT ON
INSERT INTO temp_wc VALUES (@ID, 'fix scope identity error')

【讨论】:

很好的建议 - 我在触发器中使用了一个临时表并且它起作用了(这让我感到惊讶,因为我想知道临时表的范围是否会持续足够长的时间)。 if (select count(1) from inserted) = 1 begin create table #TempIdTable (ResetIdentity int identity(1, 1)) set identity_insert #TempIdTable on insert into #TempIdTable (ResetIdentity) select UserID from inserted set identity_insert #TempIdTable off end @MartinSmith 不正确,我有同样的问题,并使用SCOPE_IDENTITY() 创建了一个带有插入的临时表作为触发器中的最后一条语句。【参考方案4】:

这可能会帮助将来解决此问题的任何人:

https://docs.microsoft.com/en-us/sql/t-sql/functions/scope-identity-transact-sql?view=sql-server-2017

您可能需要修补 SQL Server,因为看起来SCOPE_IDENTITY 应该返回实际接收主插入的表上的插入 ID,而不是 SQL 触发器插入语句。

来自 Microsoft docs for SQL Server 2017(以防链接损坏):

备注 SCOPE_IDENTITY、IDENT_CURRENT 和@@IDENTITY 类似 函数,因为它们返回插入到标识中的值 列。

IDENT_CURRENT 不受范围和会话限制;它仅限于 指定的表。 IDENT_CURRENT 返回为 a 生成的值 任何会话和任何范围内的特定表。有关详细信息,请参阅 IDENT_CURRENT (Transact-SQL)。

SCOPE_IDENTITY 和 @@IDENTITY 返回最后一个身份值 在当前会话的任何表中生成。但是,SCOPE_IDENTITY 返回仅在当前范围内插入的值; @@IDENTITY 是 不限于特定范围。

例如,有两个表,T1 和 T2,一个 INSERT 触发器是 在 T1 上定义。当一行插入到 T1 时,触发器触发并 在 T2 中插入一行。这个场景说明了两个范围:插入 在 T1 上,由触发器在 T2 上插入。

假设 T1 和 T2 都有标识列,@@IDENTITY 和 SCOPE_IDENTITY 在 INSERT 结束时返回不同的值 T1 上的声明。 @@IDENTITY 返回最后一个标识列值 在当前会话的任何范围内插入。这就是价值 插入 T2。 SCOPE_IDENTITY() 返回插入的 IDENTITY 值 在 T1。这是在同一范围内发生的最后一个插入。这 SCOPE_IDENTITY() 函数返回空值,如果该函数是 在任何 INSERT 语句进入标识列之前调用 范围。

失败的语句和交易可以改变当前的身份 一个表并在标识列值中创建间隙。身份 即使尝试回滚的事务,值也永远不会回滚 将值插入表中未提交。例如,如果一个 由于 IGNORE_DUP_KEY 违规,INSERT 语句失败, 表的当前标识值仍在递增。

这里有另一个链接:

http://www.sqlbadpractices.com/how-not-to-retrieve-identity-value/

此代码的问题是您可能无法检索身份 您插入的值。例如,如果在 表在另一个表上执行插入,你会得到最后一个 创造了身份价值。即使您从未创建任何触发器,您也可以 由于 SQL Server 创建了他的复制表,因此结果会出现偏差 自己的复制触发器。

【讨论】:

我发帖已经有 5 年了,所以我无法真正测试,但这似乎是答案 @Wayneio 是的,我使用的是 SQL Server 2012,它似乎已在以后的版本中进行了修补。我花了几个小时来解决你的堆栈问题,并解决它:P【参考方案5】:

使用SELECT IDENT_CURRENT(‘tablename’)

“它返回表中生成的最后一个 IDENTITY 值,与创建该值的连接无关,也与生成该值的语句的范围无关。”

查看此链接了解详情。

http://blog.sqlauthority.com/2007/03/25/sql-server-identity-vs-scope_identity-vs-ident_current-retrieve-last-inserted-identity-of-record/

【讨论】:

这必须放在应用程序代码中,在触发器之后。但是我只能访问触发器,所以我需要停止触发器实际更改 scope_identity。 如果您有多个客户端同时使用IDENT_CURRENT() 添加行不是一个好主意。您可能会得到另一个客户端生成的身份值。

以上是关于停止触发器更改 scope_identity的主要内容,如果未能解决你的问题,请参考以下文章

而不是SQL Server中的触发器丢失SCOPE_IDENTITY?

select SCOPE_IDENTITY()用法

SCOPE_IDENTITY的用法

@@IDENTITY与SCOPE_IDENTITY的用法

如何使用数据触发器停止自定义情节提要?

如何得到SqlServer的自增ID