2010年5月6日更新: 只有真正懂得了这个道理的人, 才算真正理解了关系数据库. 如何才算懂得了这个道理? - 即使你有一百个理由要用关联主键, 你也能找到这唯一的一个理由放弃, 改而使用单一字段做主键.
------
在数据库设计中, 每一个表都应该有一个字段作为主键. 这个字段一般是自增整数字段, 或者某些数据库支持的自动产生不重复字符串的字段, 也可以是程序自己产生的唯一标识. 总之, 每一个数据库表都应该有一个单一的字段作为主键.
使用单一的字段作为表的主键, 有许多优点. 最重要的一条是可以通过一个原子属性(如整数, 字符串)来标识一行记录. 一旦可以方便地标识一行记录, 那么数据的查询, 更新, 删除也都非常简单.
如果一个表没有主键, 那么必须通过一行记录本身才能标识一行记录. 也就是, 你必须知道一行记录的每一个字段的值, 才能在 SQL 语句中操作这条记录.
数据库表支持联合主键, 也就是使用表的至少2个字段作为主键. 使用联合主键的缺点和不使用主键的缺点有很大的相似性, 因为当联合主键所使用的字段越多, 联合主键的缺点就越来越趋近于不使用主键, 直到表的所有字段作为主键时, 和不使用主键几乎是完全相同的 - 除了前者不能存在重复行,
作为主键的字段一般使用自增整数字段, 因为绝大多数关系型数据库管理系统都支持这种字段类型, 而且速度快, 占用空间少. 一个害怕使用自增整数字段作为主键的理由是自增整数会回绕从而导致重复. 但这种担心是没有必要的. 对于最普通的32位无符号整数, 其最大值是在 Mysql 中是 4294967295(43亿) , 如果数据库表以每秒1条的速度保存记录, 那么一天也只能 86400 条(约10万条)记录, 需要 49710 天(约136年)之后才能发生重复. 幸运的是, 绝大多数系统产生数据的速度比这个假设慢得多了. 如果系统产生数据的速度确实足够快速, 则可以使用 64 位的长整型整数.
如果确实需要联合主键, 则可以使用 UNIQUE 索引来代替. 任何不得不使用联合主键的需求, 都是基于对关系数据库的错误理解而产生的. 并不是要用"单一主键"来覆盖"复合主键"的需求, 而是"在表设计中, 单一主键是必须的, 用联合唯一索引来覆盖'复合主键'的需求".
一个例子:
表1: 使用联合主键(只是作为反面例子出现, 它应该被联合唯一索引代替)
table_a{
field_a: PK;
field_b: PK;
field_...: PK
field_c;
}
表2: 不使用联合主键
table_b{
id: PK;
field_a: UNIQUE;
field_b: UNIQUE;
field_...: UNIQUE
field_c;
}
* 联合主键导致数据操作复杂化
这两种表定义都能反映相同的业务模型. 使用联合主键对保存数据的程序代码影响不大, 情况1通过联合主键保证不会产生重复数据, 但情况2通过联合唯一索引保存不会产生重复数据. 但对查找操作有不良影响, 同时影响到使用了查找功能的更新和删除操作. 考虑查找特定几条记录的情况:
表1: 查找 (field_a, filed_b, ...) = [(1, 2, ...), (1, 3, ...), (2, 4, ...), ...] 这几条记录.
表2: 查找 id = [1, 2, 3, ...] 这几条记录.
显然, 表2的描述更简洁, 从而使程序代码和 SQL 语句更简洁. 如果把所有的字段都作为主键, 那么表1就不存在查找特定几条记录的功能了, "因为已经知道, 所以不必再找". 这是一种最极端的联合主键的副作用, 联合主键的字段越多, 就越趋向于这种极端.
* 联合主键不利于表示"关系"
联合主键不利于表示"关系", 是因为它会造成不必要的冗余. 考虑有另外两个表, 需要分别关联 table_a 和 table_b:
table_ac{
id;
table_a_field_a;
table_b_field_b;
...
}
table_bc{
id;
table_b_id;
}
显然, 不使用联合主键可以减少冗余. 如果使用所有字段作为联合主键, 将变成把 table_a 的数据保存两份, 一份在 table_a, 另一份包含在 table_ac. 个人认为, 联合主键是是关系数据库理论一种概念, 在实际中如果使用是"反关系数据库模型"的, 实际使用应该用联合唯一索引替代.