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 ...
随机推荐
- ASP.NET MVC+LayUI视频上传
前言: 前段时间在使用APS.NET MVC+LayUI做视频上传功能的时,发现当上传一些内存比较大的视频就会提示上传失败,后来通过查阅相关资料发现.NET MVC框架为考虑安全问题,在运行时对请求的 ...
- Hadoop的序列化
普通的序列化需要将类型的继承结构也序列化,但是hadoop只序列化对象本身,忽略继承关系,因为hadoop中传输的自定义类型一般都是简单的类型,这样可以减少传输的序列化数据,降低网络带宽的使用.
- Java web Cookie详解(持久化+原理详解+共享问题+设置中文+发送多个Cookie)
Java web Cookie详解 啥是cookie? 查询有道词典得: web和饼干有啥关系? 这个谜底等等来为大家揭晓 会话技术 web中的会话技术类似于生活中两个人聊天,不过web中的会话指的是 ...
- 移动端与Web端疫情数据展示
1.题目要求 2.整体思想 首先是在前两阶段已经完成的echarts可视化.利用Jsoup爬取疫情数据基础上来进行调用与完善.大致思想是在Android Studio上完成交互去调用ecplise中的 ...
- 利用 Python 写一个颜值测试小工具
我们知道现在有一些利用照片来测试颜值的网站或软件,其实使用 Python 就可以实现这一功能,本文我们使用 Python 来写一个颜值测试小工具. 很多人学习python,不知道从何学起.很多人学习p ...
- 11、Java 日期时间 日期工具类
一.简介 在Java8之前,日期时间API一直被开发者诟病,包括:java.util.Date是可变类型,SimpleDateFormat非线程安全等问题.故此,Java8引入了一套全新的日期时间处理 ...
- 微信公众号添加zip文件下载
微信公众号添加zip文件下载的教程 我们都知道创建一个微信公众号,在公众号中发布一些文章是非常简单的,但公众号添加附件下载的功能却被限制,如今可以使用小程序“微附件”进行在公众号中添加附件.如:zip ...
- MyBatisPlus分页查询,删除操作
分页查询 分页查询在网页使用十分之多 原始的limit进行分页 pageHelper第三方插件 3. MP内置的分页插件 导入配置 如何使用,官网的代码如下 //分页插件 @Bean public P ...
- 剑指offer之字符串是否为数值
1. 题目 这是<剑指offer>上的一道题,刚开始觉得这是一道挺简单的题目,后来发现自己太年轻了,考虑的因素太少了,思考了而是分钟还是无从下手,看了作者的思路深深被他折服了,题目如下: ...
- 全面介绍eBPF-概念
全面介绍eBPF-概念 前面介绍了BCC可观测性和BCC网络,但对底层使用的eBPF的介绍相对较少,且官方欠缺对网络方面的介绍.下面对eBPF进行全面介绍. 目录 全面介绍eBPF-概念 BPF概述 ...