获取三层信息:
此图层包含具有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 |
+-----------------------------+--------------+
自然键
或者,上面的两个表可以没有ID
,并使用Surname和FirstName作为自然主键,正如Mike Sherrill所解释的那样。在这种情况下,假设下面的图层引用varchar
而不是int
。
在此图层中使用复合索引。此值可以是UNIQUE
或PRIMARY
,具体取决于是否将代理键用作主键。
+-----------------+--------------+
| FirstName | LastName |
+-----------------+--------------+
| 1 | 2 |
| 1 | 3 |
| 2 | 3 |
| 3 | 1 |
| 4 | 2 |
| ... | ... |
+-----------------+--------------+
在此层中,通过ParentsOf
表格探索人与人之间的关系。
ParentsOf
+-----------------+-----------------+
| Person | PersonParent |
+-----------------+-----------------+
OR
+-----------------+-----------------+-----------------+-----------------+
| PersonFirstName | PersonSurname | ParentFirstName | ParentSurname |
+-----------------+-----------------+-----------------+-----------------+
假设引用完整性对我来说非常重要,我会在这些索引上有FOREIGN KEYS
,这样我就可以让数据库负责监视它自己在这方面的完整性,如果我是使用ORM,它会像Doctrine那样对复合主键有原生支持......
请帮助我理解:
利用代理键与第一层上的自然键进行权衡的清单。
使用复合键与第二层上的代理键进行权衡的清单,可以转移到第三层。
我对听到哪个更好更感兴趣,因为我知道专业人士对这一主题存在重大分歧,这将引发一场宗教战争。相反,我非常简单地并且尽可能客观地要求通过将代理键传递给每个层与维护主键(自然/复合或代理/复合)来进行权衡。任何人都可以找到某人说 从不 或 始终 在SO和其他网站上使用代理键。相反,我会在你的答案中最理解的是对权衡的理性分析。
编辑:有人指出,一个姓氏的例子是使用6NF的一个不好的例子。为了保持问题的完整性,我将离开它。如果您无法想象这个用例,那么更好的一个可能是" Grocery Items"的列表。 AKA:
+-----------------------------+--------------+
| 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 |
+-----------------------------+--------------+
自然复合键示例:
+-----------------------------+--------------+
| 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 |
+-----------------------------+--------------+
推荐配对
+-----------------+-----------------+-----------------+-----------------+
| Grocery1 | Brand1 | Grocery2 | Brand2 |
+-----------------+-----------------+-----------------+-----------------+
重申一下,这也只是一个例子。这不是我建议继续进行的方式,但它应该有助于说明我的问题。
这种方法存在不足之处。我将重申,这个问题是要求了解下面每种方法的优点和缺点,而不是强调一个比另一个更好。我相信大多数人都能够回顾这个具体例子的可疑性质来回答核心问题。此编辑适用于那些不能编辑的编辑。
下面有一些非常好的答案,如果您对要走哪个方向感到好奇,请阅读它们。
结束编辑
谢谢!
答案 0 :(得分:23)
这里有一些权衡:
所有子表外键只需要一列来引用主键。
非常容易更新表中的自然键,而无需使用外键更新每个子表
较小的主键/外键索引(即不宽)这可以使数据库运行得更快,例如在父表中删除记录时,需要搜索子表以确保这一点不会造成孤儿。窄索引扫描速度更快(只是轻微)。
您将拥有更多索引,因为您很可能还想索引数据中存在的任何自然键。
数据库中的索引更少
数据库中的列数减去
更容易/更快地插入大量记录,因为您不需要抓取序列生成器
更新复合中的一个键需要更新每个子表。
我只发现了一个有意义的实例。当您需要标记每个表中的每个记录以获得行级安全性时。
例如,假设您有一个存储50,000个客户端数据的数据库,并且每个客户端都不应该看到其他客户端的数据 - 这在Web应用程序开发中很常见。
如果每条记录都标有client_id
字段,则表示您正在创建行级安全环境。大多数数据库都具有在正确设置时强制执行行级安全性的工具。
首先要做的是设置主键和外键。通常是具有id
字段作为主键的表。通过添加client_id
,密钥现在是复合密钥。并且必须将client_id
带到所有子表。
组合密钥基于2个代理键,是一种确保客户端和数据库整体数据完整性的防弹方法。
在此之后,您将创建视图(或者如果使用Oracle EE设置虚拟专用数据库)和其他各种结构,以允许数据库强制执行行级安全性(这是它拥有的主题)。
假设此数据结构不再标准化为n度。每个pk / fk中的client_id
字段使其他正常模型非规范化。该模型的好处是易于在数据库级别强制执行行级安全性(数据库应该这样做)。每个选择,插入,更新,删除都限制为当前设置的client_id
会话。该数据库具有会话感知。
代理钥匙永远是安全的赌注。它们需要更多的工作来设置并需要更多存储空间。
我认为最大的好处是:
能够更新一个表中的PK,并且所有其他子表都可以立即更改,而不会被触及。
当数据混乱时 - 由于编程错误,它会在某些时候出现,代理键会使清理变得更加容易,在某些情况下只能执行,因为有代理键。
查询性能得到改善,因为数据库能够搜索属性以找到s.key,然后通过单个数字键连接所有子表。
Natural Keys尤其是复合NKeys使编写代码变得痛苦。当你需要加入4个表时," where子句"与使用单个SKeys相比,它会更长(并且更容易搞乱)。
代理键是"安全"路线。自然键在一些地方是有益的,我说数据库中大约有1%的表。
答案 1 :(得分:7)
首先,您的第二层可以至少以四种不同的方式表达,并且它们都与您的问题相关。下面我使用伪SQL,主要是使用PostgreSQL语法。某些类型的查询将需要递归和多个额外的索引,无论结构如何,所以我不再对此有所说明。使用支持聚簇索引的dbms可以影响这里的一些决策,但是不要假设聚簇索引上的六个连接比简单地从单个覆盖索引中读取值更快;测试,测试,测试。
其次,第一层确实没有多少权衡。外键可以引用声明为not null unique
的列,其方式与引用声明为primary key
的列的方式完全相同。代理键将表的宽度增加4个字节;对于大多数(但不是全部)数据库应用程序而言,这是微不足道的。
第三,正确的外键和唯一约束将在所有这四种设计中保持参照完整性。 (但请参见下文,"关于Cascades"。)
:一种。代理键的外键
create table people (
FirstName integer not null
references FirstNames (ID),
LastName integer not null
references Surnames (ID),
primary key (FirstName, LastName)
);
<强> B中。自然键的外键
create table people (
FirstName varchar(n) not null
references FirstNames (FirstName),
LastName varchar(n) not null
references Surnames (Surname),
primary key (FirstName, Surname)
);
<强>℃。代理键的外键,额外的代理键
create table people (
ID serial primary key,
FirstName integer not null
references FirstNames (ID),
LastName integer not null
references Surnames (ID),
unique (FirstName, LastName)
);
<强> 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)
);
现在让我们看看ParentsOf表。
:一种。用于代替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)
);
要检索给定行的名称,您需要四个联接。您可以直接加入&#34; FirstNames&#34;和&#34; Surnames&#34;表;你不需要加入通过&#34; People&#34;表得到名字。
<强> 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)
);
此设计需要零连接来检索给定行的名称。许多SQL平台根本不需要读取表,因为它们可以从主键上的索引中获取所有数据。
<强>℃。代理键的外键,C中的附加代理键,
create table ParentsOf (
Person integer not null
references People (ID),
PersonParent integer not null
references People (ID),
primary key (Person, PersonParent)
);
要检索姓名,您必须加入通过&#34; people&#34;表。您总共需要六个联接。
<强> d。自然键的外键,D中的附加代理键,上面
该设计具有与上面C中相同的结构。因为&#34;人&#34; D中的表格,在上面,有自然键引用表格&#34; FirstNames&#34;和&#34; Surnames&#34;,你只需要两个连接到桌面&#34;人们&#34;得到名字。
关于ORM
ORM不像SQL开发人员编写SQL那样构建SQL。如果SQL开发人员编写需要六个连接来获取名称的SELECT语句,则ORM可能会执行七个更简单的查询来获取相同的数据。这可能是一个问题;它可能没有。
关于Cascades
代理ID号使每个外键引用一个隐含的,未声明的&#34; ON UPDATE CASCADE&#34;。例如,如果您对姓氏表运行此更新语句。 。
update surnames
set surname = 'Smythe'
where surname = 'Smith';
然后所有的史密斯都将成为史密斯。防止这种情况的唯一方法是撤消&#34;姓氏&#34;的更新权限。隐含的,未声明的&#34; ON UPDATE CASCADE&#34;并不总是一件好事。撤销权限仅仅是为了防止不必要的隐含&#34;级联&#34;并不总是一件好事。
答案 2 :(得分:2)
使用自然键可以实现更简单,更快速的查询,因为不需要一直加入外键链来查找&#34; natural&#34;价值例如用于在屏幕上显示。
答案 3 :(得分:2)
我将避免在这里进行纯粹的学术讨论并考虑一些实用的考虑因素,因为现代数据库设计通常需要考虑可扩展性,移动性(断开操作)和冲突解决方案,其中密钥的选择可能会产生很大的影响。
可能影响您选择的事情是:
我的建议是从整体上看系统的特性,超越理论数据库设计,以及对数据库上方的非平凡完整堆栈有效的方法。您为关键设计做出的选择可能会影响或破坏系统的可用性,从而有助于或损害开发复杂性,从而增加或减少您的上市时间以及质量和可靠性的整体权衡。
答案 4 :(得分:1)
我曾经看过这个主键的标准列表。我觉得这是一个很好的起点,这种讨论
有时两个或多个标准之间存在冲突,我们必须在它们之间妥协。不幸的是,许多人甚至从未想过如何设计密钥,他们使用某种自动生成的密钥,可能是标识列,guid或其他任何内容。
代理键的一个缺点是执行规则声明变得更加困难(大多数DBMS不支持检查约束中的子查询)。我正在考虑以下规则:
CHECK ( jobtitle <> 'BOSS' OR salary > 100 )
然而,我发现代理键的最大问题是你可以摆脱非常奇怪的结构,甚至没有注意到。
答案 5 :(得分:1)
您可以在数据库中找到的常见用例是对历史记录进行版本控制
使用用户表:
ID Name Value DeletedFlag
1 Smith 78 0
2 Martin 98 0
3 John 78 1
4 Edouard 54 0
5 John 64 0
John填写了一个信息,然后决定删除它并填写一个新信息。
如果您不使用独特的PK,您将无法管理这种情况。
这使得在开发和生产中非常容易将某些数据标记为已删除,并取消标记以进行某些测试或数据更正,而不是备份或恢复或造成很多混淆。
在整数上重建索引也更快,并且占用的磁盘空间更少。
答案 6 :(得分:1)
如果我们的复合键由STUDENT和COURSE列组成,那么 数据库将确保我们永远不会输入重复值。
实施例。
使用复合键,这是不允许的,数据库会阻止它。
STUDENT COURSE
1 CS101
1 CS101
但如果我们选择代理密钥作为密钥,我们将需要找到另一种方法来防止这种重复。
思考 哪些字段组合是可能的键可以帮助您更好地发现和理解问题。
答案 7 :(得分:0)
我认为你误解了数据方面的基本原因:
1)您正在使用单个标识符(人名 - 假设确实唯一标识一个人),将其拆分为亚原子部分,然后由于6NF,将它们放入单独的关系变量中。通常这样的分割是出于实际原因而做出的,并且名字/姓氏是一个常见的例子;决定通常是基于分裂的复杂性,频率等与再次将属性重新组合在一起的决定。这里的分裂是不切实际的。
2)6NF总是可以实现,但并不总是可取的。在这种情况下,它使得更难以定义一个约束,该约束能够验证组件在组合中是有效的(假设您按时间分割日期,月份和年份,并将每个部分存储在单独的relvars中!)。
3)对于人员标识符,名字和姓氏的复合很少。通常根据所需的信任级别选择标识符。雇主检查参考资料,资格等,然后发出工资单参考。警方提议可能需要在路边看到您的驾驶执照,但如果您被判定犯罪,将会收到指纹。 DBMS无法验证某个人,因此自动增量整数也很少。