C#生成唯一值的方法

使用1、MaxId表存储各表的MaxId

专门一个数据库,记录各个表的MaxId值,建一个存储过程来取Id,逻辑大致为:开启事物,对于在表中不存在记录,直接返回一个默认值为1的键值,同时插入该条记录到table_key表中。而对于已存在的记录,key值直接在原来的key基础上加1更新到MaxId表中并返回key

第一步:创建表
create table table_key
(
       table_name   varchar(50) not null primary key,
       key_value    int         not null
)


第二步:创建存储过程来取自增ID
create procedure up_get_table_key
(
   @table_name     varchar(50),
   @key_value      int output
)
as
begin
     begin tran
         declare @key  int
         
         --initialize the key with 1
         set @key=1
         --whether the specified table is exist
         if not exists(select table_name from table_key where table_name=@table_name)
            begin
              insert into table_key values(@table_name,@key)        --default key vlaue:1
            end
         -- step increase
         else    
            begin
                select @key=key_value from table_key with (nolock) where table_name=@table_name
                set @key=@key+1
                --update the key value by table name
                update table_key set key_value=@key where table_name=@table_name
            end
        --set ouput value
    set @key_value=@key

    --commit tran
    commit tran
        if @@error>0
      rollback tran
end

 存储过程中不使用事物,一旦使用到事物性能就急剧下滑。直接使用UPDATE获取到的更新锁,即SQL SERVER会保证UPDATE的顺序执行。(已在用户过千万的并发系统中使用)

create procedure [dbo].[up_get_table_key]
(
   @table_name     varchar(50),
   @key_value      int output
)
as
begin

	SET NOCOUNT ON;
	DECLARE @maxId INT
	UPDATE table_key
	SET @maxId = key_value,key_value = key_value + 1 
	WHERE table_name=@table_name
	SELECT @maxId

end

 结论:适用中型应用,此方案解决了分表,关联表插入记录的问题。但是无法满足高并发性能要求。同时也存在单点问题,如果这个数据库cash掉的话……

我们目前正头痛这个问题,因为我们的高并发常常出现数据库访问超时,瓶颈就在这个MaxId表。我们也有考虑使用分布式缓存(egmemcached)缓存第一次访问MaxId表数据,以提高再次访问速度,并定时用缓存数据更新一次MaxId表,但我们担心的问题是:

a)         倘若缓存失效或暴掉了,那缓存的MaxId没有更新到数据库导致数据丢失,必须停掉站点来执行Select max(id)各个表来同步MaxId表。

b)         分布式缓存不是一保存下去,其他服务器上就立马可以获取到的,即数据存在不确定性。(其实也是缓存的一个误用,缓存应该用来存的是频繁访问并且很少改动的内容)

         改进方案:

整体思想:建立两台以上的数据库ID生成服务器,每个服务器都有一张记录各表当前IDMaxId表,但是MaxId表中Id的增长步长是服务器的数量,起始值依次错开,这样相当于把ID的生成散列到每个服务器节点上。例如:如果我们设置两台数据库ID生成服务器,那么就让一台的MaxId表的Id起始值为1(或当前最大Id+1),每次增长步长为2,另一台的MaxId表的ID起始值为2(或当前最大Id+2),每次步长也为2。这样就将产生ID的压力均匀分散到两台服务器上,同时配合应用程序控制,当一个服务器失效后,系统能自动切换到另一个服务器上获取ID,从而解决的单点问题保证了系统的容错。(Flickr思想)

但是要注意:1、多服务器就必须面临负载均衡的问题;2、倘若添加新节点,需要对原有数据重新根据步长计算迁移数据。

结论:适合大型应用,生成Id较短,友好性比较好。(强烈推荐)

2、COMB数据类型的基本设计思路是这样的:既然GUID数据因毫无规律可言造成索引效率低下,影响了系统的性能,那么能不能通过组合的方式,保留GUID10个字节,用另6个字节表示GUID生成的时间(DateTime),这样我们将时间信息与GUID组合起来,在保留GUID的唯一性的同时增加了有序性,以此来提高索引效率。

NHibernate中,COMB型主键的生成代码如下所示:

/// <summary> /// Generate a new <see cref="Guid"/> using the comb algorithm. 
        /// </summary> 
        private Guid GenerateComb()
        {
            byte[] guidArray = Guid.NewGuid().ToByteArray();

            DateTime baseDate = new DateTime(1900, 1, 1);
            DateTime now = DateTime.Now;

            // Get the days and milliseconds which will be used to build    
            //the byte string    
            TimeSpan days = new TimeSpan(now.Ticks - baseDate.Ticks);
            TimeSpan msecs = now.TimeOfDay;

            // Convert to a byte array        
            // Note that SQL Server is accurate to 1/300th of a    
            // millisecond so we divide by 3.333333    
            byte[] daysArray = BitConverter.GetBytes(days.Days);
            byte[] msecsArray = BitConverter.GetBytes((long)
              (msecs.TotalMilliseconds / 3.333333));

            // Reverse the bytes to match SQL Servers ordering    
            Array.Reverse(daysArray);
            Array.Reverse(msecsArray);

            // Copy the bytes into the guid    
            Array.Copy(daysArray, daysArray.Length - 2, guidArray,
              guidArray.Length - 6, 2);
            Array.Copy(msecsArray, msecsArray.Length - 4, guidArray,
              guidArray.Length - 4, 4);

            return new Guid(guidArray);
        }

 结论:适合大型应用。即保留GUID的唯一性的同时增加了GUID有序性,提高了索引效率;解决了关联表业务问题;生成的Id不够友好;占据了32位。(强烈推荐)

3、自己写编码规则

优点:全局唯一Id,符合业务后续长远的发展(可能具体业务需要自己的编码规则等等)。

缺陷:根据具体编码规则实现而不同;还要考虑倘若主键在业务上允许改变的,会带来外键同步的麻烦。

我这边写两个编码规则方案:(可能不唯一,只是个人方案,也请大家提出自己的编码规则

1)         12位年月日时分秒+5位随机码+3位服务器编码  (这样就完全单机完成生成全局唯一编码)---20

缺陷:因为附带随机码,所以编码缺少一定的顺序感。(生成高唯一性随机码的方案稍后给给出程序)

2)         12位年月日时分秒+5位流水码+3位服务器编码 (这样流水码就需要结合数据库和缓存)---20位   (将影响顺序权重大的“流水码”放前面,影响顺序权重小的服务器编码放后)

缺陷:因为使用到流水码,流水码的生成必然会遇到和MaxId、序列表、Sequence方案中类似的问题

(为什么没有毫秒?毫秒也不具备业务可读性,我改用5位随机码、流水码代替,推测1秒内应该不会下99999[五位]条语法)

 

结论:适合大型应用,从业务上来说,有一个规则的编码能体现产品的专业成度。(强烈推荐)

猜你喜欢

转载自wyf.iteye.com/blog/2225604