在SQL Server中删除记录后重置身份种子


680

我已将记录插入到SQL Server数据库表中。该表已定义了主键,并且自动增量标识种子设置为“是”。这样做主要是因为在SQL Azure中,每个表都必须定义一个主键和标识。

但是由于我必须从表中删除一些记录,所以这些表的标识种子将受到干扰,索引列(索引列(以1为增量自动生成)也将受到干扰)。

删除记录后如何重置标识列,以便该列按升序排列?

标识列不用作数据库中任何地方的外键。


4
“在SQL Azure中”-“每个表必须有一个主键”-true-“和定义的身份”-false。身份和主键是正交的概念。标识列不必是表的PK。主键不必是标识列。
Damien_The_Unbeliever 2014年

好。我的概念可能是错误的。但是现在我已经用PK和Identity Seed定义了表结构。如果必须删除一些行,如何以正确的数字升序重置身份种子
xorpower 2014年

29
我总是会争辩说,如果您关心在Identity列中生成的实际数值,则会滥用它们。标识列只需要关心的是它会自动生成唯一值(是!),并且可以将这些值存储在数值列中(此位仅与声明保留这些值的列有关)。您不应该向任何人展示它们,因此他们采用什么价值观都没有关系。
Damien_The_Unbeliever 2014年

您可以使用dbcc check
id

Answers:


1097

DBCC CHECKIDENT管理命令用来复位身份计数器。命令语法为:

DBCC CHECKIDENT (table_name [, { NORESEED | { RESEED [, new_reseed_value ]}}])
[ WITH NO_INFOMSGS ]

例:

DBCC CHECKIDENT ('[TestTable]', RESEED, 0);
GO

在早期版本的Azure SQL数据库中不支持此功能,但现在支持。


请注意,根据文档new_reseed_value参数在SQL Server版本之间有所不同:

如果表中存在行,则将下一行插入new_reseed_value值。在版本SQL Server 2008 R2和更早版本中,插入的下一行使用new_reseed_value +当前增量值。

但是,我发现此信息具有误导性(实际上是完全错误的),因为观察到的行为表明至少SQL Server 2012仍在使用new_reseed_value +当前的增量值逻辑。微软甚至与自己Example C在同一页面上发现的矛盾:

C.将当前身份值强制为新值

下面的示例将AddressType表的AddressTypeID列中的当前标识值强制为10。由于该表具有现有行,因此插入的下一行将使用11作为值,即为该值定义的新的当前增量值。列值加1。

USE AdventureWorks2012;  
GO  
DBCC CHECKIDENT ('Person.AddressType', RESEED, 10);  
GO

尽管如此,这一切都为较新的SQL Server版本留下了不同行为的选择。我猜想,直到Microsoft清除其自己的文档中的内容之前,唯一可以确定的方法就是在使用前进行实际测试。


23
语法将... DBCC CHECKIDENT( '[TestTable的]',RESEED,0)GO
Biki

2
似乎DBCC CHECKIDENT在即将发布的版本(V12 / Sterling)中受支持:azure.microsoft.com/zh-cn/documentation/articles/…虽然,对于这种特殊情况,我仍然建议TRUNCATE TABLE :)
Solomon Rutzky

1
直到“ GO”出现在另一行之前,它对我才起作用。
mrówa

1
由于同一行中的GO关键字,语法被标记出来,我不知道为什么。您可以将其向下移动一行吗?我将该行复制并粘贴了50次,现在我必须回去修复它。
AnotherDeveloper 2016年

4
为我完美地工作。值得指出的是,当重新播种的表,如果你想补种,使您的第一个记录是ID为1,则补种命令必须补种为0,从而使下一个记录是ID 1
迈克·普强公司

215
DBCC CHECKIDENT ('TestTable', RESEED, 0)
GO

其中0是identity起始值


15
如果表为空,例如您刚刚调用TRUNCATE,则新的种子值应为下次使用的值(即1而不是0)。如果表不为空,它将使用new_reseed_value + 1MSDN
kjbartel

