【问题标题】:Is is safe to use @@IDENTITY in a transaction?在事务中使用@@IDENTITY 是否安全?
【发布时间】:2016-09-08 19:52:22
【问题描述】:

我正在阅读this 的回答,了解有关将最后一个身份值输入数据库的不同方法。

据我了解,@@IDENTITY 通常是一个非常糟糕的主意,因为它可能会返回一个与您预期不同的身份,例如最近由扳机。

但是如果你的代码在一个事务中呢?

例如,这是我正在执行的事务的简化版本(使用 ColdFusion):

<cftransaction>
    <cfquery name="queryInsertA" datasource="source">
        INSERT INTO tableA (columnName) VALUES (value)
    </cfquery>
    <cfquery name="queryInsertB" datasource="source">
        INSERT INTO tableB (fkey_tableA, columnName) VALUES (@@IDENTITY, value)
    </cfquery>
</cftransaction>

既然,“If a transaction is successful, all of the data modifications made during the transaction are committed and become a permanent part of the database”,这是否意味着它也可以防止使用@@IDENTITY 时可能出现的问题?还是我误解了交易的行为?

【问题讨论】:

  • 我不会说这是 100 % 安全的,如果您在一个只有您可以访问的受控环境中执行此操作,那么是的,请继续。但是,如果它位于可以被不同的进程/用户/应用程序大量使用的系统/服务器上,那么不会。即使它代表 1% 的风险,我也不会承担。
  • 为什么要使用@@identity 而不是scope_identity()?顺便说一句,后者也是当您使用带有简单 INSERT 的“结果”属性时 CF 自动返回的内容。
  • 我主要是好奇@@identity 是否安全。 (看起来这是一个很大的不)。 scope_identity() 一路走好!
  • 嗯,肯定比@@identity 好。但是,如果您使用多处理器运行 2008 或更早版本,请务必阅读第一个链接的 cmets 中提到的并行计划错误。对于 2008 年,推荐的解决方法是使用 OUTPUT

标签: sql sql-server tsql coldfusion transactions


【解决方案1】:

您链接的答案已经解释了@@IDENTITY:范围的主要问题。如果你的插入触发了另一个插入,你会得到一个意想不到的身份。交易不会改变任何东西。

【讨论】:

  • 这是我的假设。很高兴知道交易无济于事,您应该始终只使用select scope_identity()(或获取最后一个身份值的其他更安全的方法之一)。
【解决方案2】:

如果您想获取插入到表中的最后一个标识值,请使用 Ident_current() 函数。

   Select ident_current ('your table name')

你也可以使用scope_identity(),它只会在那个特定的范围内带来一个表的标识值。

  Select scope_identity()

【讨论】:

  • @Ectropy - 因为您正在寻找刚刚插入的记录的 id,所以您肯定想要 scope_identity(),而不是 ident_current()。如上所述,后者将返回 any session and any scope 的最后一个 id - 这不是你想要的。
  • 是的,这些是获得身份的另一种更好的方法。
  • @Leigh 是的,听起来不错。如果其他人插入ident_current() 正在查看的表中,我可能会得到一个不正确的身份值。也不安全!
【解决方案3】:

您不需要@@Identity,也不需要2 个单独的查询。使用 Scope_identity() 函数来保证完整性,并使其成为同一连接和查询的一部分 - 就像这样。

<cfquery name="putUser" datasource="#dsn#">
SET NOCOUNT ON
INSERT INTO users(username, email)
VALUES 
('#usersname#','#email#' )
SELECT  SCOPE_IDENTITY() AS newId FROM users
SET NOCOUNT OFF 
</cfquery>

<cfoutput>#putUser.newID#</cfoutput>

这将是完全安全的,但就像 所有 db 事务一样,它仍然会出现死锁,因此调整仍然很重要。

CFTRANSACTION 适用于可能还涉及一些 CF 逻辑的多个数据库操作,但通过将数据库锁定和事务系统保持在一起,让它们为您工作。

【讨论】:

  • 这是有用的建议。我通常会根据SELECT/INSERT/UPDATE/DELETE 语句完成一个cfquerySELECT scope_identity() 的别名也很有用,因为它可以很容易地在另一个 cfquery 中使用身份。
【解决方案4】:

