温少的日志

我希望我所作的事情对别人有所帮助!
随笔 - 180, 文章 - 1, 评论 - 1077, 引用 - 6
数据加载中……

小议ID生成算法

(本文作者温少,首发于博客园,转载请注明)
ID生成算法,其中一种就是使用GUID(又称UUID),使用128位存储。UUID的一个问题是太长,可读性太差,人脑无法记忆。

替代方案之一,就是使用关系数据库的自增长字段,自增长字段的一个问题是,无法预先创建一个ID,只能够在保存的时候才能生成ID,这对于批量关联插入数据来说,不满足需求。

替代方案之二,就是使用一个记录ID的表,每次加一,在事务中使用Select FOR UPDATE来读取然后UPDATE SET FVALUE = FVALUE + 1,或者使用我之前文章中所提到的CAS算法。 这样做,会导致性能低下,每生成一个ID的成本都很高。

替代方案之三,就是把ID分成两部分,Seed和IncrementID。Seed采用上面的方案二或者其他办法生成,IncrementID使用一个AtomicInteger来每次递增生成。SEED转化为九进制数字,这样SEED就不会包含9,于是使用9作为分隔符,把SEED和IncrementID隔开。这样做,就可以做高性能产生ID,而且确保不重复。甚至可以更进一步,SEED由一个中心服务器生成。使用9个分隔符号隔开SEED和IncrementID,好处是SEED是变长,而不是使用固定位数来保存SEED,这样产生的ID会更短,可读性更好。

举例,34915,其中34时SEED,15是IncrementID,9是分隔符,SEED部分采用九进制表示法,确保不出现9,第一个9之后的内容属于IncrementID。

我已经做了一个实现,用于实际开发中,思路采用方案三,有很多实作的细节,但是总体设计思路就是如此。
(本文作者温少,首发于博客园,转载请注明)

posted on 2007-11-16 07:06 温少 阅读(3679) 评论(47)  编辑 收藏 所属分类: 推荐阅读

评论

#1楼    回复  引用  查看    

恩,不错
2007-11-16 07:49 | 无会      

#2楼    回复  引用  查看    

不错,很有意思。
不过楼主讲的:
--------------------------------------------------------
替代方案之一,就是使用关系数据库的自增长字段,自增长字段的一个问题是,无法预先创建一个ID,只能够在保存的时候才能生成ID,这对于批量关联插入数据来说,不满足需求。
--------------------------------------------------------
这个关系数据库应该是指 Microsoft SQL Server 之类的数据库吧?
如果是象 Oracle 之类的数据库,可以使用“序列”:
CREATE SEQUENCE SEQ_TEST;
这样,对于批量关联插入数据来说,就没有问题了。
我个人认为,Oracle 数据库的“序列”功能比 SQL Server 数据库的“自增长字段”功能无论从创意上、用途上都要好上太多了。
2007-11-16 08:13 | 银河      

#3楼    回复  引用  查看    

Oracle功能是更强,但是也挺不方便的。创建一个序列,远别自增字段要复杂。

楼主的这篇文章的思路,曾出现在《广西科学院学报》2006年11月第22卷第4期上。
2007-11-16 08:29 | 大石头      

#4楼    回复  引用  查看    

我提供我自己的一种方法

http://www.cnblogs.com/zdq2601/archive/2007/11/16/961143.html
2007-11-16 08:43 | 柳絮飞      

#5楼 [楼主]   回复  引用  查看    

@银河
我也熟悉Oracle,文章草草写成,所以没写上Oracle的实现。毕竟Oracle的SEQUENCE是比较特别,和MySQL、SQL Server、Sybase不一样,DB的语法和MS SQL Server类似的。

@大石头
我和《广西科学院学报》没有任何关系。

@柳絮飞
你的方法不可靠
2007-11-16 08:58 | 温少      

#6楼    回复  引用  查看    

不明白为什么非要用9来做分隔符,类似guid用-不行么,这样搞成9进制,是不是有点复杂了
2007-11-16 09:23 | jillzhang      

#7楼    回复  引用    

你这种方法,某个ID被删除后,还可以再使用吗?
数据复制、导入导出是否也能像GUID那样安全?
2007-11-16 09:35 | [阿毅] [未注册用户]

#8楼    回复  引用  查看    

按你分析3是个优选方案,但复杂点,而且如你说还要注意细节
2007-11-16 09:36 | Enzo      

#9楼 [楼主]   回复  引用  查看    

@jillzhang
如果使用-分隔,就不是数字了,存储空间就会增大很多,效率也会下降。

@阿毅
本文只是提供思路,在实际的实现中,可以做到你所说的要求。包括SEED生成的唯一性保证,备份恢复,灾备等等。在SEED的生成方案可以做很多事情。
2007-11-16 09:39 | 温少      

#10楼    回复  引用  查看    

学习了.http://www.cnblogs.com/jiangshaofen/archive/2007/04/18/717785.html
2007-11-16 09:42 | 预备役中尉      

