SQL Server解惑——标识列的限制和跳号现象
1:每个表只能创建一个标识列。
如下测试所示,如果表中有一个标识列,新增一个标识列就会遇到错误“Multiple identity columns specified for table 'TEST'. Only one identity column per table is allowed.“
CREATE TABLE dbo.TEST
(
ID INT IDENTITY(1,1) ,
NAME VARCHAR(32)
);
ALTER TABLE dbo.TEST ADD ID1 INT IDENTITY(10,1)

2:标识列不能被更新。
如果你更新标识列,就会遇到类似下面这样的错误。
Cannot update identity column 'xxx'.
3:SQL Server不能通过ALTER语句修改标识列的increment值大小。
如果非要调整标识列的increment值大小,只能通过重建表来实现。如果想通过增加列或删除列的方法,非常麻烦。很多情况下也是不行的。例如,有些情况下需要你对新增的自增标识列更新数据才能保证数据一致性。还有一种非常规方法就是修改系统基表sys.syscolpars。这个后续整理一篇。
4:SQL Server不能通过ALTER语句修改表标识列的SEED的大小。但是可以DBCC CHECKIDENT命令调整。SEED可以调大也可以调小,但是有一些限制!
#查看某个表中的自增列当前的值:
DBCC CHECKIDENT (TableName,NORESEED)
#调整标识列的当前值(SEED)为50
DBCC CHECKIDENT('dbo.TEST', RESEED, 50);
通过DBCC CHECKIDENT命令调整SEED值大小,也是有限制的,如下实验所示:
USE AdventureWorks2014;
GO
IF EXISTS (SELECT 1 FROM sys.objects WHERE type='U' AND name='TEST')
BEGIN
DROP TABLE test;
END
GO
CREATE TABLE dbo.TEST
(
ID INT IDENTITY(1,1) ,
NAME VARCHAR(32)
);
INSERT INTO dbo.TEST
( NAME )
SELECT 'K1' UNION ALL
SELECT 'K2' UNION ALL
SELECT 'K3' UNION ALL
SELECT 'K4' UNION ALL
SELECT 'K5' UNION ALL
SELECT 'K6';
SET IDENTITY_INSERT dbo.TEST ON;
GO
INSERT INTO dbo.TEST
( ID, NAME )
SELECT 13, 'k13';
GO
SET IDENTITY_INSERT dbo.TEST OFF;
GO
DBCC CHECKIDENT(test)
DBCC CHECKIDENT('test', RESEED ,9);
INSERT INTO dbo.TEST
( NAME )
SELECT 'K9' UNION ALL
SELECT 'K10' UNION ALL
SELECT 'K11' UNION ALL
SELECT 'K12' UNION ALL
SELECT 'K13' ;
SELECT * FROM dbo.TEST;

如果你修改一下表结构,标识列为主键或有唯一约束的话,
CREATE TABLE dbo.TEST
(
ID INT IDENTITY(1,1) PRIMARY KEY,
NAME VARCHAR(32)
);
那么上面脚本运行到插入数据时就会报主键冲突。错误如下所示:
Msg 2627, Level 14, State 1, Line 38
Violation of PRIMARY KEY constraint 'PK__TEST__3214EC2731C41DF1'. Cannot insert duplicate key in object 'dbo.TEST'. The duplicate key value is (13).
那么接下来,我们将上面的脚本稍微调整一下,你会看到完全不同的结果。如下所示:
USE AdventureWorks2014;
GO
IF EXISTS (SELECT 1 FROM sys.objects WHERE type='U' AND name='TEST')
BEGIN
DROP TABLE test;
END
GO
CREATE TABLE dbo.TEST
(
ID INT IDENTITY(1,1) ,
NAME VARCHAR(32)
);
INSERT INTO dbo.TEST
( NAME )
SELECT 'K1' UNION ALL
SELECT 'K2' UNION ALL
SELECT 'K3' UNION ALL
SELECT 'K4' UNION ALL
SELECT 'K5' UNION ALL
SELECT 'K6';
SET IDENTITY_INSERT dbo.TEST ON;
GO
INSERT INTO dbo.TEST
( ID, NAME )
SELECT 13, 'k13';
GO
SET IDENTITY_INSERT dbo.TEST OFF;
GO
DBCC CHECKIDENT('test', RESEED ,9);
GO
DBCC CHECKIDENT(test);
GO
INSERT INTO dbo.TEST
( NAME )
SELECT 'K9' UNION ALL
SELECT 'K10' UNION ALL
SELECT 'K11' UNION ALL
SELECT 'K12' UNION ALL
SELECT 'K13' ;
SELECT * FROM dbo.TEST;

