获取表中最后一个身份的最佳方法


35

哪一个是获取我刚刚通过插入生成的标识值的最佳选择?这些陈述对绩效有何影响?

  1. SCOPE_IDENTITY()
  2. 汇总功能 MAX()
  3. TOP 1从TableName中选择IdentityColumnORDER BY IdentityColumn DESC

1
使用postgreSQL,您可以从书架上获取它postgresql.org/docs/9.1/static/sql-insert.html
Yevgeniy Afanasyev,

Leftfield选项-如果表中有一个Guid列,并且可以生成一个新的Guid并将其插入到插入过程中的新列中,则可以选择带有该Guid的行以提取生成的int标识。
niico

Answers:


56

使用SCOPE_IDENTITY()如果您插入单行并希望检索生成的ID。

CREATE TABLE #a(identity_column INT IDENTITY(1,1), x CHAR(1));

INSERT #a(x) VALUES('a');

SELECT SCOPE_IDENTITY();

结果:

----
1

OUTPUT如果要插入多行并且需要检索生成的ID 请使用该子句

INSERT #a(x) 
  OUTPUT inserted.identity_column 
  VALUES('b'),('c');

结果:

----
2
3

为什么这是最好的更快的选择?

除了性能,这些是在默认隔离级别和/或有多个用户的情况下唯一可以保证正确的方法。即使您忽略了正确性方面,SQL Server SCOPE_IDENTITY()也会将插入的值保存在内存中,因此自然地,这比对表或对系统表进行单独的查询要快。

无视正确性就像是告诉邮递员他很好地完成了今天的邮件-他完成路线的速度比平均时间快10分钟,问题是,没有邮件被传递到正确的地方。

请勿使用以下任何一种:

  • @@IDENTITY -由于不能在所有情况下都使用它,例如,当带有标识列的表具有触发器,并且该触发器也插入具有自己标识列的另一个表时,您将获得错误的值。
  • IDENT_CURRENT()-我在这里对此进行了详细介绍,评论也很有用,但是从本质上讲,在并发的情况下,您常常会得到错误的答案。
  • MAX()TOP 1-您必须使用可序列化的隔离保护这两个语句,以确保MAX()您得到的不是别人的。这比仅使用昂贵得多SCOPE_IDENTITY()

每当您插入两行或更多行,并且需要生成所有标识值时,这些功能也会失败-唯一的选择是使用OUTPUT子句。


我的记忆中又触发了一个问题。每当我们需要获取任何会话或用户在特定表中生成的最后一个标识时,唯一正确且最佳的方法就是该列的MAX()?
AA.SC 2015年

当我在表中插入多行时,SCOPE_IDENTITY()将始终返回最后一次生成的标识怎么办?如果列是主键而不是身份列怎么办?
AA.SC 2015年

@ AA.SC是的,它将返回最后一个。如果不是标识列,则否,这些功能都不起作用。在这种情况下,PK值从何而来?
亚伦·伯特兰

我已经在我们的应用程序的列中看到了这种情况,其中该列是INT类型,并且开发人员在需要插入新记录的时候使用MAX(列名)+1
AA.SC 2015年

然后他们已经知道他们刚刚插入了什么值。SQL Server没有任何办法告诉您(事实发生后,您将不能再依赖于拉MAX,除非您完全隔离了整个事务,这对性能或并发性都不利)。
亚伦·伯特兰

7

除了性能,它们都有不同的含义。

SCOPE_IDENTITY()将为您提供直接插入当前范围内的任何表中的最后一个标识值(范围=批处理,存储过程等,但不在当前范围内触发的触发器之内)。

IDENT_CURRENT()将为您提供任何用户从任何范围插入特定表的最后一个身份值。

@@IDENTITY为您提供由最新INSERT语句为当前连接生成的最后一个标识值,而不管表或作用域如何。(附带说明:Access使用此功能,因此触发器在将值插入具有标识列的表中时会遇到一些问题。)

如果表的标识步骤是否定的,或者正在插入行,则使用MAX()TOP 1会给您完全错误的结果SET IDENTITY_INSERT。这是演示所有这些的脚本:

CREATE TABLE ReverseIdent (
    id int IDENTITY(9000,-1) NOT NULL PRIMARY KEY CLUSTERED,
    data char(4)
)

INSERT INTO ReverseIdent (data)
VALUES ('a'), ('b'), ('c')

SELECT * FROM ReverseIdent

SELECT IDENT_CURRENT('ReverseIdent') --8998
SELECT MAX(id) FROM ReverseIdent --9000

SET IDENTITY_INSERT ReverseIdent ON

INSERT INTO ReverseIdent (id, data)
VALUES (9005, 'd')

SET IDENTITY_INSERT ReverseIdent OFF

SELECT IDENT_CURRENT('ReverseIdent') --8998
SELECT MAX(id) FROM ReverseIdent --9005

摘要:坚持使用SCOPE_IDENTITY()IDENT_CURRENT()@@IDENTITY,并确保您使用的是可以返回实际需要的内容。


1
你为什么鼓励使用IDENT_CURRENT()@@IDENTITY当你自己的脚本演示了他们的输出不正确的结果?
亚伦·伯特兰

1
@AaronBertrand我不确定我是否遵循。最后生成的标识值是8998(注意步骤是-1),这就是IDENT_CURRENT()返回的值。MAX()永远不会返回超出第一行的正确值,因为id会向后计数,并且IDENTITY_INSERTon表示9005不是生成的标识值,因此不会被反映IDENT_CURRENT()。但是,如果您真正追求的是返回结果,它可能会返回“不正确”的结果SCOPE_IDENTITY()。选择适合该工作的工具。
db2

OP似乎在他们插入的标识值之后-在这种情况下8998不正确。我认为您提到的边缘情况(向后递增和IDENTITY_INSERT开启)甚至进一步反对使用IDENT_CURRENT,并且@@ IDENTITY绝对不应该使用,因为存在触发危险(现在或以后添加)。我仍在努力理解为什么IDENT_CURRENT将成为OP想要使用的(特别是在并发状态下),或者为什么在存在更可靠的方法时任何人都将使用@@ IDENTITY的原因。
亚伦·伯特兰

@AaronBertrand这个问题尚不能100%明确,期望的结果是当前作用域中的最后一个插入项(在这方面,选项1与2和3不同),所以我认为描述这两个以及它们的方式是一个好主意。不同。但是我确实同意,这@@IDENTITY几乎从来不是获取生成的身份值的理想方法。要点是,MAX()或者说TOP 1像是不太可靠的IDENT_CURRENT(),如果您了解它的功能,则可以使用它。可能对维护工作或其他有用。
db2
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.