#11楼 [楼主]   回复  引用  查看    

@预备役中尉
你文章中的COMB类型占用空间太多不可取。我们通常需要更短小更高效的ID,同时要求唯一,这两个需求是互相冲突的。方案三是一种比较好的兼顾两个需求的办法。
2007-11-16 09:46 | 温少      

#12楼    回复  引用    

COMB为什么不行?我感觉很好啊
2007-11-16 09:54 | 不错 [未注册用户]

#13楼    回复  引用  查看    

"""
替代方案之一,就是使用关系数据库的自增长字段,自增长字段的一个问题是,无法预先创建一个ID,只能够在保存的时候才能生成ID,这对于批量关联插入数据来说,不满足需求。
"""

SQL Server 的可以手工插入 ID 到自增列。
2007-11-16 10:03 | 木野狐(Neil Chen)      

#14楼    回复  引用  查看    

SET IDENTITY_INSERT [ database_name . [ schema_name ] . ] table { ON | OFF }
2007-11-16 10:05 | 木野狐(Neil Chen)      

#15楼    回复  引用  查看    

不错的想法。
2007-11-16 10:13 | 随心所欲      

#16楼 [楼主]   回复  引用  查看    

@木野狐
如按你所说,可以手工插入ID到自增列,那么这个ID从何而来?:)
2007-11-16 10:23 | 温少      

#17楼 [楼主]   回复  引用  查看    

回12楼
COMB的问题是太长,效率会很低。这个我们吃过亏!当数据量上百万过千万之后,你将会十分痛苦。。。。
2007-11-16 10:24 | 温少      

#18楼    回复  引用  查看    

@温少
哦,这个我用的比较多是在做数据迁移的时候。数据是在别的表或者库中已有的。

"批量关联插入数据来说,不满足需求" 这个用 @@identity 就可以得到了吧?应该也不是问题。

个人感觉还是自增列最方便使用 :)
2007-11-16 10:27 | 木野狐(Neil Chen)      

#19楼    回复  引用  查看    

你的第三種方法好像沒什么用處吧﹐既然seed需要前兩種方法生成﹐那整個ID都可以了吧﹐為什么還要increament呢?有點不懂
2007-11-16 10:31 | 小生      

#20楼 [楼主]   回复  引用  查看    

@小生
你没认真想一下 :) SEED生成一次就可以了,程序启动时生成,然后一直使用。
2007-11-16 10:37 | 温少      

#21楼    回复  引用  查看    

我也来溱个热闹,主键生成

http://www.cnblogs.com/darkangle/archive/2007/11/10/955187.html
2007-11-16 10:46 | 沙加      

#22楼 [楼主]   回复  引用  查看    

@沙加
ID最好是INT,或者BIGINT,不要使字符串,否则占用的空间就是太大了!
2007-11-16 10:52 | 温少      

#23楼    回复  引用  查看    

我想如果用两个字段,一个自增ID一个GUID,一般情况下用自增ID,迁移时才用GUID?
2007-11-16 10:53 | Dove.Net      

#24楼    回复  引用  查看    

@温少
那为何不把seek和inceremtnid做成两列,合并起来做主,这样不用分割,效率还高呀
2007-11-16 11:31 | jillzhang      

#25楼    回复  引用  查看    

分段的做法,你可以参考SAP软件,很成熟的方案了。
2007-11-16 12:02 | 鞠强      

#26楼    回复  引用  查看    

