sqlserver数据库主键的生成方式小结(sqlserver,mysql)

主键的生成方式主要有三种:
一. 数据库自动生成
二. GUID
三. 开发创建

严格讲这三种产生方式有一定的交叉点,其定位方式将在下面进行讲解。
第一种方式,主要将其定位在自增长的标识种子:可以设置起始数值,及增长步长。其优点在于使用时完全将并发任务交于数据库引擎管理,你不用担心存在多用户使用的时候会产生两个相同的ID的情况。其缺点也在于此,多数的数据库不提供直接获取标识ID的方式,对于开发人员来说产生ID的方式是透明的,开发人员几乎无法干预此项。对于数据的迁移也不是很方便。
由于存在上面的利弊,这种自增长的ID一般多用于设计基础表(系统运行的基础信息,如员工表)主键,而极少(根本不)用于主从表主、外键,因为在产生主从表数据并关联时,必须确定主表的ID,然后才能定位从表的关联ID。
例(MsSQL):


代码如下:

--创建测试表
CREATE TABLE [Identity](
Id INT IDENTITY(1,2) NOT NULL PRIMARY KEY,--种子的起始值1,步长2
Number VARCHAR(20) UNIQUE NOT NULL,
Name VARCHAR(20) NOT NULL,
Password VARCHAR(20) DEFAULT(123),
Description VARCHAR(40) NULL
)
--插入记录
INSERT INTO [Identity](Number,Name,Description) VALUES('001','1st','Id=1,因为起始值1')
INSERT INTO [Identity](Number,Name,Description) VALUES('002','2nd','Id=3,因为起始值1,步长2')
INSERT INTO [Identity](Number,Name,Description) VALUES('003','3rd','Id=5,由于字符长度超长,报错插入失败,造成此Id产生后被放弃')
INSERT INTO [Identity](Number,Name,Description) VALUES('004','4th','Id=7 not 5,因为第三条记录插入失败')
--检索记录,查看结果
SELECT * FROM [Identity]

结果:
(1 行受影响)
(1 行受影响)
消息 8152,级别 16,状态 14,第 3 行
将截断字符串或二进制数据。
语句已终止。
(1 行受影响)
(3 行受影响)
Id Number Name Password Description
1 001 1st 123 Id=1,因为起始值1
3 002 2nd 123 Id=3,因为起始值1,步长2
7 004 4th 123 Id=7 not 5,因为第三条记录插入失败
第二种方式,GUID即Globally Unique Identifier,也称为UUID(Universally Unique IDentifier),全球唯一标识符,GUID一般由32位十六进制的数值组成,其中包含网卡地址、时间及其他信息。任何两台电脑都不会产生相同的GUID,他的优点在唯一性,当需要数据库整合时,能节约不少劳动力。比如总公司和分公司各自系统独立运行,所有分公司数据定期需要提交到总部,可以避免合并数据时主键冲突问题,同时GUID还兼具自增长标识种子特点,无需开发人员太多的关注。但是GUID信息量大,占用空间也大,关联检索时,估计效率上也不是很高,对于32位的十六进制其可读性也差,虽然主键有对用户的无意义性,但是在设计或者调试交流时很不方便。
从长远考虑,为了保证数据的可移植性,一般还是会选择使用GUID来作为主键。
例(MsSQL):


代码如下:

--创建测试表
CREATE TABLE GUID(
Id UNIQUEIDENTIFIER NOT NULL PRIMARY KEY,--当然你也可以用字符串来保存
Number VARCHAR(20) UNIQUE NOT NULL,
Name VARCHAR(20) NOT NULL,
Password VARCHAR(20) DEFAULT(123)
)
--插入记录
INSERT INTO GUID(Id,Number,Name) VALUES(NewID(),'001','1st')
INSERT INTO GUID(Id,Number,Name) VALUES(NewID(),'002','2nd')
INSERT INTO GUID(Id,Number,Name) VALUES(NewID(),'003','3rd')
--检索记录,查看结果
SELECT * FROM GUID