这个是实验测试时意外发现的一个问题,当时,它导致我得出不同的实验结果,结论也搞错了,问题出在DBCC CHECKIDENT (table_name),如果表的当前标识值小于标识列中存储的最大标识值,则使用标识列中的最大值对其进行重置。我使用DBCC CHECKIDENT(test)本意是来查看标识列的当前值,所以正确的做法应该用DBCC CHECKIDENT(test, NORESEED)这条命令。其实这里也衍生了一个问题,由于可以人为调整SEED的值,所以标识列的值的唯一性,必须通过“PRIMARY KEY”或“UNIQUE”约束或者通过“UNIQUE”索引来实现。将字段设置为标识列并不能保证值的唯一值。
4: 不能通过ALTER语句将已经存在的一个字段改为标识列
CREATE TABLE dbo.TEST
(
ID INT ,
NAME VARCHAR(32)
);
--这种语法是不允许的
ALTER TABLE dbo.TEST ALTER COLUMN ID IDENTITY(10,1)
5:在内存优化表中,种子和增量必须分别设置为 1、1。 将种子或增量设置为 1 以外的值会导致以下错误:内存优化表不支持使用 1 以外的种子和增量值。另外,必须同时指定种子和增量,或者二者都不指定。 如果二者都未指定,则取默认值 (1,1)
6:如果事务回滚会导致标识列跳号。如下实验所示,这种现象和Oracle、MySQL数据库的行为一致。
--事务回滚导致标识列自增跳号
INSERT INTO dbo.TEST
( NAME )
SELECT 'K1' UNION ALL
SELECT 'K2' UNION ALL
SELECT 'K3' UNION ALL
SELECT 'K4' UNION ALL
SELECT 'K5' UNION ALL
SELECT 'K6';
BEGIN TRAN
INSERT INTO dbo.TEST
( NAME )
SELECT 'K7';
ROLLBACK TRAN;
INSERT INTO dbo.TEST
( NAME )
SELECT 'KKK';
SELECT * FROM dbo.TEST;
7: 事务内部,可能出现标识列的跳号。
如下实验所示:
USE AdventureWorks2014;
GO
IF EXISTS (SELECT 1 FROM sys.objects WHERE type='U' AND name='TEST_TRAN')
BEGIN
DROP TABLE TEST_TRAN;
END
GO
CREATE TABLE dbo.TEST_TRAN
(
ID INT IDENTITY(1,1) PRIMARY KEY,
TRN_NAME VARCHAR(32)
);
在会话1和会话2同时执行下面SQL语句,模拟并发的事务。
--会话1:
DECLARE @row_index INT;
SET @row_index =1;
BEGIN TRAN
WHILE @row_index <=10
BEGIN
INSERT INTO TEST_TRAN
VALUES('TRANS_1');
SET @row_index +=1;
WAITFOR DELAY '00:00:01';
END
COMMIT TRAN;
--会话2
DECLARE @row_index INT;
SET @row_index =1;
BEGIN TRAN
WHILE @row_index <=10
BEGIN
INSERT INTO TEST_TRAN
VALUES('TRANS_2');
SET @row_index +=1;
WAITFOR DELAY '00:00:01';
END
COMMIT TRAN;
执行完上面脚本后,我们可以看到在并发情况下,同一事务内可能出现跳号。这个可以称其为“逻辑跳号”

