自增长主键Id的设计

一、引言

在使用ORM框架时,一个表有一个主键是必须的,如果没有主键,就没有办法来唯一的更新一条记录。在Sql Server数据库和Mysql数据库设置自增长的主键是一件很轻松的事情,如果在Oracle数据库中设置自增长的主键是比较繁琐的。本文不讨论数据库里单表的自增长问题,探讨的是多表自增长唯一Id的设计。
如果各位看官遇到这个多表自增长唯一Id的这个需求,会怎么处理呢?

二、GUID的介绍

关于自增长主键的问题,有些人可能会想到.Net中的GUID,先对这个GUID进行测试。

 public void GuidTest()
        {
            string guid = Guid.NewGuid().ToString();
            Console.WriteLine(guid);
            Console.WriteLine(guid.Length);
        }

GUID Test

下面是输出结果:

c8eb1c81-eafa-423b-a1bf-15fd5df829c4
36

可以看到这个GUID是一个字符串,长度36位,还真够长的,估计在用的时候,如果想减少位数,可以把横杠-去掉,少了4位,还剩32位,不过还是挺长的。
以我的观察来看,现在如果谁把数据库的主键设计成这个GUID,还真的是个二货:)
原因有三,1)太长了,在数据库里占空间  
              2)排序不方便
              3)检索时字符串的效率不如整数
本来对GUID只想一笔而过,看来写得太多了。

三、单机版自增长Id实现

针对多表下的自增长的唯一的ID,首先想到的就是时间,把这个时间格式化成整形的数字,就可以解决问题。为了减少位数,把日期的年的部分20去掉,毫秒数取两位,共15位。
为了防止重复,程序每次生成,如果当前生成的Id大于当前时间下的Id,会保存在在xml里,下次程序启动,会检查xml里保存的Id和当前时间下的Id,两者取其大。这样做是为了,防止程序重启,生成的Id重复。当然,为了多线程的安全,顺手lock。代码如下:

public class IdGenerator
    {
        private static string idXml = "id.xml";
        private static long current = 0;
        private static object obj = new object();
        private static string format = "yyMMddHHmmssfff"; //15位

        static IdGenerator()
        {
            var xDoc = XDocument.Load(idXml);
            long last = 0;
            long.TryParse(xDoc.Root.Value, out last);

            //每次启动检查最后生成的Id是否大于当前时间下的Id
            long now = long.Parse(DateTime.Now.ToString(format));
            if (last > now)
            {
                current = last;
            }
            else
            {
                current = now;
            }
        }

        public static long GetId()
        {
            lock (obj)
            {
                current += 1;

                //如果当前的生成的Id大于当前时间下的Id,就要保存下来
                if (current > long.Parse(DateTime.Now.ToString(format)))
                {
                    var xDoc = XDocument.Load(idXml);
                    xDoc.Root.Value = current.ToString();
                    xDoc.Save(idXml);
                }
                return current;
            }
        }
    }

IdGenerator

四、分布式下的自增长Id实现
在现实环境下,项目中可能有多个网站或多个应用程序使用这个自增长的Id,这样就要考虑分布式的情况,这里实现分布式使用WCF技术,WCF实现在本地和服务器通过契约来通信,抽象之后,调用服务器代码就像调用本地代码。
首先定义契约:

namespace WCFServiceTest
{
    [ServiceContract]
    public interface IIdGenContract
    {
        [OperationContract]
        long GetIdByWCF();
    }
}

IIdGenContract

该接口里有一个方法GetIdByWCF,这个方法需要服务端来实现。

namespace WCFServiceTest
{
    [ServiceBehavior(IncludeExceptionDetailInFaults = true, InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple, MaxItemsInObjectGraph = Int32.MaxValue)]
    public class IdGenService : IIdGenContract
    {
        public static IdGenService Instance = new IdGenService();

        public long GetIdByWCF()
        {
            return IdGenerateHelper.IdGenerator.GetId();
        }

    }
}

IdGenService

下一步就是开启这个服务

namespace WCFServiceTest
{
    class Program
    {
        static void Main(string[] args)
        {
            ServiceHost host = new ServiceHost(IdGenService.Instance);
            host.Open();
            Console.WriteLine(typeof(IdGenService) + " Opened");
            Console.WriteLine("服务地址:" + host.Description.Endpoints[0].ListenUri);

            Console.Read();
        }
    }
}

Program

这里面需要配置文件,如果是网站就是web.config,桌面程序就是app.config,配置文件的详细会在下面的demo代码里。
服务端完成了,下面就是编写客户端的代码来调用该服务了。客户端通过契约(接口)来和服务端进行通信,也要实现这个接口。

namespace ConsoleApplication2
{
    public class IdService : ClientBase<IIdGenContract>, IIdGenContract
    {
        public long GetIdByWCF()
        {
            return base.Channel.GetIdByWCF();
        }
    }
}

ClientIdService

接下来就是测试代码了

namespace ConsoleApplication2
{
    /// <summary>
    /// 通过调用WCF服务的方式生成自增长Id
    /// </summary>
    class Program
    {
        static void Main(string[] args)
        {
            long id = new ClientIdService().GetIdByWCF();
            Console.WriteLine(id);
            Console.Read();
        }
    }
}

Program

四、结束
一切按计划执行,输出一个唯一的Id,本文完整Demo下载。

时间: 2024-10-22 18:09:44

自增长主键Id的设计的相关文章

ibatis annotations 注解方式返回刚插入的自增长主键ID的值--转

原文地址:http://www.blogs8.cn/posts/WWpt35l mybatis提供了注解方式编写sql,省去了配置并编写xml mapper文件的麻烦,今天遇到了获取自增长主键返回值的问题,发现相关问答比较少,还好最后还是圆满解决了,现把重点记录一下,解决问题的关键就是以下几行代码: 1 @Insert("insert into Product(title, image, price, detail, summary, seller) values(#{title},#{imag

ibatis annotations 注解方式返回刚插入的自增长主键ID的值

mybatis提供了注解方式编写sql,省去了配置并编写xml mapper文件的麻烦,今天遇到了获取自增长主键返回值的问题,发现相关问答比较少,还好最后还是圆满解决了,现把重点记录一下,解决问题的关键就是以下几行代码: 1 @Insert("insert into Product(title, image, price, detail, summary, seller) values(#{title},#{image},#{price},#{detail},#{summary},#{selle

Access获取新插入数据的自增长主键Id

Access数据库不能用output,自己写类似Oracle的Sequence. 1 public int InsertEx(User user) 2 { 3 int id = -1; 4 5 using (OleDbConnection conn = new OleDbConnection(AccessHelper.connectionString)) 6 { 7 conn.Open(); 8 9 OleDbCommand cmd = new OleDbCommand { Connection

mysql 插入数据失败防止自增长主键增长的方法

mysql设置了自增长主键ID,插入失败的那个自增长ID也加一的,比如失败5个,下一个成功的不是在原来最后成功数据加1,而是直接变成加6了,失败次数一次就自动增长1了,能不能让失败的不增长的? 或者说mysql插入数据失败,怎么能防止主键增长? MYSQL不保证AUTO_INCREMENT依次增长(1,2,3,4,5),但是可以保证正向增长(1,3,5,9)所以,当你某次操作失败后,下次AUTO_INCREMENT就不是顺序的了. innodb的自增是缓存在内存字典中的,分配方式是先预留,然后再

Mycat探索之旅(4)----Mycat的自增长主键和返回生成主键ID的实现

说明:MyCAT自增长主键和返回生成主键ID的实现 1) mysql本身对非自增长主键,使用last_insert_id()是不会返回结果的,只会返回0:这里做一个简单的测试 创建测试表 -------------------------------------- --创建测试表 ------------------------------------- USE test; CREATE TABLE IF NOT EXISTS t_auto_increment ( id INT NOT NULL

mybatis添加记录时返回主键id

参考:https://www.cnblogs.com/nuccch/p/7687281.html 场景 有些时候我们在添加记录成功后希望能直接获取到该记录的主键id值,而不需要再执行一次查询操作.在使用mybatis作为ORM组件时,可以很方便地达到这个目的.鉴于mybatis目前已经支持xml配置和注解2种方式,所以分别给予详细介绍. 数据表设计: drop table if exists `test`; create table `test` ( `id` bigint(20) NOT NU

mysql主主同步两个数据库同时写入,实现原理:自动增长主键不重复

出现的问题(多主自增长ID重复) ? 解决方法: 我们只要保证两台服务器上插入的自增长数据不同就可以了 如:A查奇数ID,B插偶数ID,当然如果服务器多的话,你可以定义算法,只要不同就可以了 ? 在这里我们在A,B上加入参数,以实现奇偶插入 ? A:my.ini上加入参数 ? auto_increment_offset?=?1 auto_increment_increment?=?2 这样A的auto_increment字段产生的数值是:1,?3,?5,?7,?-等奇数ID了 ? B:my.in

MyBatis在insert插入操作时返回主键ID的配置

转:http://www.cnblogs.com/icerainsoft/p/4648900.html 很多时候,在向数据库插入数据时,需要保留插入数据的id,以便进行后续的update操作或者将id存入其他表作为外键. 但是,在默认情况下,insert操作返回的是一个int值,并且不是表示主键id,而是表示当前SQL语句影响的行数... 接下来,我们看看MyBatis如何在使用MySQL和Oracle做insert插入操作时将返回的id绑定到对象中. MySQL用法: <insert id=&qu

MongoDB Long/Int(长整型)的自增长主键 解决方案

今朝有幸尝芒果,发现自增长ID类型有多种,唯独没有Long/Int. 一思路:1. 自建一个Collection(表,假设名为:IdentityEntity,其中字段:_id, Key, Value,其中_id你懂的,Key:Collection名,需要用Long/Int自增长主键的Collection名,value:Key字段中所存Collection的Long/Int自增长最大值),此表用于存入要用Long/Int自增长主键的Collection信息(为方便举例:XLogs)结构自建的Col