结果:
Id Number Name Password
8E194F55-B4D3-4C85-8667-33BC6CD33BBC 001 1st 123
7141F202-7D0E-4992-9164-5043EC9FC6F6 002 2nd 123
E0E365A0-8748-4656-AF24-5D0B216D2095 003 3rd 123
第三种方式开发创建,其便捷性在于可控制性,此可控制性是指其组成形式,可以是整形、也可以是字符型,你可以根据实际情况给予多样的组成及产生形式,说到这里可能有的朋友就想起来自动产生单号,如:20120716001或者PI-201207-0001等等,没错,自我创建同样适用于这些类似的应用。
说到自我创建,多数首先想到的是取Max(Id)+1,这种方式虽然省事,但是实际上对于定制(在生产单号之类的有一定意义的信息时可能会有这样的需求,主键没必要)及并发的处理并不是很好。如,当前表中最大编号为1000,当C1和C2用户同时取这个Id处理时,得到的都是1001,导致保存失败。常规的做法是在取值时候加锁,但是当多用户频繁操作时,性能是个很大的问题,其中主要的原因之一是直接操作的业务数据表。
针对此种情况,解决方案是使用键值表来保存表名、当前或者下一个Id及其他信息,如果系统中多个表Id都使用这种方式,那么键值表中就会有多条相应的规则记录;当然也可以让整个数据库所有表的Id从都按相同的规则从一个源产生,那么键值表中只需要一条规则记录即可。
下面来看看这样一个使用键值表例子的演变(MsSQL):


代码如下:

--创建键值表
CREATE TABLE KeyTable(
ID INT IDENTITY(1,1) PRIMARY KEY NOT NULL,
TCode VARCHAR(20) UNIQUE NOT NULL,
TName VARCHAR(50) NOT NULL,
TKey INT NOT NULL,
)
GO
--插入测试记录
INSERT INTO KeyTable(TCode,TName,TKey)
VALUES('T001','Test',0)
GO
--创建获取指定表ID的存储过程,也可以修改成函数
CREATE PROCEDURE UP_NewTableID
@TCode VARCHAR(20),@NextID INT OUTPUT
AS
DECLARE @CurTKey INT,@NextTKey INT
BEGIN TRAN TransID
SELECT @CurTKey=TKey
FROM KeyTable
WHERE TCode = @TCode
IF @@ROWCOUNT = 0
BEGIN
ROLLBACK TRAN TransID
RAISERROR('Warning: No such row is exists',16,1)
RETURN
END
SET @NextTKey = @CurTKey + 1
--WAITFOR DELAY '00:00:05'
UPDATE KeyTable
SET TKey = @NextTKey
WHERE TCode = @TCode
IF @@ROWCOUNT = 0
BEGIN
ROLLBACK TRAN TransID
RAISERROR('Warning: No such row is updated',16,1)
RETURN
END
COMMIT TRAN TransID
SET @NextID = @NextTKey
GO

执行存储过程UP_NewTableID:


代码如下:

DECLARE @NextID INT
EXEC UP_NewTableID 'T001',@NextID OUTPUT
PRINT @NextID

运行的时会发现很正常,获取的结果也很正确。但是如果在高并发的情况,多个用户可能就会获取相同的ID,如果获取的ID后是用于保存对应表中的记录,那么最多只有一个用户能保存成功。
下面模拟一下并发情形,将上面的存储过程UP_NewTableID中语句WAITFOR DELAY '00:00:05'的注释去掉,打开3个查询分析器的窗体,依次执行上面语句。
预期是想分别获得1,2,3,但是也许会发现多个窗体的运行结果都是:1。这就是说在更新语句执行之前,大家都获取的ID是0,所以下一个数值都是为1。(实际的数值,根据DELAY的参数大小及运行时间按间隔有关)
从这方面来分析的话有的朋友可能就会想到,是否可以在更新语句执行时判断ID是不是原始ID了?修改过程:


代码如下:

ALTER PROCEDURE UP_NewTableID
@TCode VARCHAR(20),@NextID INT OUTPUT
AS
DECLARE @CurTKey INT,@NextTKey INT
BEGIN TRAN TransID
SELECT @CurTKey=TKey
FROM KeyTable
WHERE TCode=@TCode
IF @@ROWCOUNT=0BEGIN
ROLLBACK TRAN TransID
RAISERROR('Warning: No such row is exists',16,1)
RETURN
END
SET @NextTKey=@CurTKey+1
WAITFOR DELAY '00:00:05'
UPDATE KeyTable
SET TKey=@NextTKey
WHERE TCode=@TCode AND TKey=@CurTKey--此处加上TKey的校验
IF @@ROWCOUNT=0BEGIN
ROLLBACK TRAN TransID
RAISERROR('Warning: No such row is updated',16,1)
RETURN
END
COMMIT TRAN TransID
SET @NextID=@NextTKey
GO

