在我们的数据库设计中,不可逃避的就是数据库表的主键,可能有很多朋友没有深入思考过,主键的设计对整个数据库的设计影响很大,因此我们不得不要重视起来。
主键的必要性 :
在有些数据库中,虽然主键不是必需的,但最好为每个表都设置一个主键,不管是单主键还是复合主键。它存在代表着表结构的完整性,表的记录必须得有唯一区分的字段,主键主要是用于其他表的外键关联,以及本记录的修改与删除。
主键的无意义性 :
在开发过程中,读者可能会看到将一些表使用有意义的字段表示主键,例如“用户登录信息表”将“登录名”(英文名)作为主键,“订单表”中将“订单编号”作为主键,如此设计主键一般都是没什么问题,因为将这些主键基本不具有“意义更改”的可能性。但是,也有一些例外的情况,例如“订单表”需要支持需求“订单可以作废,并重新生成订单,而且订单号要保持原订单号一致”,那将“订单编号”作为主键就满足不了要求了。因此读者在使用具有实际意义的字段作为主键时,需要考虑是否存在这种可能性。
主键的选择
我们现在在思考一下,应该采用什么来作表的主键比较合理,申明一下,主键的设计没有一个定论,各人有各人的方法,哪怕同一个,在不同的项目中,也会采用不同的主键设计原则。
第一:编号作主键
此方法就是采用实际业务中的唯一字段的“编号”作为主键设计,这在小型的项目中是推荐这样做的,因为这可以使项目比较简单化,但在使用中却可能带来一些麻烦,比如要进行“编号修改”时,可能要涉及到很多相关联的其他表,就象黎叔说的“后果很严重” ; 还有就是上面提到的“业务要求允许编号重复时”,我们再那么先知,都无法知道业务将会修改成什么 ?
第二:自动编号主键
这种方法也是很多朋友在使用的,就是新建一个 ID 字段,自动增长,非常方便也满足主键的原则,
优点是:
数据库自动编号,速度快,而且是增量增长,聚集型主键按顺序存放,对于检索非常有利 ;
数字型的,占用空间小,易排序,在程序中传递也方便 ;
如果通过非系统增加记录(比如手动录入,或是用其他工具直接在表里插入新记录,或老系统数据导入)时,非常方便,不用担心主键重复问题。
缺点是:
其实缺点也就是来自其优点,就是因为自动增长,在手动要插入指定 ID 的记录时会显得麻烦,尤其是当系统与其他系统集成时,需要数据导入时,很难保证原系统的 ID 不发生主键冲突(前提是老系统也是数字型的) ;
如果其他系统主键不是数字型那就麻烦更大了,会导致修改主键数据类型了,这也会导致其他相关表的修改,后果同样很严重 ;
就算其他系统也是数字型的,在导入时,为了区分新老数据,可能想在老数据主键前统一加一个“ o ”(old)来表示这是老数据,那么自动增长的数字型又面临一个挑战。
MySQL(auto_increment)、SQL Server(IDENTITY)、Informix、Oracle(首先创建自增序列,接着为自增主键的表创建插入时的触发器,给自增主键ID赋值)等数据库都支持这种自增主键,这种主键在各种系统中应用广泛,但是如果考虑到有新旧系统并存等问题,为了避免不必要的麻烦,使用自增主键要三思。
第三: Max 加一
优点是:
由于自动编号存在那些问题,所以有些朋友就采用自己生成,同样是数字型的,只是把自动增长去掉了,
采用在Insert 时,读取 Max 值后加一,这种方法可以避免自动编号的问题,
缺点是:
但也存在一个效率问题,如果记录非常大的话,那么Max() 也会影响效率的 ;
更严重的是并发性问题,如果同时有两人读到相同的 Max 后,加一后插入的 ID 值会重复,这已经是有经验教训的了。
第四:自制加一
考虑 Max 加一的效率后,有人采用自制加一,也就是建一个特别的表,字段为:表名,当前序列值。这样在往表中插入值时,先从此表中找到相应表的最大值后加一,进行插入,有人可能发现,也可能会存在并发处理,这个并发处理,我们可以采用 lock 线程的方式来避免,在生成此值的时,先 Lock ,取到值以后,再 unLock 出来,这样不会有两人同时生成了。这比 Max 加一的速度要快多了。
但同样存在一个问题:在与其他系统集成时,脱离了系统中的生成方法后,很麻烦保证自制表中的最大值与导入后的保持一致,而且数字型都存在上面讲到的“ o ”老数据的导入问题。因此在“自制加一”中可以把主键设为字符型的。字符型的自制加一我倒是蛮推荐的,应该字符型主键可以应付很多我们意想不到的情况。
1 package com.aspboy.base.database.util; 2 import java.util.HashMap; 3 public class KeyGenerator 4 { 5 private static KeyGenerator keygen=new KeyGenerator(); 6 private static final int POOL_SIZE=20; 7 private HashMap keylist=new HashMap(10); 8 private KeyGenerator() 9 {} 10 11 public static KeyGenerator getInstance() 12 { 13 return keygen; 14 } 15 public synchronized int getNextKey(String keyName) 16 { 17 KeyInfo keyinfo; 18 if (keylist.containsKey(keyName)) 19 { 20 keyinfo=(KeyInfo)keylist.get(keyName); 21 System.out.println("key found"); 22 } 23 else 24 { 25 keyinfo=new KeyInfo(POOL_SIZE,keyName); 26 keylist.put(keyName, keyinfo); 27 System.out.println("new key created"); 28 } 29 return keyinfo.getNextKey(); 30 } 31 32 } 33 34 35 package com.aspboy.base.database.util; 36 import com.aspboy.base.database.DBBean; 37 public class KeyInfo 38 { 39 private int keyMax; 40 private int Keymin; 41 private int nextKey; 42 private int poolSize; 43 private String keyName; 44 //private int times ; 45 46 public KeyInfo(int poolSize,String keyName) 47 { 48 this.poolSize=poolSize; 49 this.keyName=keyName; 50 loadFromDB(); 51 //times++; 52 //System.out.println("get keyvalue from db tiems=="+times); 53 54 } 55 public int getKeyMax() { 56 return keyMax; 57 } 58 public int getKeymin() { 59 return Keymin; 60 } 61 62 private void loadFromDB() 63 { 64 String sql1="update tb_key set KeyValue=KeyValue+"+poolSize+" where keyName='"+keyName+"'"; 65 DBBean.executeSql(sql1); 66 String sql2="select KeyValue from tb_key where keyName='"+keyName+"'"; 67 int keyFromDB=DBBean.getIntSingle(sql2); 68 keyMax=keyFromDB; 69 Keymin=keyFromDB-poolSize+1; 70 nextKey=Keymin; 71 } 72 public int getNextKey() { 73 74 if (nextKey>keyMax) 75 { 76 loadFromDB(); 77 System.out.println("get keyvalue from db"); 78 79 } 80 return nextKey++; 81 } 82 83 84 } 85 86 87 88 调用 方法: KeyGenerator.getInstance().getNextKey(keyName); 89 90 91 keyName 为表名称 92 93 94 相关数据库结构: 95 96 表tb_key 97 98 字段如下: 99 100 KeyValue:pk大小 101 102 keyName:表名 103