获取三层信息:
该层包含具有UNIQUE自然索引的数据和可轻松转移的代理键.
Table Surnames:
+-----------------------------+--------------+
| ID (Auto Increment, PK) | Surname |
+-----------------------------+--------------+
| 1 | Smith |
| 2 | Edwards |
| 3 | Brown |
+-----------------------------+--------------+
Table FirstNames
+-----------------------------+--------------+
| ID (Auto Increment, PK) | FirstName |
+-----------------------------+--------------+
| 1 | John |
| 2 | Bob |
| 3 | Mary |
| 4 | Kate |
+-----------------------------+--------------+
Run Code Online (Sandbox Code Playgroud)
自然键
或者,上面的两个表可以没有,ID并使用Surname和FirstName作为自然主键,正如Mike Sherrill所解释的那样.在这种情况下,假设下面的层引用varchar而不是int.
在该层中,使用复合索引.该值可以是UNIQUE或PRIMARY,取决于是否将代理键用作主键.
+-----------------+--------------+
| FirstName | LastName |
+-----------------+--------------+
| 1 | 2 |
| 1 | 3 |
| 2 | 3 |
| 3 | 1 |
| 4 | 2 |
| ... | ... |
+-----------------+--------------+
Run Code Online (Sandbox Code Playgroud)
在这一层中,通过ParentsOf表格探索人与人之间的关系.
ParentsOf
+-----------------+-----------------+
| Person | PersonParent |
+-----------------+-----------------+
OR
+-----------------+-----------------+-----------------+-----------------+
| PersonFirstName | PersonSurname | ParentFirstName | ParentSurname |
+-----------------+-----------------+-----------------+-----------------+
Run Code Online (Sandbox Code Playgroud)
假设引用完整性对我来说非常重要,我将拥有FOREIGN KEYS这些索引,以便我在这方面保持数据库负责监视自己的完整性,如果我要使用ORM,它会像Doctrine一样对复合主键有本机支持......
请帮我理解:
使用代理键与第一层上的自然键进行权衡的列表.
使用复合键与第二层上的代理键进行权衡的列表,可以转移到第三层.
我对听到哪个更好更感兴趣,因为我知道专业人士对这一主题存在重大分歧,这将引发一场宗教战争.相反,我非常简单地并且尽可能客观地要求通过将代理键传递给每个层与维护主键(自然/复合或代理/复合)来进行权衡.任何人都可以找到一个人说不要或总是在SO和其他网站上使用代理键.相反,对你的答案进行合理的权衡分析是我最欣赏的.
编辑:有人指出,一个姓氏的例子是使用6NF的一个不好的例子.为了保持问题的完整性,我将离开它.如果您无法想象这个用例,那么更好的一个可能是"Grocery Items"列表.又名:
+-----------------------------+--------------+
| ID (Auto Increment, PK) | Grocery |
+-----------------------------+--------------+
| 1 | Sponges |
| 2 | Tomato Soup |
| 3 | Ice Cream |
| 4 | Lemons |
| 5 | Strawberries |
| 6 | Whipped Cream|
+-----------------------------+--------------+
+-----------------------------+--------------+
| ID (Auto Increment, PK) | Brand |
+-----------------------------+--------------+
| 1 | Bright |
| 2 | Ben & Jerry's|
| 3 | Store Brand |
| 4 | Campbell's |
| 5 | Cool Whip |
+-----------------------------+--------------+
Run Code Online (Sandbox Code Playgroud)
自然复合键示例:
+-----------------------------+--------------+
| Grocery | Brand |
+-----------------------------+--------------+
| Sponges | Bright |
| Ice Cream | Ben & Jerry's|
| Ice Cream | Store Brand |
| Tomato Soup | Campbell's |
| Tomato Soup | Store Brand |
| Lemons | Store Brand |
| Whipped Cream | Cool Whip |
+-----------------------------+--------------+
Run Code Online (Sandbox Code Playgroud)
推荐配对
+-----------------+-----------------+-----------------+-----------------+
| Grocery1 | Brand1 | Grocery2 | Brand2 |
+-----------------+-----------------+-----------------+-----------------+
Run Code Online (Sandbox Code Playgroud)
重申一下,这也只是一个例子.这不是我建议继续进行的方式,但它应该有助于说明我的问题.
这种方法存在不足之处.我将重申,这个问题是要求了解下面每种方法的优点和缺点,而不是强调一个比另一个更好.我相信大多数人都能够回顾这个具体例子的可疑性质来回答核心问题.此编辑适用于那些不能编辑的编辑.
下面有一些非常好的答案,如果你对要走哪个方向感到好奇,请阅读它们.
结束编辑
谢谢!
Bri*_*ity 23
这是一些权衡:
所有子表外键只需要一列来引用主键.
很容易更新表中的自然键,而无需使用外键更新每个子表
较小的主/外键索引(即不宽)这可以使数据库运行得更快,例如,当在父表中删除记录时,需要搜索子表以确保不会创建孤立.窄索引扫描速度更快(只是轻微).
您将拥有更多索引,因为您很可能还希望索引数据中存在的任何自然键.
数据库中的索引更少
数据库中的列数较少
更容易/更快地插入大量记录,因为您不需要抓取序列生成器
更新复合中的一个键需要更新每个子表.
我只发现了一个有意义的实例.当您需要标记每个表中的每个记录以获得行级安全性时.
例如,假设您有一个存储50,000个客户端数据的数据库,并且每个客户端都不应该看到其他客户端的数据 - 这在Web应用程序开发中很常见.
如果每个记录都标记了一个client_id字段,那么您将创建一个行级安全环境.大多数数据库都具有在正确设置时强制执行行级安全性的工具.
首先要做的是设置主键和外键.通常是一个以id字段为主键的表.通过添加client_id键现在是复合键.并且必须携带client_id到所有儿童桌.
组合密钥基于2个代理键,是一种确保客户端和数据库整体数据完整性的防弹方法.
在此之后,您将创建视图(或者如果使用Oracle EE设置虚拟专用数据库)和其他各种结构,以允许数据库强制执行行级安全性(这是它拥有的主题).
假设此数据结构不再标准化为n度.client_id每个pk/fk中的字段使其他正常模型非规范化.该模型的好处是易于在数据库级别强制执行行级安全性(数据库应该这样做).每个选择,插入,更新,删除都限制为client_id您当前设置的会话.数据库具有会话感知功能.
代理键始终是安全的选择.它们需要更多的工作来设置并需要更多存储空间.
我认为最大的好处是:
能够更新一个表中的PK和所有其他子表,无需触摸即可立即更改.
当数据混乱时 - 由于编程错误,它会在某些时候出现,代理键使得清理变得容易得多,在某些情况下只能进行清理,因为有代理键.
由于数据库能够搜索属性以查找s.key,然后通过单个数字键连接所有子表,因此查询性能得到了提高.
Natural Keys特别是复合NKeys使编写代码变得痛苦.当您需要连接4个表时,"where子句"将比使用单个SKeys时更长(并且更容易搞乱).
代理键是"安全"的路线.自然键在一些地方是有益的,我会说数据库中大约1%的表.
首先,您的第二层可以至少以四种不同的方式表达,并且它们都与您的问题相关.下面我使用的是伪SQL,主要是使用PostgreSQL语法.无论结构如何,某些类型的查询都需要递归和多个附加索引,因此我不再赘述.使用支持聚簇索引的dbms可以影响这里的一些决策,但是不要假设聚簇索引上的六个连接比简单地从单个覆盖索引中读取值更快; 测试,测试,测试.
其次,第一层的权衡确实不多.外键可以引用声明的列not null unique,它们可以引用声明的列primary key.代理键将表的宽度增加4个字节; 这对于大多数(但不是全部)数据库应用程序来说都是微不足道的.
第三,正确的外键和唯一约束将在所有这四种设计中保持参照完整性.(但请参见下文"关于瀑布".)
A.代理密钥的外键
create table people (
FirstName integer not null
references FirstNames (ID),
LastName integer not null
references Surnames (ID),
primary key (FirstName, LastName)
);
Run Code Online (Sandbox Code Playgroud)
B.自然键的外键
create table people (
FirstName varchar(n) not null
references FirstNames (FirstName),
LastName varchar(n) not null
references Surnames (Surname),
primary key (FirstName, Surname)
);
Run Code Online (Sandbox Code Playgroud)
C.代理钥匙的外键,额外的代理钥匙
create table people (
ID serial primary key,
FirstName integer not null
references FirstNames (ID),
LastName integer not null
references Surnames (ID),
unique (FirstName, LastName)
);
Run Code Online (Sandbox Code Playgroud)
D.自然键的外键,额外的代理键
create table people (
ID serial primary key,
FirstName varchar(n) not null
references FirstNames (FirstName),
LastName varchar(n) not null
references Surnames (Surname),
unique (FirstName, Surname)
);
Run Code Online (Sandbox Code Playgroud)
现在让我们看看ParentsOf表.
A.上面A中代理键的外键
create table ParentsOf (
PersonFirstName integer not null,
PersonSurname integer not null,
foreign key (PersonFirstName, PersonSurname)
references people (FirstName, LastName),
ParentFirstName integer not null,
ParentSurname integer not null,
foreign key (ParentFirstName, ParentSurname)
references people (FirstName, LastName),
primary key (PersonFirstName, PersonSurname, ParentFirstName, ParentSurname)
);
Run Code Online (Sandbox Code Playgroud)
要检索给定行的名称,您需要四个连接.您可以直接加入"FirstNames"和"Surnames"表; 您不需要通过 "人员"表加入以获取名称.
B.上面B中自然键的外键
create table ParentsOf (
PersonFirstName varchar(n) not null,
PersonSurname varchar(n) not null,
foreign key (PersonFirstName, PersonSurname)
references people (FirstName, LastName),
ParentFirstName varchar(n) not null,
ParentSurname varchar(n) not null,
foreign key (ParentFirstName, ParentSurname)
references people (FirstName, LastName),
primary key (PersonFirstName, PersonSurname, ParentFirstName, ParentSurname)
);
Run Code Online (Sandbox Code Playgroud)
此设计需要零连接来检索给定行的名称.许多SQL平台根本不需要读取表,因为它们可以从主键上的索引获取所有数据.
C.替代密钥的外键,C中的附加代理键,上面
create table ParentsOf (
Person integer not null
references People (ID),
PersonParent integer not null
references People (ID),
primary key (Person, PersonParent)
);
Run Code Online (Sandbox Code Playgroud)
要检索名称,您必须通过 "人员"表加入.你需要总共六个连接.
D.自然键的外键,D中的附加代理键,上面
该设计具有与上面C中相同的结构.因为D中的"people"表格更远,具有引用表"FirstNames"和"Surnames"的自然键,所以你只需要两个连接到表"people"来获取名称.
关于ORM
ORM不像SQL开发人员编写SQL那样构建SQL.如果SQL开发人员编写需要六个连接来获取名称的SELECT语句,则ORM可能会执行七个更简单的查询来获取相同的数据.这可能是一个问题; 它可能不会.
关于瀑布
代理ID号使每个外键引用一个隐含的,未声明的"ON UPDATE CASCADE".例如,如果您对姓氏表运行此更新语句...
update surnames
set surname = 'Smythe'
where surname = 'Smith';
Run Code Online (Sandbox Code Playgroud)
然后所有史密斯都将成为史密斯.防止这种情况的唯一方法是撤消对"姓氏"的更新权限.隐含的,未声明的"ON UPDATE CASCADE"并不总是一件好事.撤销权限仅仅是为了防止不必要的隐含"级联"并不总是一件好事.