如果打开个3个执行过程来模拟并发,那么会有2个窗体出现:
消息 50000,级别 16,状态 1,过程 UP_NewTableID,第 28 行
Warning: No such row is updated
由此会看到还是会由于并发导致有用户操作失败,但是较上一个至少将错误出现的时间点提前了。
那么有没有更好的方法,从查询到更新结束整个事务过程中,不会有任何其他事务插入其中来搅局的办法呢,答案很明确,有,使用锁!需要选择适当的锁,否则效果将和上面的一样。


代码如下:

ALTER PROCEDURE UP_NewTableID
@TCode VARCHAR(20),@NextID INT OUTPUT
AS
DECLARE @CurTKey INT,@NextTKey INT
BEGIN TRAN TransID
SELECT @CurTKey=TKey
FROM KeyTable WITH (UPDLOCK)--采用更新锁,并保持到事务完成
WHERE TCode=@TCode
IF @@ROWCOUNT=0BEGIN
ROLLBACK TRAN TransID
RAISERROR('Warning: No such row is exists',16,1)
RETURN
END
SET @NextTKey=@CurTKey+1
WAITFOR DELAY '00:00:05'
UPDATE KeyTable
SET TKey=@NextTKey
WHERE TCode=@TCode--此处无需验证TKey是否与SELECT的相同
COMMIT TRAN TransID
SET @NextID=@NextTKey
GO

可以打开N(N>=2)个窗体来进行测试,将会看到所有操作都被串行化,结果就是我们想要的那样。如此注释或者去掉模仿并发的语句WAITFOR DELAY '00:00:05'即可。
如前面所说,这同样适应于单据编号类似编码的产生形式,只要对前面的代码及键值表稍作修改即可,有兴趣的朋友可以一试。如果是从前端取得这个编号,并应用于各个记录,那么可能存在跳号的可能。如果为了保证不存在跳号,一种解决方案就是使用跳号表,将跳号记录定期扫描并应用于其他记录。另一种解决方案是将记录的保存操作放置到编号产生的过程中,形成一个串行化的事务。

俗话说萝卜白菜各有所爱,您用哪一种自有你的道理。

(0)

