【问题标题】:When to use one field as primary key instead of 2?何时使用一个字段而不是 2 个作为主键?
【发布时间】:2012-12-20 02:20:59
【问题描述】:
我经常看到一些这样的数据库设计:
案例一:
用户表
--id[自动增加]
--用户名
--密码
--电子邮件
案例 2:
用户表
--用户名
--密码
--电子邮件
角色表:
--角色ID
--角色名
用户表角色:
--id[自动增加]
--用户名
--角色ID
我有如下问题:
在案例 1 中:
为什么不使用 UserName 字段作为主键 (PK)?为什么要使用另一个像 id [自动增加的] 作为 PK 的文件?
如果只有 UserName 和 Email,为什么不使用 Email 作为 PK?
那么,最好的方法是什么?
在案例 2 中:
在 UserRoleTable 中,为什么不同时使用 UserName 和 RoleID 作为 PK?为什么要使用另一个文件,如 id [自动增加] 作为 PK?
那么,在这种情况下,最好的方法是什么?
【问题讨论】:
标签:
database
database-design
【解决方案1】:
案例 1:为什么不使用 UserName 字段作为主键 (PK)?为什么要使用另一个提交的like id [自动增加的] 作为PK?
UserTable.UserName 在此数据模型中具有内在含义,称为“自然键”。另一方面,UserTable.id 是“代理键”。
如果你的模型中有一个自然键,你不能用代理键来消除它,你可以直接替换它。所以问题是:你只使用自然键,还是自然 and 代理键?这两种策略实际上都是有效的,各有利弊。
代理键的典型原因:
- 使 子表 中的 FK 更小(在本例中为整数与字符串),以实现更小的存储空间和更好的缓存。
- 无需 ON UPDATE CASCADE。
- 对 ORM 工具的友好度。
另一方面:
- 您现在有两个键而不是一个,需要一个额外的索引,使 父表 更大且对缓存不太友好,并且由于索引维护而减慢了 INSERT/UPDATE//DELETE。1
- 可能需要更多的 JOIN-ing2。
- 并且可能无法与clustering 配合使用。3
如果只有UserName和Email,为什么不使用Email作为PK呢?
设计者可能希望避免在用户更改电子邮件时需要的 ON CASCADE UPDATE。
案例2:在UserRoleTable中,为什么不同时使用UserName和RoleID作为PK?
如果同一用户/角色对不能有多个连接,则无论如何您都必须有一个密钥。
除非有 FK 引用 UserTableRole 的子表或使用了不友好的 ORM,否则没有理由需要额外的代理 PK。
1 如果使用集群,自然键下的二级索引可能会额外“胖”(因为它包含集群键的副本,通常是 PK)和查询时需要双重查找(因为聚簇表中的行没有稳定的物理位置,所以必须通过聚簇键定位,除非一些 DBMS 特定的优化,如 Oracle 的“rowid 猜测”)。
2 例如仅通过读取联结表您将无法找到 UserName - 您必须使用 UserTable 加入它。
3 代理通常以对客户端应用程序没有意义的方式排序。自动增量代理键的顺序取决于 INSERT 的顺序,并且查询通常不会在“按插入顺序的用户范围”上进行。诸如 GUID 之类的一些代理可能是随机排序的。
【解决方案2】:
我能想到的不使用像 UserName 这样的东西作为主键的一个原因是它们可能会发生变化。将任何暴露给外界的东西作为主键都会冒着这些东西被改变的风险,最好有一个稳定的主键。
如果用户更改了电子邮件或用户名怎么办;你真的想改变你所有关系中的钥匙吗? IMO,最好有一个永远不会看到外界的稳定密钥,每个人都对此一无所知,因此无论数据库中可能发生什么变化,它都可以保持稳定。
【解决方案3】:
你的问题本质上是使用natural vs surrogate key的优缺点。
灵活性是首要考虑因素,使用代理键可以更轻松地更改其用户名。将来您可能需要允许重复的用户名,例如合并。
速度是另一个问题,在像用户表这样经常访问的表上,对整数进行连接通常比对字符串进行连接要快。
另一个是表大小,当用作外键时,您必须存储整个键的值。代理非常紧凑,比自然键小得多。
大多数 ORM 还需要使用代理,因为它提供了表之间的一致性。
此外,在许多系统上,假设电子邮件是唯一的可能不一定安全。
我同意,尽管在像 UserRole 这样的关系表中,通常最好使用外键中的主复合键。
【解决方案4】:
在您的示例中,我可以想到在用户名上使用代理主键 (Id) 的几个原因。
- 如果有的话,id 字段很少会更新。如果用户名是主键,则必须在更新时级联使用用户名作为外键的所有表。
- 性能。 int 比较优于字符串比较。
- id 键在其他表中作为外键时占用的存储空间更少。
- id 字段允许您不暴露可能敏感的数据。例如。考虑一个网络应用程序 url domain/posts/user/1242 vs domain/posts/user/myusername
对于第二个问题,使用 userid 比使用 UserTableRole 中的用户名更好。对于这个多对多表是否也包含一个代理键是否更好,这是一个见仁见智的问题。我讨厌为多对多的表使用代理 id 键,并且通常只制作两个外键 id 的复合主键。我唯一会考虑使用代理键的情况是,如果我需要在另一个表中将其用作外键。