您还可以使用cfqueryresult 属性。如果查询对 ID 执行标识或自增值的 INSERT,则会在结构中返回一个名为 GENERATEDKEY 的键。

<cftransaction>
    <cfquery name="queryInsertA" datasource="source" result="resultA">
        INSERT INTO tableA (columnName) VALUES (value)
    </cfquery>
    <cfquery name="queryInsertB" datasource="source">
        INSERT INTO tableB (fkey_tableA, columnName) VALUES (#resultA.generatedKey#, value)
    </cfquery>
</cftransaction>

请记住,这只是 CF9 及更高版本。

【讨论】:

  • 好的,所以如果你用ColdFusion方式,通过结果的generatedKey也不会选择错误的身份值吗?对于使用 ColdFusion 服务器的人来说,这似乎是一个不错的选择。
  • @Ectropy - 不确定您是否看到早期的 cmets,但 CF 的“result”属性通过调用 scope_identity() 获取 ID。因此,适用于scope_identity() 的任何规则也应该适用于 result.generatedKey 属性。 (注意,仅适用于单条记录插入)。
  • 知道有用。所以在某些情况下,OUTPUT 可能是更好的选择。
【解决方案5】:

您可以使用序列并在插入期间使用它,如下所示:

CREATE SEQUENCE Testseq  
    START WITH 1  
    INCREMENT BY 1 ;

使用以下查询访问序列:

SELECT NEXT VALUE FOR Testseq;

【讨论】:

  • 很有趣,但是由于某些要求,这些特定的数据库表必须使用 int identity(1,1)。看起来这是创建递增值但不使用身份的建议?
  • 是的,序列是“某种”身份的替代品。在某些意义上类似,但更灵活一些(尽管有点 Oracle 式的 IMO ;-)。它们于 2012 年推出。Brief Summary of SequencesNEXT VALUE FOR (Transact-SQL)
【解决方案6】:

为了简单:

如果

您知道您在 db 系统中独自一人,这意味着,没有其他用户或进程同时运行,没有其他事务在运行,在您使用它时绝对有零活动,我的意思是它,零活动,然后,好的......

否则

不!如果在您的事务运行时确实发生了我上面列出的任何事情,那么您最终会得到错误的身份。

【讨论】:

  • 并不孤单!这是服务器中的狂野西部。由于事务不会提供任何保护,@@IDENTITY 使用起来并不安全。
【解决方案7】:

这取决于在您的事务被实例化的同时还有什么正在运行。如果在与事务无关的表上存在可以插入新标识值的触发器,那么您当前所在的事务范围将不会保护您。

例如,假设我创建了一个更新 Table_A 并在其中插入一条记录的 SPROC。该表上有一个标识字段,每次插入新记录时都会增加该表中的 ID 值。在我的 SPROC 中,我创建了一个事务并将我的插入插入到事务中。插入后,我将@@IDENTITY 的值存储在同一事务内的变量中。

现在我还有另一个表 Table_B 有它自己的标识值,但这个表是触发器维护的。如果我正在执行我的 SPROC 以在 Table_A 中插入一行,并且在此更新期间 Table_B 也通过触发器更新,那么当我检索 @@IDENTITY 的值时,它实际上会给我创建的 ID 的值Table_B 而不是 Table_A。

您绝对应该在存储过程中使用事务,但最好选择插入的表的 MAX(ID) 来检索您创建的 ID,而不是 @@IDENTITY。

【讨论】:

  • 你最好选择 MAX(ID) 不,不要使用 MAX。该方法也不是线程安全的(在默认事务级别下)。
  • 同意 - 这不是一个密封的解决方案,但是在使用 Identity 时我还没有找到这个问题的完美答案。你有其他选择吗?
  • 是的,这个线程中提到的那些:scope_identity()OUTPUT(请参阅关于 2008 年错误的注释)。虽然我不推荐@@identity 或select max(id),但后者更不安全。至少@@identity 仅限于当前会话。而select max(id) 是服务器范围的。在默认事务级别下,绝对没有什么可以阻止其他线程获取相同的值。因此,在繁忙的服务器上,您很可能会得到一个完全错误的 ID。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-09-09
  • 1970-01-01
  • 2012-10-09
  • 2010-12-29
  • 1970-01-01
相关资源
最近更新 更多