相关推荐

  • sqlserver主键设计的注意点

    在设计主键的时候往往需要考虑以下几点: 1.无意义性:此处无意义是从用户的角度来定义的.这种无意义在一定程度上也会减少数据库的信息冗余.常常有人称呼主键为内部标识,为什么会这样称呼,原因之一在于"内部",所谓内部从某种程度上来说就是指表记录,从大的范围来说就是数据库,如果你在设计的时候选择了对用户来说有意义的信息来作为主键,那么迟早会面对用户提出对这块信息进行更新的需求,那么你就违背了它应有的静态. 2.静态性:主键除了唯一地标识一条记录及外键的关联外,应不再考虑其他的意义,最理想的状

  • 如何获取SqlServer2005表结构(字段,主键,外键,递增,描述)

    1.获取表的基本字段属性 复制代码 代码如下: --获取SqlServer中表结构 SELECT syscolumns.name,systypes.name,syscolumns.isnullable,syscolumns.length FROM syscolumns, systypes WHERE syscolumns.xusertype = systypes.xusertype AND syscolumns.id = object_id('你的表名') 运行效果 2.如果还想要获取字段的描述

  • 详解SQL Server数据库架构和对象、定义数据完整性

    前言 本节我们继续SQL之旅,本节我们如题来讲讲一些基本知识以及需要注意的地方,若有不妥之处,还望指出,简短的内容,深入的理解. 数据库架构和对象 数据库包含架构,而架构又包含对象,架构可以看做是表.视图.存储过程等对象的容器.架构是一个命名空间,它被用做对象名称的前缀,比如在Cnblogs的架构中有一个名称为Blogs的表,此时我们用架构式限定式名称(即两部分式对象名称)所以Blogs表示为Cnblogs.Blogs.如果我们引用对象时省略了架构名称,SQL Server将会检查对象是否存在用

  • SQLSERVER聚集索引和主键(Primary Key)的误区认识

    很多人会把Primary Key和聚集索引搞混起来,或者认为这是同一个东西.这个概念是非常错误的. 主键是一个约束(constraint),他依附在一个索引上,这个索引可以是聚集索引,也可以是非聚集索引. 所以在一个(或一组)字段上有主键,只能说明他上面有个索引,但不一定就是聚集索引. 例如下面: 复制代码 代码如下: USE [pratice] GO CREATE TABLE #tempPKCL ( ID INT PRIMARY KEY CLUSTERED --聚集索引 ) ---------

  • Oracle数据完整性和锁机制简析

    本课内容属于Oracle高级课程范畴,内容略微偏向理论性,但是与数据库程序开发和管理.优化密切相关:另外本课的部分内容在前面章节已经涉及,请注意理论联系实际. 事务 事务(Transaction)从 通讯的角度看:是用户定义的数据库操作序列,这些操作要么全做.要么全不做,是不可分割的一个工作单元.事务控制语句称为TCL,一般包括Commit和Rollback. 事务不是程序,事务和程序分属两个概念.在RDBMS中,一个事务可以有一条SQL语句.一组SQL语句或者整个程序:一个应用程序又通常包含多

  • SQLServer导出sql文件/表架构和数据操作步骤

    很多朋友问到sql server数据库"生成脚本",只导出了数据库的sql脚本,而表里的数据依然没有导出来.很简单,看教程: 注:我这里用的SQLServer2008,其它版本应该差不多. 一.选中要导出的数据库–>任务–>生成脚本(或者在任务里面有生成脚本这个选项,好好找找能找到的!) 二.出现"生成和发布脚本"窗口,点击下一步 三.选择要编写脚本的数据库对象,全部导出选第一个,如果你想导出部分数据勾选    第二个就好了 四.出现这个画面后点击&qu

  • 深入Mysql,SqlServer,Oracle主键自动增长的设置详解

    1.把主键定义为自动增长标识符类型MySql在mysql中,如果把表的主键设为auto_increment类型,数据库就会自动为主键赋值.例如: 复制代码 代码如下: create table customers(id int auto_increment primary key not null, name varchar(15));insert into customers(name) values("name1"),("name2");select id fr

  • sqlserver 因为选定的用户拥有对象,所以无法除去该用户的解决方法

    现象: 把之前备份的数据库还原到现在的SQL数据库中,现在要把数据库中的用户删除掉,结果却提示: 因为选定的用户拥有对象,所以无法除去该用户 解决办法: 1.打开企业管理器,展开服务器,右键点击本地服器,属性: 打开SQL Server 属性(配置)窗口 2.选择常规窗口 选择 服务器设置窗口,将 允许对系统目录直接进行修改 选上,确定. 4.展开数据库,看到数据库系统表 sysusers ,右键--打开表--返回所有行: 5.选择数据库中要删除的用户,找到相关行,点右键 删除: 选择是, 最后

  • SQLServer2005 批量查询自定义对象脚本

    object_definition的官方参考 http://msdn.microsoft.com/zh-cn/library/ms176090.aspx 以下代码可以批量查看或者导出视图.存储过程.触发器和函数的脚本 复制代码 代码如下: select name , xtype , object_definition (id ) from sysobjects where xtype in ('V' , 'P' , 'TR' , 'IF' , 'TF' , 'FN' ) order by xty

  • SQLSERVER实现更改表名,更改列名,更改约束代码

    废话不多说了,具体详情如下所示: 1.修改表名 格式:sp_rename tablename,newtablename sp_rename tablename,newtablename 2.修改字段名 格式:sp_rename 'tablename.colname',newcolname,'column' sp_rename 'tablename.colname',newcolname,'column' 3.添加字段 格式:alter table table_name add new_colum

  • 小议sqlserver数据库主键选取策略

    因为主键可以唯一标识某一行记录,所以可以确保执行数据更新.删除的时候不会出现张冠李戴的错误.当然,其它字段可以辅助我们在执行这些操作时消除共享冲突,不过就不在这里讨论了.主键除了上述作用外,常常与外键构成参照完整性约束,防止出现数据不一致.所以数据库在设计时,主键起到了很重要的作用. 常见的数据库主键选取方式有: 自动增长字段 手动增长字段 UniqueIdentifier "COMB(Combine)"类型 一.自动增长型字段 很多数据库设计者喜欢使用自动增长型字段,因为它使用简单.

  • SQLServer主键和唯一约束的区别

    首先说明一点,主键又称主键约束,它也是一种约束,看下它和唯一约束的创建语法: alter table Person add constraint PK_Id primary key (Id) alter table Person add constraint UQ_Name unique (Name) 主键和唯一约束都要求字段值唯一,除此外,它们还有如下区别: ·同一张表只能有一个主键,但能有多个唯一约束: ·主键字段值不能为NULL,唯一约束字段值可以为NULL: ·主键字段可以做为其他表的外

随机推荐