<div class="codebody" id="code54023"> --创建测试表 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 VALUES('001','1st','Id=1,因为起始值1') INSERT INTO Identity VALUES('002','2nd','Id=3,因为起始值1,步长2') INSERT INTO Identity VALUES('003','3rd','Id=5,由于字符长度超长,报错插入失败,造成此Id产生后被放弃') INSERT INTO Identity VALUES('004','4th','Id=7 not 5,因为第三条记录插入失败') --检索记录,查看结果 SELECT FROM [Identity]
<div class="codebody" id="code45737"> --创建测试表 CREATE TABLE GUID( Id UNIQUEIDENTIFIER NOT NULL PRIMARY KEY,--当然你也可以用字符串来保存 Number VARCHAR(20) UNIQUE NOT NULL, Password VARCHAR(20) DEFAULT(123) ) --插入记录 INSERT INTO GUID(Id,Number,Name) VALUES(NewID(),'001','1st') INSERT INTO GUID(Id,'002','2nd') INSERT INTO GUID(Id,'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): <div class="codetitle"><a style="CURSOR: pointer" data="23711" class="copybut" id="copybut23711" onclick="doCopy('code23711')">代码如下:
<div class="codebody" id="code23711"> --创建键值表 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',1) RETURN END COMMIT TRAN TransID SET @NextID = @NextTKey GO
运行的时会发现很正常,获取的结果也很正确。但是如果在高并发的情况,多个用户可能就会获取相同的ID,如果获取的ID后是用于保存对应表中的记录,那么最多只有一个用户能保存成功。 下面模拟一下并发情形,将上面的存储过程UP_NewTableID中语句WAITFOR DELAY '00:00:05'的注释去掉,打开3个查询分析器的窗体,依次执行上面语句。 预期是想分别获得1,2,3,但是也许会发现多个窗体的运行结果都是:1。这就是说在更新语句执行之前,大家都获取的ID是0,所以下一个数值都是为1。(实际的数值,根据DELAY的参数大小及运行时间按间隔有关) 从这方面来分析的话有的朋友可能就会想到,是否可以在更新语句执行时判断ID是不是原始ID了?修改过程: <div class="codetitle"><a style="CURSOR: pointer" data="44669" class="copybut" id="copybut44669" onclick="doCopy('code44669')">代码如下:<div class="codebody" id="code44669"> ALTER PROCEDURE UP_NewTableID @TCode VARCHAR(20),@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',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',1) RETURN END COMMIT TRAN TransID SET @NextID=@NextTKey GO
如果打开个3个执行过程来模拟并发,那么会有2个窗体出现: 消息 50000,级别 16,状态 1,过程 UP_NewTableID,第 28 行 Warning: No such row is updated 由此会看到还是会由于并发导致有用户操作失败,但是较上一个至少将错误出现的时间点提前了。 那么有没有更好的方法,从查询到更新结束整个事务过程中,不会有任何其他事务插入其中来搅局的办法呢,答案很明确,有,使用锁!需要选择适当的锁,否则效果将和上面的一样。 <div class="codetitle"><a style="CURSOR: pointer" data="26915" class="copybut" id="copybut26915" onclick="doCopy('code26915')">代码如下:<div class="codebody" id="code26915"> ALTER PROCEDURE UP_NewTableID @TCode VARCHAR(20),@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',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