每个表都应该有一个单字段代理/人工主键吗?

Jac*_*las 36 database-design primary-key surrogate-key

我了解代理/人工密钥的一个好处 - 它们不会改变,而且非常方便。无论它们是单场还是多场,都是如此——只要它们是“人造的”。

但是,有时将自动递增的整数字段作为每个表的主键似乎是一个策略问题。拥有这样一个单字段键是否总是最好的主意,为什么(或为什么不)?

需要明确的是,这个问题不是关于人工 vs 自然的——而是关于是否所有人工键都应该是单字段的

Joe*_*own 30

我会说不,并非总是如此,但大多数时候是的。.

在某些情况下,您不需要代理或人工密钥:

  • 纯交表。如果没有交集成为外键目标的风险,并且交集吸引独立属性的风险很小或没有风险(即两个父表的 FK 以外的其他东西),那么您可以使用组合以公平的信心将 FK 作为 PK。
  • 使用静态业务键查找表。如果您有一个
    包含唯一业务密钥的查找表,该业务密钥固定在您的
    业务外部,并且出于任何 实际目的而更改的可能性为零
    ,那么直接使用业务密钥可以使
    事情变得更简单。一个示例可能是州或省
    代码列表或 ANSI 标准编号列表等。
  • 包含从多个独立来源合并的数据的表格。如果您的系统有许多数据源必须硬塞到一个表中,比如在总部,那么有时您需要一个复合键,其中包括源系统键值和指示源系统是什么的代码。

在某些情况下,老式的单调递增整数代理键并不理想。您可以拥有作为字母数字代理项的键。这些可能包括:

  • 您需要合并来自多个独立来源的数据的情况。为了避免键冲突,您可以使用 GUID 而不是 IDENTITY 键。
  • 您被迫使用非数字键表示的情况。假设您有一个车牌数据库。您的键可以是字母数字值而不是纯数字。
  • 某些外部要求迫使您对关键值应用压缩的情况。您可以使用六个基数 36 位,而不是为 int32 使用 10 位数字。

为什么大多数时候是的?这个问题最基本的答案是,如果您需要修改任何表上的主键值,那简直就是地狱。由于几乎用户可以看到或触摸的任何东西都可能会在某个时候进行更新,因此使用可见的键值会带来纯粹的地狱。使用代理键可以防止您落入这个陷阱。

话虽如此,请记住,YAGNI 在应用此概念时仍有空间。您不需要将带有 IDENTITY 键的代码表强制放入架构的每个角落和缝隙中,以防万一有人决定您的员工表中的男性性别符号需要从 M 更改为 X 或一些愚蠢的事情。


gbn*_*gbn 14

“这取决于”

是:当自然键很宽且非数字时,代理 IDENTITY/AUTONUMBER 字段很好。注意:这假设 SQL Server 和 Sybase 等中默认发生的“PK”和聚集索引的合并

否:当 2 个父键足够时,很多/很多表。或者当自然键较短且长度固定时,例如货币代码

当然,脑残的 ORM(阅读:(n)Hibernate)可能胜过这些规则......

编辑:再次阅读问题

具有 2 个代理父键的多/多表将具有多列 PK。
但是,它不需要另一个代理列。

如果表确实有代理(IDENTITY 等)键,则它不需要是多列。

您可以拥有一个包含代理的超级键,但这将强制执行其他规则(例如subtypes


Jac*_*las 12

不。

我会说肯定存在单字段键不如复合键的情况,至少对于外键而言是这样。这并不是说如果您愿意,您也不应该有单字段代理键,但我个人更喜欢最常用作外键目标的键称为主键

我将尝试在以下示例中说明我的观点,其中:

  • brand 是汽车品牌,例如福特、丰田等
  • dealer 是与品牌相关的实体经销商(例如仅销售福特汽车的福特经销商)
  • model 是汽车类型,例如福特福克斯、福特嘉年华等
  • stock 是每个经销商的当前前院汽车数量

如果我们为dealer和创建一个单字段代理键,model如下所示:

create table brand( brand_id integer primary key );

create table dealer( dealer_id integer primary key, 
                     brand_id integer references brand )

create table model( model_id integer primary key, 
                    brand_id integer references brand )

create table stock( model_id integer references model, 
                    dealer_id integer references dealer, 
                    quantity integer,
                      primary key(model_id, dealer_id) )
Run Code Online (Sandbox Code Playgroud)

那么就可以插入一行stock,将福特dealer与“丰田”模型联系起来。添加brand_id references brandstock只会使问题变得更糟。另一方面,如果我们将外键作为主键的一部分,如下所示:

create table brand( brand_id integer primary key );

create table dealer( brand_id integer references brand, 
                     dealer_id integer, 
                       primary key(brand_id, dealer_id) )

create table model( brand_id integer references brand, 
                    model_id integer, 
                      primary key(brand_id, model_id) )

create table stock( brand_id integer, 
                    model_id integer, 
                    dealer_id integer, 
                    quantity integer,
                      primary key(brand_id, model_id, dealer_id),
                      foreign key(brand_id, model_id) references model,
                      foreign key(brand_id, dealer_id) references dealer )
Run Code Online (Sandbox Code Playgroud)

现在,“福特”经销商只能存货“福特”汽车的规则是由模型自然执行的。

请注意,在“复合键”示例中dealer_id,根据偏好可能是唯一的,也可能不是唯一的。它不需要是唯一的(即备用键),但是通过这样做(可能是一点存储空间)损失很少,并且它可以非常方便,所以这就是我通常设置它的方式,例如:

create table dealer( brand_id integer references brand, 
                     dealer_id serial unique, 
                       primary key(brand_id, dealer_id) )
Run Code Online (Sandbox Code Playgroud)

  • 当然,DRI 强制执行业务规则。您必须决定将在代码中实施哪些规则以及在您的架构中实施哪些规则。架构更改几乎总是比代码更改更难。有两种业务规则可以进入您的数据模型。一种属于那里,一种不属于。对您的业务很重要的数据的基本性质不会发生太大变化。您对该数据进行操作的具体方式_更加不稳定_。像汽车有制造商这样的规则属于数据模型。像经销商永远不会出售两个品牌的汽车这样的规则不会。 (4认同)
  • “架构更改几乎总是比代码更改更难” IMO 恰恰相反。实际上,我几乎不同意您刚才所说的所有内容,但我怀疑与您争论是否有任何意义,因此我将就此搁置。 (4认同)
  • 考虑到关于示例的通常条件不一定从各个角度都完美,我会说这种类型的设计特别脆弱。虽然找到一种使用 DRI 来执行业务规则的方法是令人满意的,但它也剥夺了您响应变化的一些能力。如果 Toyota 购买 Ford,或者即使 Ford 经销商决定出售二手 Toyota,您的 DRI 驱动的业务规则也会让您头疼。 (3认同)