我正在设计我的第一个数据库,我发现自己对在为分类变量的每个实例存储整数或字符串之间进行选择感到沮丧。
我的理解是,如果我有一个包含城市的表,我想将其作为国家表的子项,那么最有效的方法是将国家表的 PK 作为城市表中的 FK。但是,为了便于使用和调试,最好始终将字符串名称与国家/地区 PK 相关联。我考虑过的每个解决方案要么不被推荐,要么看起来过于复杂。
我想就这些方法的优点发表意见(或了解新方法),并了解是否必须采用这种方式,或者数据库是否只是因为传统而采用这种方式。
可能的方法:
使用字符串作为国家/地区的 PK。然后我将在任何子表中为它提供一个人类可读的 FK。显然性能不如使用整数,但我怀疑这可能是获得我想要的便利的最不坏的方式。
使用将每个国家/地区的字符串名称连接到 states 表的应用程序逻辑创建一个视图。
- 我不喜欢这个,因为如果应用程序逻辑中断,表格的可读性就会降低。此外,我希望大型连接操作比字符串 PK/FK 的性能损失更严重。
- 创建一个单独的表以将数字 ID 与适当的字符串 ID 连接起来。我不确定是否最好有一个表来编码每种类型的关系,或者一个大表有一个大的 ID 池,涵盖所有整数键-字符串值关系。然后,我可以使用应用程序逻辑来查找适当的字符串,并在用户给出字符串名称时将适当的 PK 填充到子表中。
- 我觉得这也可能非常耗费资源,因为每次向子项添加新行时都必须进行查找。这也意味着我仍然必须创建我想要的视图。
- 使用
enum
数据类型。本能地,这将是我的首选方法,因为它似乎是自然键和合成键之间的理想平衡:使用整数 ID 并给 ID 一个字符串标签,这样字符串本身就不需要重复。
- 不幸的是,我的研究发现不建议这样做。原因之一是类别不能轻易删除。我不确定这对我来说是否会破坏交易,但我也想知道为什么 DBMS 是这样设计的。分类变量是否常用到足以为它们添加便利功能?
虽然 ypercube 为 的特定示例提出了一个很好且合乎逻辑的观点,但
Countries
我会避免使用基于字符串的数据类型,因为不同数据库系统对字符串所做的某些假设可能会产生潜在的意外影响。例如,在 Microsoft SQL Server 中,优化器通常假设VARCHAR
列是半满的,并将根据该假设生成请求内存的执行计划。这可能会导致内存资源分配过多(甚至不足)以服务于单个查询。我想其他数据库系统也会围绕基于字符串的数据类型做出其他有趣的假设,无论好坏。但比性能更重要的是数据准确性。表的首要任务是存储数据,并且最好准确地存储它。主键的首要任务是建立唯一性,理想情况下它应该是不可变的。代理键的好处是确保所有这些事情在人类可读的值能够改变的情况下保持真实。这实际上遵循了另一个称为one-field one- purpose的良好原则,因为代理键的含义与业务对象的含义完全分离。
回到您的
Countries
示例, a 的名称会更改并不常见Country
,但这也不是不可能的。在过去的 50 年中,一些国家改变了名称。即使使用 ISO 代码也不能 100% 保证它永远不会改变,Country
因为这些代码是如何生成的(尽管从业务对象中移除比使用业务的人类可读值更多)对象本身)。因此,如果使用自然键值并且很可能在它发生变化的那一天发生变化,那么现在您将面临数据准确性的风险,因为您不仅需要确保
Countries
正确更新表,还必须对引用的每个表执行相同的操作Countries
在外键中。当然,更新引用旧值的每条记录也会产生额外的性能开销,而不是在将代理键用作主键时仅在一个地方更新它。但在我看来,更大的问题(回到表格的主要目标)是数据准确性。
视图是统一、转换和呈现数据到应用层的出色工具,在某些情况下(例如当您的表结构需要更改时)甚至可以帮助进行数据维护。由于视图可以充当应用程序和数据库表之间的一层,因此在更改这些表的结构时应用程序的风险较小。从性能的角度来看,使用它们本质上没有任何问题,并且
JOIN
性能(通过代理键)不应该是正确架构和索引数据库的问题。这取决于。由于缺乏更清晰的方式来描述它,通常当它当前在它所在的主表中重复时,将人类可读的值重构到一个单独的表中是有意义的。这样就有一个地方可以唯一地定义该值可以轻松准确地维护。如果做得好,这将大致遵循规范化的原则。
如果“特殊查找表”是指用于多种对象的单个表(例如您的帖子提到的枚举表),我不建议这样做。它可能比多个单独的对象表更容易维护,但是您会丢失关系数据库系统的一些关系属性。
这主要是说数据的准确性。没有什么能阻止您这样做,这不是最佳实践,因为增加了数据准确性的风险,并且在您需要更新值时使数据管理更加困难和性能降低。如果它是外键表中的一个常见值,那么您将面临锁定升级的风险,从而可能导致更长的等待时间并阻塞对该表的读取查询。
有些人实现了这种设计,但更多的是因为他们的业务规则和用例依赖于历史数据跟踪。但是对于具有标准用例的常规事务数据库,它会膨胀您的数据,并且仍然无法解决上述外键引用,您也必须使用更改更新它们或膨胀这些表。即使我有维护历史数据的用例,我也会将交易历史与活动记录分开存储在单独的历史表中。