外键的哪一列:id 或任何其他列,为什么?
Which column for foreign key: id or any other column and why?
TL;DR
- 外键是否应始终引用另一个 table 的 id 列?为什么或者为什么不?这有标准规则吗?
- 使用
id
列以外的任何其他唯一列作为外键是否会产生相关成本?性能/存储?有多重要?是不是在业界皱眉?
示例:这是我的示例问题的架构:
在我的架构中,有时我使用 id
列作为外键,有时使用其他一些数据列。
在 vehicle_detail
table 我使用 unique
size
列作为来自 vehicle_size
table 的外键unique
color
列作为来自 vehicle_color
table.
的外键
但是在vehicle_user
中我使用了user_identifier_id
作为外键,它引用user_identifier
[=58=中的id
主键列].
正确的方法是什么?
附带说明一下,garage_level
、garage_spaceid
、vehicle_garage_status
和 vehicle_parking_status
[=58= 没有 id
列]s 因为它们只有一列是主键,它们存储的数据在每个 table 中最多只有 15 行,而且它可能永远不会改变。我还应该在那些中有一个 id
列吗?
外键必须以主键或唯一约束为目标。引用主键是正常的,因为你通常想引用另一个 table 中的单个行,而主键是 table 行的标识符。
从技术的角度来看,外键是引用主键还是另一个唯一约束并不重要,因为在 PostgreSQL 中两者的实现方式相同,使用唯一索引。
关于您的具体示例,将 vehicle_size
的唯一 size
列作为外键的目标并没有错,尽管它回避了您为什么不这样做的问题size
主键并完全省略 id
列。没有必要每个 table 都有一个 id
列作为自动生成的数字主键,除非可能有 ORM 和其他软件需要它。
外键是用于在两个 table 之间建立 link 的字段或列。 FOREIGN KEY 是一个 table 中的一列(或列的集合),它引用另一个 table.
中的 PRIMARY KEY
没有规定它应该引用 id
列,但它引用的列应该是主键。在实际场景中,它通常指的是 Id
列,因为在大多数情况下它是 table 中的主键。
一个 foreign 键基本上是一个不同的 table 的列(它总是不同的 table,因为那是它所服务的角色).它用于连接/获取来自不同 table 的数据。可以把它想象成说学校是一个数据库,对于学生的不同方面有很多不同的table。
比如使用入场号码 1234,从帐户 table 你可以得到费用和运动 table 你可以得到他参加的运动。
现在没有规定外键必须是id列,你可以随便留着。但是,要使用外键,你应该在两个 table 中都有一个匹配的列,因此通常只使用 id 列。正如我在上面的例子中所说的那样,运动 table 和账户 table 中唯一常见的是入场号码。
admn_no | sports |
+---------+------------+
| 1234 | basketball
+---------+---------+
| admn_no | fees |
+---------+---------+
| 1234 | 1000000 |
+---------+---------+
现在说使用查询\
select * from accounts join sports using (admn_no);
您将获得:
+---------+---------+------------+
| admn_no | fees | sports |
+---------+---------+------------+
| 1234 | 1000000 | basketball |
+---------+---------+------------+
PS:抱歉格式错误
OP 问题是关于“正确的方法”。
我将尝试根据现有评论和答案提供某种摘要,一般 DO
和一般 DONT
用于 FK。
已经说过了
一个。 “外键必须以主键或唯一约束为目标”
字面上来自 Laurenz Albe answer 并在评论中注明
乙。 “坚持你认为改变最少的任何东西”
已被 Adrian Klavier 在评论中指出。
备注
没有必须在单个列上定义 PK 或唯一约束的一般规则。
所以问题标题本身必须更正:“外键的哪一列(s):id或任何其他列(s) 为什么?
我们来谈谈“为什么”。
为什么:一般做,一般不要和建议
Is there a cost associated with using any other unique column other than id column for foreign key? Performance / storage? How significant? Is it frowned in the industry?
General DO:分析需求,使用逻辑,使用数学(通常算术就够了)。没有一种数据库设计总是适用于所有情况。总是问自己:“它可以改进吗?”。永远不要满足于现有 FK 的设计,如果需求发生变化或 DBMS 发生变化或存储选项发生变化 - 修改设计。
General DONT:不要认为所有情况都有一个正确的规则。不要认为:“如果那在 database/table 中起作用,那么它也适用于这种情况”。
让我用一个常见的例子来说明这一点。
示例:PK id uuid
字段
- 我们查看数据库,发现 table 对两个类型的字段具有唯一约束
integer
(4 字节)+ date
(4 字节)
- 另外:这个table有一个
uuid
类型的字段id
(16字节)
- PK 定义于
id
- 来自其他 table 的所有 FK 都针对
id
字段
这个设计对不对?
情况 A. 常见情况 - 不行
让我们使用数学:
int
+date
的唯一约束:它是 4+4=8 字节
- 数据永远不会改变
- 所以它是这个 table
中主键的一个很好的候选者
- 并且没有什么可以阻止将它用于相关 tables
中的外键
所以看起来每行额外的 16 个字节 + 索引成本是一个错误。
这是一个非常常见的错误,尤其是在随机 uuids 上组合使用 MSSQL + CLUSTERED 索引时
总是出错吗?
没有
考虑后一种情况。
案例 B. 分布式系统 - OK
假设你有一个分布式系统:
ServerA
、ServerB
、ServerC
为数据来源
HeadServer
- 是数据聚合器
serverA
-ServerC
上的数据可能重复:同一条记录可能存在于多个实例中
- 聚合数据不得重复
- 相关 table 的数据可以来自不同的实例:table 的数据来自
serverA
,table 的数据来自 [=28] =]-serverC
- 您需要记录每条记录的来源
在这种情况下 id uuid
上存在 PK 是合理的:
- 唯一约束允许删除重复记录
- 代理键允许来自不同来源的相关数据
案例C.'id'用于通过API暴露数据 - OK
假设您有一个 API 来访问外部消费者的数据。
有一个很好的唯一约束:
client_id
:递增整数范围 1..100000
invoice_date
: 日期 '20100101'..'20210901'
以及 id
上的代理键和随机 uuid。
您可以在表单中创建外部 API:
/server/invoice/{client_id}/{invoice_date}
/server/invoice/{id}
从安全 POV /{id}
优越的原因:
- 不可能从一个
uuid
推导出另一个 的值存在
- 更容易为不同类型的实体实施授权系统。例如。 entityA 在
int
上有自然键,在 bigint' and entityC on
int+
byte+
date` 上有 entityB
在这种情况下,代理键不仅合理而且变得必不可少。
后记
我希望我对主要正确原则的解释是清楚的:“没有普遍正确的原则”。
附加建议:避免 CASCADE UPDATE/DELETEs:
尽管这取决于您使用的 DBMS。
但总的来说:
- “显式优于隐式”
- CASCADE 很少按预期工作
- 当 CASCADES 工作时 - 通常它们有性能问题
感谢您的关注。
我希望这对某人有所帮助。
TL;DR
- 外键是否应始终引用另一个 table 的 id 列?为什么或者为什么不?这有标准规则吗?
- 使用
id
列以外的任何其他唯一列作为外键是否会产生相关成本?性能/存储?有多重要?是不是在业界皱眉?
示例:这是我的示例问题的架构:
在我的架构中,有时我使用 id
列作为外键,有时使用其他一些数据列。
在
的外键vehicle_detail
table 我使用unique
size
列作为来自vehicle_size
table 的外键unique
color
列作为来自vehicle_color
table.但是在
vehicle_user
中我使用了user_identifier_id
作为外键,它引用user_identifier
[=58=中的id
主键列].
正确的方法是什么?
附带说明一下,garage_level
、garage_spaceid
、vehicle_garage_status
和 vehicle_parking_status
[=58= 没有 id
列]s 因为它们只有一列是主键,它们存储的数据在每个 table 中最多只有 15 行,而且它可能永远不会改变。我还应该在那些中有一个 id
列吗?
外键必须以主键或唯一约束为目标。引用主键是正常的,因为你通常想引用另一个 table 中的单个行,而主键是 table 行的标识符。
从技术的角度来看,外键是引用主键还是另一个唯一约束并不重要,因为在 PostgreSQL 中两者的实现方式相同,使用唯一索引。
关于您的具体示例,将 vehicle_size
的唯一 size
列作为外键的目标并没有错,尽管它回避了您为什么不这样做的问题size
主键并完全省略 id
列。没有必要每个 table 都有一个 id
列作为自动生成的数字主键,除非可能有 ORM 和其他软件需要它。
外键是用于在两个 table 之间建立 link 的字段或列。 FOREIGN KEY 是一个 table 中的一列(或列的集合),它引用另一个 table.
中的 PRIMARY KEY没有规定它应该引用 id
列,但它引用的列应该是主键。在实际场景中,它通常指的是 Id
列,因为在大多数情况下它是 table 中的主键。
一个 foreign 键基本上是一个不同的 table 的列(它总是不同的 table,因为那是它所服务的角色).它用于连接/获取来自不同 table 的数据。可以把它想象成说学校是一个数据库,对于学生的不同方面有很多不同的table。
比如使用入场号码 1234,从帐户 table 你可以得到费用和运动 table 你可以得到他参加的运动。
现在没有规定外键必须是id列,你可以随便留着。但是,要使用外键,你应该在两个 table 中都有一个匹配的列,因此通常只使用 id 列。正如我在上面的例子中所说的那样,运动 table 和账户 table 中唯一常见的是入场号码。
admn_no | sports |
+---------+------------+
| 1234 | basketball
+---------+---------+
| admn_no | fees |
+---------+---------+
| 1234 | 1000000 |
+---------+---------+
现在说使用查询\
select * from accounts join sports using (admn_no);
您将获得:
+---------+---------+------------+
| admn_no | fees | sports |
+---------+---------+------------+
| 1234 | 1000000 | basketball |
+---------+---------+------------+
PS:抱歉格式错误
OP 问题是关于“正确的方法”。
我将尝试根据现有评论和答案提供某种摘要,一般 DO
和一般 DONT
用于 FK。
已经说过了
一个。 “外键必须以主键或唯一约束为目标”
字面上来自 Laurenz Albe answer 并在评论中注明
乙。 “坚持你认为改变最少的任何东西”
已被 Adrian Klavier 在评论中指出。
备注
没有必须在单个列上定义 PK 或唯一约束的一般规则。
所以问题标题本身必须更正:“外键的哪一列(s):id或任何其他列(s) 为什么?
我们来谈谈“为什么”。
为什么:一般做,一般不要和建议
Is there a cost associated with using any other unique column other than id column for foreign key? Performance / storage? How significant? Is it frowned in the industry?
General DO:分析需求,使用逻辑,使用数学(通常算术就够了)。没有一种数据库设计总是适用于所有情况。总是问自己:“它可以改进吗?”。永远不要满足于现有 FK 的设计,如果需求发生变化或 DBMS 发生变化或存储选项发生变化 - 修改设计。
General DONT:不要认为所有情况都有一个正确的规则。不要认为:“如果那在 database/table 中起作用,那么它也适用于这种情况”。
让我用一个常见的例子来说明这一点。
示例:PK id uuid
字段
- 我们查看数据库,发现 table 对两个类型的字段具有唯一约束
integer
(4 字节)+date
(4 字节) - 另外:这个table有一个
uuid
类型的字段id
(16字节) - PK 定义于
id
- 来自其他 table 的所有 FK 都针对
id
字段
这个设计对不对?
情况 A. 常见情况 - 不行
让我们使用数学:
int
+date
的唯一约束:它是 4+4=8 字节- 数据永远不会改变
- 所以它是这个 table 中主键的一个很好的候选者
- 并且没有什么可以阻止将它用于相关 tables 中的外键
所以看起来每行额外的 16 个字节 + 索引成本是一个错误。
这是一个非常常见的错误,尤其是在随机 uuids 上组合使用 MSSQL + CLUSTERED 索引时
总是出错吗?
没有
考虑后一种情况。
案例 B. 分布式系统 - OK
假设你有一个分布式系统:
ServerA
、ServerB
、ServerC
为数据来源HeadServer
- 是数据聚合器serverA
-ServerC
上的数据可能重复:同一条记录可能存在于多个实例中- 聚合数据不得重复
- 相关 table 的数据可以来自不同的实例:table 的数据来自
serverA
,table 的数据来自 [=28] =]-serverC
- 您需要记录每条记录的来源
在这种情况下 id uuid
上存在 PK 是合理的:
- 唯一约束允许删除重复记录
- 代理键允许来自不同来源的相关数据
案例C.'id'用于通过API暴露数据 - OK
假设您有一个 API 来访问外部消费者的数据。
有一个很好的唯一约束:
client_id
:递增整数范围 1..100000invoice_date
: 日期 '20100101'..'20210901'
以及 id
上的代理键和随机 uuid。
您可以在表单中创建外部 API:
/server/invoice/{client_id}/{invoice_date}
/server/invoice/{id}
从安全 POV /{id}
优越的原因:
- 不可能从一个
uuid
推导出另一个 的值存在
- 更容易为不同类型的实体实施授权系统。例如。 entityA 在
int
上有自然键,在bigint' and entityC on
int+
byte+
date` 上有 entityB
在这种情况下,代理键不仅合理而且变得必不可少。
后记
我希望我对主要正确原则的解释是清楚的:“没有普遍正确的原则”。
附加建议:避免 CASCADE UPDATE/DELETEs: 尽管这取决于您使用的 DBMS。
但总的来说:
- “显式优于隐式”
- CASCADE 很少按预期工作
- 当 CASCADES 工作时 - 通常它们有性能问题
感谢您的关注。
我希望这对某人有所帮助。