7:数据库实例非正常重启(崩溃,故障转移或关闭而导致SQL Server服务意外重启),出现标识列的跳号
关于这个,官方文档有简单介绍。
Consecutive values after server restart or other failures -SQL Server might cache identity values for performance reasons and some of the assigned values can be lost during a database failure or server restart. This can result in gaps in the identity value upon insert. If gaps are not acceptable then the application should use its own mechanism to generate key values. Using a sequence generator with the NOCACHE option can limit the gaps to transactions that are never committed.
个人简单测试了一下,发现在SQL Server 2012在服务器非正常重启(崩溃,故障转移或关闭而导致SQL Server服务意外重启)后会出现跳号(identity column jump)情况。可以通过启用踪标志272解决这个问题(参考下面链接),SQL Server 2014下测试时,也是如此。注意:如果正常重启SQL Server实例,并不会出现这种情况。这个跟ORACLE SEQUENCE跳号总结中的情况有点类似。
https://www.dfarber.com/computer-consulting-blog/articles/how-to-solve-identity-problem-in-sql-2012/
https://blog.sqlauthority.com/2017/03/24/sql-server-jump-identity-column-restart/
https://blog.sqlauthority.com/2018/01/24/sql-server-identity-jumping-1000-identity_cache/
https://www.codeproject.com/Tips/668042/SQL-Server-2012-Auto-Identity-Column-Value-Jump-Is
个人测试,在任务管理器,杀掉SQL Server的进程后,发现标识列跳号的大小为1000,根据上面博客资料,标识列跳号的多少还跟标识列的数据类型有关。

不过在SQL Server 2017,引入了新特性IDENTITY_CACHE来解决这个问题!
按照网上搜索的资料来看,踪标志272让SQL Server使用以前的代码来实现标识列的功能。
That flag sets SQL 2012 back to the prior code for IDENTITY fields. However, unless you are actually running out of numbers, there is no reason to use that flag. IDENTITY fields are unique, not sequential. You probably need to rethink your method.
那么我们想搞清楚标识列的下一个值保存在哪里呢? SQL Server数据库有个系统视图sys.identity_columns可以查看某个表的标识列的当前值和下一个值。
SELECT name ,
is_identity ,
seed_value ,
increment_value ,
last_value
FROM sys.identity_columns
WHERE object_id = OBJECT_ID('TEST');
但是 sys.identity_columns是一个系统视图,它的数据来自sys.syscolpars,而视图的字段last_value的值是通过内置函数IdentityProperty计算出来的
SET QUOTED_IDENTIFIER ON
SET ANSI_NULLS ON
GO
CREATE VIEW sys.identity_columns AS
SELECT object_id = id,
name = name,
column_id = colid,
system_type_id = xtype,
user_type_id = utype,
max_length = length,
precision = prec,
scale = scale,
collation_name = convert(sysname,CollationPropertyFromId(collationid,'name')),
is_nullable = sysconv(bit, 1 - (status & 1)), -- CPM_NOTNULL
is_ansi_padded = sysconv(bit, status & 2), -- CPM_NOTRIM
is_rowguidcol = sysconv(bit, status & 8), -- CPM_ROWGUIDCOL
is_identity = sysconv(bit, status & 4), -- CPM_IDENTCOL
is_filestream = sysconv(bit, status & 32), -- CPM_FILESTREAM
is_replicated = sysconv(bit, status & 0x20000), -- CPM_REPLICAT
is_non_sql_subscribed = sysconv(bit, status & 0x40000), -- CPM_NONSQSSUB
is_merge_published = sysconv(bit, status & 0x80000), -- CPM_MERGEREPL
is_dts_replicated = sysconv(bit, status & 0x100000), -- CPM_REPLDTS
is_xml_document = sysconv(bit, 0),
xml_collection_id = sysconv(int, 0),
default_object_id = sysconv(int, 0),
rule_object_id = sysconv(int, 0),
seed_value = IdentityProperty(id, 'SeedValue'),
increment_value = IdentityProperty(id, 'IncrementValue'),
last_value = IdentityProperty(id, 'LastValue'),
is_not_for_replication = sysconv(bit, status & 0x10000), -- CPM_ID_REPL
is_computed = sysconv(bit, status & 16), -- CPM_COMPUTED
sysconv(bit, 0) as is_sparse,
sysconv(bit, 0) as is_column_set
FROM sys.syscolpars
WHERE number = 0 -- SOC_COLUMN
AND (status & 4) = 4 -- CPM_IDENTCOL
AND has_access('CO', id) = 1
GO
无法获取系统内置函数(built-in function)的定义,所以无法进一步分析标识列是如何保存last_value的,但是个人猜测可能跟系统基表sys.syscolpars的idtval字段有关系。DAC模式下查询跟踪,你会发现标识列ID变化后,idtval字段的值也变化了。