2
@kjbartel,Anil等:它不像“如果表为空”那么简单。该文档缺少表为空的情况,原因DELETE不是TRUNCATE,在这种情况下也是如此new_reseed+value + 1。我写了一篇关于此的文章,通过一些测试显示了实际的行为,并更新了实际的文档(现在由于在GitHub上而已,我们可以这样做):重置身份种子(RESEED)时,DBCC CHECKIDENT如何真正起作用?
所罗门·鲁兹基

87

应该注意的是,如果所有数据都是通过DELETE(即no WHERE子句)从表中删除的,则只要a)许可允许它,并且b)没有引用该表的FK(似乎是(在这种情况下),则TRUNCATE TABLE首选使用,因为这样做的效率更高,DELETE 并且可以同时重置IDENTITY种子。以下详细信息来自MSDN页面的TRUNCATE TABLE

与DELETE语句相比,TRUNCATE TABLE具有以下优点:

  • 使用较少的事务日志空间。

    DELETE语句一次删除一行,并在事务日志中为每个删除的行记录一个条目。TRUNCATE TABLE通过取消分配用于存储表数据的数据页来删除数据,并仅在事务日志中记录页面的解除分配。

  • 通常使用较少的锁。

    使用行锁执行DELETE语句时,表中的每一行都被锁定以删除。TRUNCATE TABLE始终锁定表(包括模式(SCH-M)锁)和页面,但不锁定每一行。

  • 毫无例外,表中保留了零页。

    执行DELETE语句后,该表仍可以包含空页。例如,如果没有至少排他(LCK_M_X)表锁,则无法释放堆中的空页。如果删除操作不使用表锁,则表(堆)将包含许多空页。对于索引,删除操作可以留下空白页,尽管这些页将通过后台清理过程快速释放。

如果表包含一个标识列,则该列的计数器将重置为该列定义的种子值。如果未定义种子,则使用默认值1。要保留身份计数器,请改用DELETE。

因此,以下内容:

DELETE FROM [MyTable];
DBCC CHECKIDENT ('[MyTable]', RESEED, 0);

变成:

TRUNCATE TABLE [MyTable];

请参阅TRUNCATE TABLE文档(上面链接)以获取有关限制等的更多信息。


8
虽然在正确的情况下效率更高,但这并不总是一种选择。截断将不会在为其定义了FK的表上执行。即使没有依赖记录,如果存在约束,截断也会失败。另外,截断还需要ALTER权限,而Delete只需要DELETE。
Rozwel

3
@Rozwel是的,但是我已经对我的回答进行了限定,指出需要适当的权限。另外,问题特别指出没有FK。但是,为清楚起见,我更新为指定“无FK”限制。感谢您指出了这一点。
所罗门·鲁兹基

1
唯一的疑问是任何FK都会阻止截断。有可能(尽管不常见)针对不属于PK或标识列的一部分的唯一约束具有FK。
Rozwel 2015年

1
@Rozwel再次成立,但是从问题上假设没有唯一的约束似乎是合理的,因为PK仅由于OP对Azure SQL数据库所需的理解(正确与否)而存在。无论如何,我都是为了减少歧义,因此我再次进行了更新。谢谢。
所罗门·鲁兹基

在表上具有外键并不是所有的异常,并且任何外键的存在都禁止TRUNCATE TABLE。今天早些时候,当我尝试在具有外键的表上运行TRUNCATE TABLE时,才发现这很困难,该外键是针对表中的其他两个列以及外表中的唯一索引执行的。
戴维·格雷

83

尽管大多数答案都建议将RESEED设置为0,但是很多时候我们只需要重新设置下一个可用的ID

declare @max int
select @max=max([Id])from [TestTable]
if @max IS NULL   //check when max is returned as null
  SET @max = 0
DBCC CHECKIDENT ('[TestTable]', RESEED,@max)

这将检查表并重置为下一个ID。


2
这是唯一一种在100%的时间内有效的答案
逆向工程师,

3
短一点:declare @max int select @max=ISNULL(max([Id]),0) from [TestTable]; DBCC CHECKIDENT ('[TestTable]', RESEED, @max );
吉列尔莫·普兰迪


16

尽管大多数答案都建议RESEED使用0,尽管有些人将其视为TRUNCATED表的缺陷,但Microsoft的解决方案不包括ID

DBCC CHECKIDENT ('[TestTable]', RESEED)

这将检查表并重置到下一个表ID。自MS SQL 2005到当前,该功能已可用。

https://msdn.microsoft.com/zh-CN/library/ms176057.aspx


1
不幸的是那不是事实。刚刚检查了MS SQL 2014服务器。
alehro 2015年

1
实际上,对于SQL 2014来说确实如此。我刚刚对其进行了测试,并且它对我有用。
丹尼尔·戴森

2
这在SQL 2012上对我来说不一致,有时会像我期望的那样使用下一个可用的变量,有时似乎卡在了表中的旧值上。指定种子杂项工作。
丹·菲尔德

在SQL 2016上对我不起作用-它将身份种子保持原样。它可能一次对我来说是正确的,但也可能是我的手指麻烦。无法使其再次工作
逆向工程师,

该消息表示成功,Checking identity information: current identity value '[incorrect seed]', current column value '[correct seed]'.但在新插入时它仍使用不正确的种子。
Denziloe

7

发出2条命令可以解决问题

DBCC CHECKIDENT ('[TestTable]', RESEED,0)
DBCC CHECKIDENT ('[TestTable]', RESEED)

第一个将标识重置为零,下一个将其设置为下一个可用值-jacob


2
DBCC CHECKIDENT('[TestTable]',RESEED)没有重新播种到下一个可用值
Atal Kishore

这是启用 “ Reseed标识列”选项时RedGate Data Compare使用的方法。我已经对其进行了广泛的测试(我的意思是在SQL代码中,而不是在RedGate工具中),并且它工作可靠。(除了偶尔使用RedGate,我与RedGate没有任何关系)
反向工程师

6

@雅各布

DBCC CHECKIDENT ('[TestTable]', RESEED,0)
DBCC CHECKIDENT ('[TestTable]', RESEED)

为我工作,我只需要首先从表中清除所有条目,然后在删除后在触发点中添加以上内容。现在,每当我删除条目时,都会从那里开始。


DBCC CHECKIDENT仅在删除后起作用。您最好使用truncate。但是,如果您需要其余数据,请不要使用它。另外,truncate不会提供已删除记录的记录计数。
user763539

6

Truncate 首选table,因为它会清除记录,重置计数器并回收磁盘空间。

Delete并且CheckIdent仅应在外键阻止您截断的地方使用。



4

这是一个常见的问题,答案始终是相同的:不要这样做。标识值应视为任意值,因此,没有“正确”顺序。


15
这对于生产环境是正确的,但是在开发过程中,我想记住某些实体具有特定的ID,这些ID是从种子脚本填充的。在开发过程中,它使浏览数据库变得更加容易。
弗朗索瓦·博塔2014年

7
这样的回答完全是理论上的,很少符合现实世界的需求。您可以回答OP问题,而不是用教条洗人吗?
Serj Sagan

1
有意思,兄弟。我的争辩是:如果要为列指定值,请不要在列上选择一个属性,以免这样做。代码的味道是这样的:如果每次将记录插入表中时都为identity列指定一个值,那么就没有Identity列。身份的全部要点是让服务器为您创建一个值。因此,如果您每次都覆盖该时间,那么您会以非零成本获得任何收益。同样,在adhominem论点上做得很好。
本图尔2015年

5
我当然同意你的观点。从票面价值来看,OP当然是在做错事,但是在帖子中可能没有更深的需要,即OP认为与回答他的问题无关。因此,回答问题,并给出“该做与不该做”的建议作为答案的一部分。顺便说一句,我从来没有攻击过你的角色...愚蠢的意思是我称你为愚蠢之类...
Serj Sagan

1
尽管在大多数情况下当然是正确的,但在某些情况下重新种子表是合法的。例如,我正在开发一个未开发的项目,该项目必须从一定的角度出发,以考虑要替换的前任中的现有行。在开发过程中进行播种是一个合法的用例,IMO。
大卫·A·格雷

3

运行此脚本以重置标识列。您将需要进行两项更改。将tableXYZ替换为您需要更新的任何表。同样,标识列的名称需要从临时表中删除。这在具有35,000行和3列的表上是瞬时的。显然,备份表并首先在测试环境中尝试。


select * 
into #temp
From tableXYZ

set identity_insert tableXYZ ON

truncate table tableXYZ

alter table #temp drop column (nameOfIdentityColumn)

set identity_insert tableXYZ OFF

insert into tableXYZ
select * from #temp

3
这并不完全正确:SET IDENTITY_INSERT放在错误的位置。它不在TRUNCATE周围,而是在INSERT INTO周围(因此identity_ INSERT)。另外,在需要保留数据时才使用此方法,否则与仅运行单个TRUNCATE语句相比,效率非常低。
所罗门·鲁兹基2014年

1
DBCC CHECKIDENT (<TableName>, reseed, 0)

这会将当前标识值设置为0。

插入下一个值时,标识值将增加为1。


1

使用此存储过程:

IF (object_id('[dbo].[pResetIdentityField]') IS NULL)
  BEGIN
    EXEC('CREATE PROCEDURE [dbo].[pResetIdentityField] AS SELECT 1 FROM DUMMY');
  END
GO

SET  ANSI_NULLS ON
GO
SET  QUOTED_IDENTIFIER ON
GO

ALTER PROCEDURE [dbo].[pResetIdentityField]
  @pSchemaName NVARCHAR(1000)
, @pTableName NVARCHAR(1000) AS
DECLARE @max   INT;
DECLARE @fullTableName   NVARCHAR(2000) = @pSchemaName + '.' + @pTableName;

DECLARE @identityColumn   NVARCHAR(1000);

SELECT @identityColumn = c.[name]
FROM sys.tables t
     INNER JOIN sys.schemas s ON t.[schema_id] = s.[schema_id]
     INNER JOIN sys.columns c ON c.[object_id] = t.[object_id]
WHERE     c.is_identity = 1
      AND t.name = @pTableName
      AND s.[name] = @pSchemaName

IF @identityColumn IS NULL
  BEGIN
    RAISERROR(
      'One of the following is true: 1. the table you specified doesn''t have an identity field, 2. you specified an invalid schema, 3. you specified an invalid table'
    , 16
    , 1);
    RETURN;
  END;

DECLARE @sqlString   NVARCHAR(MAX) = N'SELECT @maxOut = max(' + @identityColumn + ') FROM ' + @fullTableName;

EXECUTE sp_executesql @stmt = @sqlString, @params = N'@maxOut int OUTPUT', @maxOut = @max OUTPUT

IF @max IS NULL
  SET @max = 0

print(@max)

DBCC CHECKIDENT (@fullTableName, RESEED, @max)
go

--exec pResetIdentityField 'dbo', 'Table'

只是重新审视我的答案。我在sql server 2008 r2中遇到了一个奇怪的行为,您应该意识到。

drop table test01

create table test01 (Id int identity(1,1), descr nvarchar(10))

execute pResetIdentityField 'dbo', 'test01'

insert into test01 (descr) values('Item 1')

select * from test01

delete from test01

execute pResetIdentityField 'dbo', 'test01'

insert into test01 (descr) values('Item 1')

select * from test01

第一个选择产生0, Item 1

第二个产生1, Item 1。如果在创建表后立即执行重置,则下一个值为0。老实说,我并不惊讶Microsoft无法正确处理此问题。我发现它是因为我有一个脚本文件来填充参考表,这些参考表有时在重新创建表之后运行,有时在表已经创建时运行。


1

我使用以下脚本来执行此操作。只有一种情况会产生“错误”,即如果您已删除表中的所有行,并且IDENT_CURRENT当前设置为1,即表中只有一行开始。

DECLARE @maxID int = (SELECT MAX(ID) FROM dbo.Tbl)
;

IF @maxID IS NULL
    IF (SELECT IDENT_CURRENT('dbo.Tbl')) > 1
        DBCC CHECKIDENT ('dbo.Tbl', RESEED, 0)
    ELSE
        DBCC CHECKIDENT ('dbo.Tbl', RESEED, 1)
    ;
ELSE
    DBCC CHECKIDENT ('dbo.Tbl', RESEED, @maxID)
;

0

对于完整的DELETE行并重置IDENTITY计数,我使用此方法(SQL Server 2008 R2)

USE mydb

-- ##################################################################################################################
-- DANGEROUS!!!! USE WITH CARE
-- ##################################################################################################################

DECLARE
  db_cursor CURSOR FOR
    SELECT TABLE_NAME
      FROM INFORMATION_SCHEMA.TABLES
     WHERE TABLE_TYPE = 'BASE TABLE'
       AND TABLE_CATALOG = 'mydb'

DECLARE @tblname VARCHAR(50)
SET @tblname = ''

OPEN db_cursor
FETCH NEXT FROM db_cursor INTO @tblname

WHILE @@FETCH_STATUS = 0
BEGIN
  IF CHARINDEX('mycommonwordforalltablesIwanttodothisto', @tblname) > 0
    BEGIN
      EXEC('DELETE FROM ' + @tblname)
      DBCC CHECKIDENT (@tblname, RESEED, 0)
    END

  FETCH NEXT FROM db_cursor INTO @tblname
END

CLOSE db_cursor
DEALLOCATE db_cursor
GO

0

除非您要清理整个表,否则将种子重新设置为0不太实用。

在其他方面,Anthony Raymond给出的答案是完美的。首先获取最大标识列,然后使用最大种子。


0

我一直在尝试在开发过程中为大量表完成此操作,这很吸引人。

DBCC CHECKIDENT('www.newsType', RESEED, 1);
DBCC CHECKIDENT('www.newsType', RESEED);

因此,首先将其设置为1,然后将其设置为表中存在的行的最高索引。快速简便的idex休息。


-2

尽可能最好使用 TRUNCATE而不是删除所有记录,因为它也不使用日志空间。

万一我们需要删除并需要重置种子,请记住,如果从未填充过表,而您使用过该表,DBCC CHECKIDENT('tablenem',RESEED,0) 则第一条记录将获得身份= 0,如 msdn文档中所述

在您的情况下,仅重建索引,不必担心会丢失一系列标识,因为这是常见的情况。


3
在我看来,这个想法是只删除一些记录。
Drumbeg '16

6
这完全是错误的-使用truncate并非总是更好,实际上,仅在某些非常有限的特定情况下更好。天堂禁止有人遵循您的建议,然后需要回滚。
Thronk '16

1
@Thronk为什么要暗示那TRUNCATE会阻止ROLLBACK预期的行为?ROLLBACK仍会回滚。即使DB设置为BULK_LOGGED
所罗门·鲁兹基

2
TRUNCATE是DDL操作,它没有记录在日志文件中。除非它是交易的一部分(问题或此答案中的任何地方均未提及)。每当有人说某事总是对的时,就可以肯定他们错了。
Thronk

这是唯一一个注意到RESEED行为有所不同的答案,具体取决于之前是否使用过该序列。在多个空白表中填充了相同值的种子(先前已填充了一些表)将为插入每个表中的第一条记录产生不同的初始值。
simon coleman

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.