获取ID方法探讨
20040820153402033 + RAND () * 100
<!--[if !supportLists]-->3. <!--[endif]-->使用uniqueidentifier 列及NEWID()函数,常见于数据库复制与发布等分布式应用,如:
-- Creating a table using NEWID for uniqueidentifier data type.
cust_id uniqueidentifier NOT NULL DEFAULT newid(),
-- Inserting data into Test table.
INSERT CASSABA (cust_id, company) VALUES (newid(), 'Wartian Herkku')
<!--[if !supportLists]-->4. <!--[endif]-->以Name-Value Pair 的方式, 将各种ID的当前值保存在一张表中,需要时,则统一从该表取用, 每次使用后递增,如:
UPDATE CASSABA SET CURR_ID = @MAXID
INSERT INTO CASSABA (CURR_TIME) VALUES (GETDATE())
Set @NewID = CONVERT(NVARCHAR, SCOPE_IDENTITY())
@Category NVARCHAR (30),
@MaxID INT OUTPUT
SELECT @MaxID = MAX_ID FROM CASSABA WHERE CATEGORY = @Category
UPDATE CASSABA SET MAX_ID = @MaxID + 1 WHERE CATEGORY = @Category
<!--[if !supportLists]-->A. <!--[endif]-->不加事务,先取用MAX_ID, 再将MAX_ID 更新为 MAX_ID + 1;
<!--[if !supportLists]-->C. <!--[endif]-->加事务,先取用MAX_ID, 再将MAX_ID 更新为 MAX_ID + 1;
表面上看,这四种实现代码均是大同小异,不同点只是在于是否添加事务,以及select/update的前后顺序等。 事实上,在系统用户量不大,并发度低的时候,这四种实现都没有什么问题,但是一旦系统的用户量上来,并发度增高的时候, A,B都会产生一个非常严重的问题, 即生成重复的ID。这显然不符合我们对ID列的要求。问题出现在哪里呢?
SELECT @MaxID = MAX_ID FROM CASSABA WHERE CATEGORY = @CATEGORY
UPDATE CASSABA SET MAX_ID = @MaxID + 1 WHERE CATEGORY = @CATEGORY
<!--[if !supportLists]-->M2) <!--[endif]-->加事务, 先取用MAX_ID, 再将MAX_ID 更新为 MAX_ID + 1;主要代码如下:
SELECT @MaxID = MAX_ID FROM CASSABA WHERE CATEGORY = @CATEGORY
UPDATE CASSABA SET MAX_ID = @MaxID + 1 WHERE CATEGORY = @CATEGORY
COMMIT TRANSACTION
UPDATE CASSABA SET MAX_ID = MAX_ID + 1 WHERE CATEGORY = @CATEGORY
SELECT @MaxID = MAX_ID FROM CASSABA WHERE CATEGORY = @CATEGORY
CREATE TABLE CASSABA
CURR_ID IDENTITY (1, 1) NOT NULL ,
INSERT INTO CASSABA (CURR_TIME) VALUES (GETDATE())
SELECT @MaxID = CONVERT(NVARCHAR,SCOPE_IDENTITY())
method
|
client num
|
max duplicate times
|
unique/Total
|
time(ms)/total per client
|
average
(ms/per client)
|
M1
|
4
|
4
|
754/39947
|
C1: 565153/10,000
|
54.29
|
C2: 563086/9,947
|
|||||
C3: 564213/10,000
|
|||||
C4: 564430/10,000
|
|||||
M2
|
4
|
4
|
731/40,000
|
C1: 533840/10,000
|
53.48
|
C2: 532840/10,000
|
|||||
C3: 537793/10,000
|
|||||
C4: 532650/10,000
|
|||||
M3
|
4
|
--
|
40000/40000
|
C1: 573123/10,000
|
57.18
|
C2: 572826/10,000
|
|||||
C3: 570293/10,000
|
|||||
C4: 570873/10,000
|
|||||
M4
|
4
|
--
|
40000/40000
|
C1: 265100/10,000
|
26.77
|
C2: 261773/10,000
|
|||||
C3: 266176/10,000
|
|||||
C4: 277866/10,000
|
<!--[if !supportLists]-->1. <!--[endif]-->M1,M2均会产生重复ID,而且重复程度相当,M3, M4则不会。
<!--[if !supportLists]-->2. <!--[endif]-->M3平均每14ms产生一个ID,M4平均每7ms产生一个ID,M4的效率比M3几乎高出一倍。
SQL-92 定义了以下四种隔离级别,SQL Server 支持所有这些隔离级别:
<!--[if !supportLists]-->l <!--[endif]-->未提交读(事务隔离的最低级别,仅可保证不读取物理损坏的数据)。
<!--[if !supportLists]-->l <!--[endif]-->提交读(SQL Server 默认级别)。
<!--[if !supportLists]-->l <!--[endif]-->可重复读。
<!--[if !supportLists]-->l <!--[endif]-->可串行读(事务隔离的最高级别,事务之间完全隔离)。
SELECT @MaxID = MAX_ID FROM CASSABA WITH(UPDLOCK) WHERE CATEGORY = @CATEGORY
添加WITH(UPDLOCK) 子句后,当前事务首先获得的是类型为RID的U锁,并且在TAB上放置IX锁,在页上放置IU锁,由于U锁与U锁不兼容,所以其他的获取ID事务被阻塞,直到当前事务完成update动作结束,其他的事务才能获得U锁。
SELECT @MaxID = MAX_ID FROM CASSABA WHERE CATEGORY = @CATEGORY
UPDATE CASSABA SET MAX_ID = @MaxID + 1 WHERE CATEGORY = @CATEGORY
COMMIT TRANSACTION
那么M4(也就是本文开篇提到的第5种方法)如何呢?这种方法通过创建一个包含自增类型的表(我称之为种子表),每次需要ID的时候,就向里面插入一条数据,然后用SCOPE_IDENTITY() 函数取出当前生成的ID。这种方法不需要添加事务, 简单高效, 由SQL Server本身保证了唯一性, 而且可以扩展该表,添加一些监控栏位,如产生ID的时间,请求的客户端等。唯一美中不足的是, 每一种ID都需要与之对应的Seed表,维护起来稍显不便。