新建三个表,标识列的自增值分别为1、2、3
CREATE TABLE test1(id INT IDENTITY(1,1), name VARCHAR(10))
CREATE TABLE test2(id INT IDENTITY(1,2), name VARCHAR(10))
CREATE TABLE test3(id INT IDENTITY(1,3), name VARCHAR(10))

8:TRUNCATE表后,标识列的当前值会变为1
9:与标识列相关的系统函数的区别。
SELECT IDENT_CURRENT('dbo.TEST_TRAN');
SELECT IDENT_INCR('dbo.TEST_TRAN');
SELECT IDENT_SEED('dbo.TEST_TRAN')
SELECT SCOPE_IDENTITY();
SELECT @@IDENTITY;
IDENT_CURRENT 类似于SQL Server 2000 (8.x)的标识函数 SCOPE_IDENTITY 和 @@IDENTITY。 这三个函数都返回最后生成的标识值。 但是,上述每个函数中定义的“最后”的作用域和会话有所不同**:
· IDENT_CURRENT 返回为某个会话和用域中的指定表生成的最新标识值。
· @@IDENTITY 返回为跨所有作用域的当前会话中的任何表生成的最后一个标识值。
· SCOPE_IDENTITY 返回为当前会话和当前作用域中的某个表生成的最新标识值。
如果 IDENT_CURRENT 值为 NULL(因为表从未包含行或已被截断),IDENT_CURRENT 函数将返回种子值。
参考资料:
https://www.dfarber.com/computer-consulting-blog/articles/how-to-solve-identity-problem-in-sql-2012/
https://www.codeproject.com/Tips/668042/SQL-Server-2012-Auto-Identity-Column-Value-Jump-Is
SQL Server解惑——标识列的限制和跳号现象的更多相关文章
- SQL Server修改标识列方法(备忘)
原文:SQL Server修改标识列方法(备忘) SQL Server修改标识列方法 ----允许对系统表进行更新 exec sp_configure 'allow updates',1 reconf ...
- [SQL Server]关于标识列,标识从1开始计数的的方法
DBCC CHECKIDENT ('表名', RESEED, 0) //从30开始 DBCC CHECKIDENT (jobs, RESEED, 30)
- SQL SERVER FOR 多列字符串连接 XML PATH 及 STUFF
原文:SQL SERVER FOR 多列字符串连接 XML PATH 及 STUFF 本来用 Writer 写一篇关于一列多行合并的博客来的,结果快写完了时候,在一个插入代码时候,崩了,重新打开,居然 ...
- SQL Server 2014 聚集列存储
SQL Server 自2012以来引入了列存储的概念,至今2016对列存储的支持已经是非常友好了.由于我这边线上环境主要是2014,所以本文是以2014为基础的SQL Server 的列存储的介绍. ...
- SQL Server分区键列必须是主键一部分
SQL Server分区键列必须是主键一部分. 必须把分区列包含在主键/唯一约束/唯一索引的键列中. USE tempdb GO -- 测试表 CREATE TABLE dbo.tb( id int, ...
- sql server 自增列,值突然增大1000的情况
sql server 自增列,值突然增大1000的情况 解决方法: 1 打开配置管理器2左面点击sql服务3右面 右键点击SQL Server(MSSQLSERVER) 4点击 启动参数5 在参数 ...
- sql server 某一列求和
sql server 某一列求和 SELECT 患者来源,设备类型,检查部位,设备名称,convert(char(10),STUDY_DATE,121) as 日期, count(distinct 就 ...
- SQL Server解惑——为什么你的查询结果超出了查询时间范围
原文:SQL Server解惑--为什么你的查询结果超出了查询时间范围 废话少说,直接上SQL代码(有兴趣的测试验证一下),下面这个查询语句为什么将2008-11-27的记录查询出来了呢?这个是同事遇 ...
- SQL Server自增长列插入指定值 -- SET IDENTITY_INSERT ON|OFF(转)
想要将值插入到自动编号(或者说是标识列,IDENTITY)中去,需要设定 SET IDENTITY_INSERT 示例: 1.首先建立一个有标识列的表:CREATE TABLE products (i ...
随机推荐
- React Hook~部分实用钩子
useCompareEffect /** * useCompareEffect * useEffect只是普通的浅比较,这里做了深比较 * useEffect的依赖是否相同,相同不触发 */ impo ...
- 浅谈Mybatis持久化框架在Spring、SSM、SpringBoot整合的演进及简化过程
前言 最近开始了SpringBoot相关知识的学习,作为为目前比较流行.用的比较广的Spring框架,是每一个Java学习者及从业者都会接触到一个知识点.作为Spring框架项目,肯定少不了与数据库持 ...
- 从SpringBoot源码分析 配置文件的加载原理和优先级
本文从SpringBoot源码分析 配置文件的加载原理和配置文件的优先级 跟入源码之前,先提一个问题: SpringBoot 既可以加载指定目录下的配置文件获取配置项,也可以通过启动参数( ...
- 当Django模型迁移时,报No migrations to apply 问题时
前言:当更改model时在次迁移是不是经常报此类错误,解决以下两点便可以更新成功 1. 删除修改模型对应的app应用下的migrations中的生成文件 2. 进入数据库,找到django_migra ...
- Android Studio同时监听多个Button实现activity跳转
MainActivity.java: package com.example.test; import android.content.Intent; import android.os.Bundle ...
- python分析BOSS直聘的某个招聘岗位数据
前言 毕业找工作,在职人员换工作,离职人员找工作……不管什么人群,应聘求职,都需要先分析对应的招聘岗位,岗位需求是否和自己匹配,常见的招聘平台有:BOSS直聘.拉钩招聘.智联招聘等,我们通常的方法都是 ...
- javascript对象笔记
JS对象 对象是一个具体的事物 在JS中对象是一组无序属性和方法的集合例如字符串,数组,函数等等 对象是由属性和方法组成的 属性:是事物的特征,在对象中用属性来表示一般 ...
- 【Linux】zookeeper-3.5.6最新版安装攻略,以及安装问题汇总
第一步下载:https://mirrors.tuna.tsinghua.edu.cn/apache/zookeeper/zookeeper-3.5.6/ 浏览器打开这个地址下载我们需要的安装包 apa ...
- canvas图片编辑操作:缩放、移动、保存(PC端+移动端)
最近在写canvas关于图片的操作,看了网上的代码基本都是不行的,于是就自己写了一个. html代码 <canvas id="myCanvas" width="37 ...
- Go语言入门系列(六)之再探函数
Go语言入门系列前面的文章: Go语言入门系列(三)之数组和切片 Go语言入门系列(四)之map的使用 Go语言入门系列(五)之指针和结构体的使用 在Go语言入门系列(二)之基础语法总结这篇文章中已经 ...