谭明智的空间

我们一直在努力....

OECP中主键使用及生成策略探索

标签: 数据库 主键 JPA hibernate 生成策略

 

OECP项目物理模型的架构设计的过程中,主键存储类型和生成方式的选择成了项目成员争论最激烈的部分。对于存储类型主要采用整形和字符型两种方式,有的成员倾向于统一所有的主键类型为字符型,整个系统采用统一的标准,方便系统的开发、整合。而最后我们还是决定根据实际情况,采用不同的主键类型。但是什么时候使用整形?什么时候采用字符型?字符型需要多少位?主键的生成应该采用什么方式?这都是需要我们仔细讨论的问题。
考虑这些问题无非从高效性和易用性上进行考虑。以下是几种主键生成策略的比较。
下表列出四种主键生成方式优缺点的比较:
主键生成策略
优点
缺点
自动增长字段
1. 使用简单
1. 不同数据库获取当前值方式不同;
2. 难以应用在多个数据库间进行数据迁移的情况。
3.不能集群化
手动增长型字段
1.可以获得最新键值
2. 可以确保数据合并过程中不会出现键值冲突
1.通常情况下需要建立一张单独的表存储当前主键键值;
2.增加一次数据库访问来获取当前主键键值;
3. 考虑并发冲突等,增加系统的复杂程度。
4. 不能集群化
使用GUID
1. 直接生成GUID,获得最新键值以填充主键,使用方便;
2.可以确保数据合并过程中不会出现键值冲突;
3.  避免了前两种方式获取当前键值所增加的开销。
1.占用较多存储空间;
2.索引耗时;
3. 在多表链接查询时效率不如int
使用“COMB”类型
1. 保留GUID的已有优点;
2. 利用时间信息与GUID组合起来,增加有序性以提高索引效率。
1.需要设计COMB的生成算法;
2. GUID一样占用较多存储空间;
3. 在多表链接查询时效率不如int型,但优于GUID
从上表的对比中可以看出,问题的焦点还是在是采用高效的,但可控性、可移植性差的整形,还是采用能使用GUID这样可控性和移植性高,但是效率低,存储大的字符型主键,真有点鱼和熊掌不能兼得的味道。(COMB需要设计生成算法,增加程序的复杂度,如果算法不当,会产生意想不到的结果,GUID也可以通过优化索引的方式提升性能,暂不使用COMB
从数据库的角度来看,整形虽然查询的效率最高,但是数据的合并、移植存在着很大的问题,同时高并发的情况下,各种整形的生成方式都面临这问题,而且不利于集群化处理。而采用GUID生成方式的字符型,能很好解决集成和并发性的问题,但占用空间大,查询效率低可能成为系统运行后将出现的问题。
从程序开发的角度上看,整形生成方式的生成主键非常方便,但是主键的获取,需要整个事务结束,才能从数据库中取到,同时在多关联表保存的时候,需要先保存主表,将产生的主键传给字表,从而也可以造成性能的缺失,并且无法直接获取主键,会增加程序开发处理的复杂性。而字符型的主键,需要程序人员自定义主键生成规则,需要认为的干预主键的生成,但是主键可以在插入数据库之前就能拿到,方便程序的处理。
从系统数据的角度来看,业务数据可能存在大量的并发,采用GUID的方式是非常方便的,在数据级别很大的情况下,可以方便的进行集群化处理。档案型数据并发量小,但是被引用的多,数据合并和集成的情况也很多,完全使用整形是不合适的,完全采用GUID,又会引起性能的缺失,需要更加折中的方案,既保证使用可控性较强的能唯一标识的字符串,同时又要尽量降低字符串占得字节数。而对于系统辅助数据,根据实际情况灵活使用,不做硬性统一,在数据量较小的情况下,尽量采用整形。
结合项目中使用的JPA+Hibernate的特点,在主键的使用和生成策略上,初步确定采用一下的方案:
1、          对于大业务量的数据,采用UUID的主键生成策略。
2、          对于没有太多关联查询的数据,采用UUID的主键生成策略。
3、          对于使用数字型的主键生成,采用JPAtable生成策略,而不使用依赖数据库的生成策略。
4、          对于档案型的数据(主数据),采用字符型的主键,长度定为10,采用自定义的主键生成方式。(后面附我的简单实现)
5、          尽量避免使用联合主键的方式,统一采用无意义的逻辑主键。
Robbin的意见:
Hibernate来说,一个PO状态的判定完全依赖于主键属性的值,甚至很多PO的隐形的级联操作,例如关联对象的是否级联增加/更新这些判定也完全依赖于主键属性的值,所以主键属性值的维护对于Hibernate能否正确的运行,正确的持久化数据至关重要。

如果当你使用无意义的逻辑主键的时候,主键的维护完全是由Hibernate自动进行的,你无须关注主键的维护,自然就避免了很多问题的产生;而如果你选择自己手工维护主键(联合主键就必须手工维护),所有的这些维护主键的重任都必须由你来负责,你必须小心翼翼的编程,避免造成无法正确持久化,对于一个不是非常精通Hibernate的人来说,这通常比较难达到,更何况在分层架构中,Web层程序员仅仅操作DAO接口层,他更加不了解PO状态维护的个中微妙之处,极易犯错误那也是在所难免。

所以采用无意义的逻辑主键一定Hibernate的首选。
 
附自定义主键生成策略(初步思路):
Java代码
  1. /**  
  2.  * OECP平台的主键生成器,根据当前的时间生成10位的ID  
  3.  *   
  4.  * @author yongtree  
  5.  * @date 2009-6-29 上午09:33:24  
  6.  * @version 1.0  
  7.  */  
  8. public class OecpID {   
  9.     private static final Lock LOCK = new ReentrantLock();   
  10.     private static long lastTime = System.currentTimeMillis();   
  11.   
  12.     public static String getID() {   
  13.         LOCK.lock();   
  14.         boolean done = false;   
  15.         try {   
  16.   
  17.             while (!done) {   
  18.                 long now = System.currentTimeMillis();   
  19.                 if (now == lastTime) {   
  20.                     try {   
  21.                         Thread.currentThread();   
  22.                         Thread.sleep(1); // 如果重复,则线程延迟1毫秒   
  23.                     } catch (InterruptedException e) {   
  24.                         e.printStackTrace();// 不做任何处理   
  25.                     }   
  26.                     continue;   
  27.                 } else {   
  28.                     lastTime = now;   
  29.                     done = true;   
  30.                 }   
  31.   
  32.             }   
  33.         } finally {   
  34.             LOCK.unlock();   
  35.         }   
  36.         return buildID(Long.toString(lastTime, 32));//将 十进制的long整形转化为32进制的字符串   
  37.     }   
  38.   
  39.     /**  
  40.      * 随机得到32进制以外的几个字母中的一个字母  
  41.      * @author yongtree  
  42.      * @date 2009-6-29 上午10:56:15  
  43.      * @modifyNote  
  44.      * @return  
  45.      */  
  46.     private static char randChar() {   
  47.         char[] chars = { 'w''x''y''z' };   
  48.         Random random = new Random();   
  49.         random.nextInt(4);   
  50.         return chars[random.nextInt(4)];   
  51.     }   
  52.   
  53.     /**  
  54.      * 组装10位字符串的编号  
  55.      * @author yongtree  
  56.      * @date 2009-6-29 上午10:58:37  
  57.      * @modifyNote  
  58.      * @param id  
  59.      * @return  
  60.      */  
  61.     private static String buildID(String id) {   
  62.         if (id.length() <= 10) {   
  63.             int length = (10 - id.length());   
  64.             if (length > 0) {   
  65.                 for (int i = 0; i < length; i++) {   
  66.                     id = randChar() + id;   
  67.                 }   
  68.             }   
  69.         } else {   
  70.             id.substring(010);   
  71.         }   
  72.         return id;   
  73.     }   
  74.   
  75. }   
 
参考资料:
Guid Int 作为系统编号的取舍
HIBERNATE为什么不提倡用联合主键
GUID 當做主鍵(PK)對於效率的影響
数据库主键设计之思考
再议《反驳吕震宇的小议数据库主键选取策略(原创)

    评分: 请先登录再投票,同一篇博客一月只能投票一次!
    无人投票

相关博客:


评论


发表评论

关注此文的人们还关注