没有魔法。如果某些行是连接的,而有些行没有连接,则必须以某种方式表示此信息,并且执行此操作的“关系”方式是“联结”(又称“链接”)表。是的,联结表可以变大,但幸运的是数据库非常有能力处理大量数据。
使用联结表而不是逗号分隔列表(或类似列表)有充分的理由,包括:
在设计联结表时,请提出以下问题:
- 我需要只查询一个方向还是两个方向?1
- 如果一个方向,只需在两个外键上创建一个复合主键(我们称它们为 PARENT_ID 和 CHILD_ID)。顺序很重要:如果从父级查询到子级,PK 应该是:{PARENT_ID, CHILD_ID}。
- 如果两个方向,还以相反的顺序创建复合索引,在本例中为 {CHILD_ID, PARENT_ID}。
- “额外”数据是否很小?
- 如果是,cluster 表和cover 二级索引中的额外数据是必要的。2
- 我否,不要对表进行聚类,也不要覆盖二级索引中的多余数据。3
- 是否有任何其他表可以将联结表用作父表?
- 如果是,请考虑添加代理键是否值得让子 FK 保持苗条。但请注意,如果您添加代理键,这可能会消除集群的机会。
在许多情况下,这些问题的答案将是:两者,是和否,在这种情况下,您的表将与此类似(Oracle 语法如下):
CREATE TABLE JUNCTION_TABLE (
PARENT_ID INT,
CHILD_ID INT,
EXTRA_DATA VARCHAR2(50),
PRIMARY KEY (PARENT_ID, CHILD_ID),
FOREIGN KEY (PARENT_ID) REFERENCES PARENT_TABLE (PARENT_ID),
FOREIGN KEY (CHILD_ID) REFERENCES CHILD_TABLE (CHILD_ID)
) ORGANIZATION INDEX COMPRESS;
CREATE UNIQUE INDEX JUNCTION_TABLE_IE1 ON
JUNCTION_TABLE (CHILD_ID, PARENT_ID, EXTRA_DATA) COMPRESS;
注意事项:
-
ORGANIZATION INDEX:大多数 DBMS 称为集群的特定于 Oracle 的语法。其他 DBMS 有自己的语法,有些(MySQL/InnoDB)暗示集群,用户无法关闭它。
-
COMPRESS:一些 DBMS 支持 leading-edge index compression。由于聚簇表本质上是一个索引,因此也可以对其应用压缩。
-
JUNCTION_TABLE_IE1、EXTRA_DATA:由于二级索引覆盖了多余的数据,所以DBMS在从child到parent的方向查询时不用碰表就可以拿到。主键充当聚类键,因此在从父级向子级查询时自然会覆盖额外的数据。
实际上,您只有两个 B 树(一个是聚簇表,另一个是二级索引),根本没有表堆。这转化为良好的查询性能(父到子和子到父方向都可以通过简单的索引范围扫描来满足)和插入/删除行时相当小的开销。
这是等效的 MS SQL Server 语法(无索引压缩):
CREATE TABLE JUNCTION_TABLE (
PARENT_ID INT,
CHILD_ID INT,
EXTRA_DATA VARCHAR(50),
PRIMARY KEY (PARENT_ID, CHILD_ID),
FOREIGN KEY (PARENT_ID) REFERENCES PARENT_TABLE (PARENT_ID),
FOREIGN KEY (CHILD_ID) REFERENCES CHILD_TABLE (CHILD_ID)
);
CREATE UNIQUE INDEX JUNCTION_TABLE_IE1 ON
JUNCTION_TABLE (CHILD_ID, PARENT_ID) INCLUDE (EXTRA_DATA);
请注意,MS SQL Server 会自动对表进行集群,除非指定了 PRIMARY KEY NONCLUSTERED。
1 换句话说,您是否只需要获取给定“父母”的“孩子”,或者您可能也需要获得给定的父母孩子。
2 覆盖允许仅从索引满足查询,并避免在通过聚集表中的二级索引访问数据时需要进行昂贵的双重查找。
3 这样,额外的数据不会重复(这会很昂贵,因为它很大),但是您可以避免双重查找并用(更便宜的)表堆替换它使用权。但是,请注意clustering factor,它会破坏基于堆的表中范围扫描的性能!