很好的思路
但对于数据复制而言 因为多数据库的存在 seed的值仍然无法得到唯一 所以还是只好用guid:(
2007-11-16 12:40 | progame      

#27楼    回复  引用  查看    

其实我也觉得guid是非常好的选择,性能根本不是问题。什么是性能?0.0001ms和1ms之间的差距有千倍之多,但这样的问题不是问题,不影响使用就没有什么性能可言,相反过多关注性能,这个不能用,那个不能用,只能束手束脚,扩展和分布才是王道。
同样100W数据,你一台可以,我一台不行,我两台干行不行。再不行,我三台行不行
2007-11-16 13:11 | jillzhang      

#28楼    回复  引用  查看    

看你的文章绕了半天,我能不能这样理解。

程序启动就针对每个表取一个 seed,保存在内存里;同时每张表的incrementid=0

每次插入数据库的时候,seed+99[..]+incrementid++。

这样是不是?
2007-11-16 14:08 |       

#29楼    回复  引用  查看    

UUID的一个问题是太长,可读性太差,人脑无法记忆。...

2348374, 3283873, 7327458, 3237,可读性好?你记它们干什么?
2007-11-16 14:59 | birdshome      

#30楼    回复  引用  查看    

guid的性能还没有得到不能容忍的地步的时候,我觉得没必要自行设计其他方式,包括那种时间拼凑的那种,在以后复制或者数据分布的时候,都会遇到hell式的灾难
2007-11-16 16:08 | jillzhang      

#31楼    回复  引用  查看    

我觉得在很多需要经常恢复数据的表,采用第3种是不错的
2007-11-16 19:01 | 代码乱了      

#32楼    回复  引用  查看    

既然是做主键,本该就不应该让其带有任何的业务意义,因此也就不必记忆它.个人不太赞同博主的做法.学习.
2007-11-16 19:47 | 预备役中尉      

#33楼 [楼主]   回复  引用  查看    

SEED如果由一台集中服务器来分配,而且带上一个额外信息,例如central_server_id、request_host_id、seed_inc_id,就可以做到集中分配,做到完全的UUID效果。这个和域名分配的思路是一致的。

由于引入了central_server_id,就可以做到多台分配服务器,容错于是也能够做到了。

采用方案三的好处就是,可以使用一个LONG就足够存储数据了,初始时甚至采用INT就足够了。在数据关联较为复杂的时候,很多表的数据中,很大一部分就是ID,ID的大小占用数据空间的很大一部分,这一点我们深有体会。在实际应用中,用过GUID,深切感受到他的坏处。
2007-11-17 03:44 | 温少      

#34楼    回复  引用    

当数据量比较大的时候,int的长度可能不够,那么我们应该使用long还是使用decimal呢(我指在C#代码中)?
同样的,在数据库中使用什么呢,我想:number(18,0)【Oracle】,decimal(18,0)【sqlserver】,可行吗?
望指教!
2007-11-17 21:20 | teamleader [未注册用户]

#35楼    回复  引用    

guid的主要问题在于效率,可读性不是什么问题(以字符串方式存储的guid效率更低);
通过类似序列的方式确定的主键,主要问题在于每取一次主键都要和数据库进行交互;
identity列的问题在于在UI层不能方便地取得最新的Identity值;
楼主的办法不错,学习了。

期待楼主有空的时候贴出详细的实现,因为并发问题,生成效率问题也不太好弄:)

还有这种int型的自定义主键策略有没有比较好的方式能够解决复制或者数据分布的方式呢?

期待楼主的新篇问世!
2007-11-17 21:20 | teamleader [未注册用户]

#36楼    回复  引用  查看    

我还是guid 当主键,需要记忆的有意义的代码不作为主键,只在代码内校验唯一性了。
2007-11-18 03:29 | 萧寒      

#37楼    回复  引用    

@柳絮飞
你的方法不可靠


为什么我的方法不可靠???
2007-11-18 08:19 | zdq2601 [未注册用户]

#38楼    回复  引用  查看    

@zdq2601
如果并发数量大了,一定不可靠。
DateTime.Now 的毫秒数不准确。
2007-11-18 09:58 | 随风流月      

#39楼    回复  引用  查看    

@温少
我在想,可不可以给方案2做出一个改进,就是预先申请一批 ID,需要的时候再使用?这样或许性能可以提高。方案3太复杂。
2007-11-18 09:59 | 随风流月      

#40楼 [楼主]   回复  引用  查看    

@随风流月
采用方案2预先生成一批,以前就做过类似的事情,问题就是集群环境下麻烦,就算不是集群环境,多台机器开发,一起连同一个开发数据库时,也会很烦人。
2007-11-18 14:59 | 温少      

#41楼    回复  引用    

我个人觉得应该为三位分类码+八位日期码+四位递增码。所有表ID统一生成。
2007-12-04 13:19 | iss168 [未注册用户]

#42楼    回复  引用  查看    

@iss168
用八位日期码的话,业务大的情况下,四位递增会够用吗?
2007-12-29 00:00 | 助平君      

#43楼    回复  引用    

如果是Oracle,可以设置序列的Cache Size , 程序稍作处理就很不错了。
2008-01-08 15:47 | 羊肉汤 [未注册用户]

#44楼    回复  引用    

@随风流月的方案好,有想法
2008-01-25 16:33 | dataflow1 [未注册用户]

#45楼    回复  引用    

各位的ID实现算法不知道有没有在超频繁的场景下使用的经验。比如说移动的业务。繁忙程度远非你们这些算法所能实现的。
2008-07-16 10:15 | helloboy [未注册用户]

#46楼 [楼主]   回复  引用  查看    

我所说的做法,每秒钟可以产生100万个ID。
2008-07-16 12:50 | 温少      

#47楼    回复  引用  查看    

楼主:
有几个疑问:
1.你的Seed,是一个表就是统一的一个呢,还是一个表里面可以有不同的?
如Seed值 表1=11,表2=22,表3=33(固定),
还是可以,表1,里面的Seed,即可以有11,也可以有22,表2里面也有11,22呢?

2.“9是分隔符”,请问,这个分隔符何用?是为了增加查询速度吗??
3.“15是IncrementID”这个值你是如何得到的?
我想最关键的是这个值不重复吧。
是得到的系统自增?还是max(id)??还是………………

4.既然您这么设计,肯定考虑了,多库的整合。
如果库1,库2,要进行整合,那么该如何处理呢?

2008-09-24 11:37 | 无双      

标题  
姓名  
主页
Email (博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2007-11-16 07:08 编辑过


相关链接: