数据库设计和非数字主键的使用

Posted

技术标签:

【中文标题】数据库设计和非数字主键的使用【英文标题】:Database Design and the use of non-numeric Primary Keys 【发布时间】:2009-05-29 10:00:38 【问题描述】:

我目前正在为客户和网站管理应用程序设计数据库表。我的问题是关于使用主键作为表的功能部分(而不是仅仅因为)为每个表分配“ID”号。

例如,目前数据库中有四个相关的表,其中一个使用传统的主键编号,其他的使用唯一名称作为主键:

--
-- website
--
CREATE TABLE IF NOT EXISTS `website` (
  `name` varchar(126) NOT NULL,
  `client_id` int(11) NOT NULL,
  `date_created` timestamp NOT NULL default CURRENT_TIMESTAMP,
  `notes` text NOT NULL,
  `website_status` varchar(26) NOT NULL,
  PRIMARY KEY  (`name`),
  KEY `client_id` (`client_id`),
  KEY `website_status` (`website_status`),
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

--
-- website_status
--
CREATE TABLE IF NOT EXISTS `website_status` (
  `name` varchar(26) NOT NULL,
  PRIMARY KEY  (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `website_status` (`name`) VALUES
('demo'),
('disabled'),
('live'),
('purchased'),
('transfered');

--
-- client
--
CREATE TABLE IF NOT EXISTS `client` (
  `id` int(11) NOT NULL auto_increment,
  `date_created` timestamp NOT NULL default CURRENT_TIMESTAMP,
  `client_status` varchar(26) NOT NULL,
  `firstname` varchar(26) NOT NULL,
  `lastname` varchar(46) NOT NULL,
  `address` varchar(78) NOT NULL,
  `city` varchar(56) NOT NULL,
  `state` varchar(2) NOT NULL,
  `zip` int(11) NOT NULL,
  `country` varchar(3) NOT NULL,
  `phone` text NOT NULL,
  `email` varchar(78) NOT NULL,
  `notes` text NOT NULL,
  PRIMARY KEY  (`id`),
  KEY `client_status` (`client_status`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=4 ;

--
-- client_status
---
CREATE TABLE IF NOT EXISTS `client_status` (
  `name` varchar(26) NOT NULL,
  PRIMARY KEY  (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

INSERT INTO `client_status` (`name`) VALUES
('affiliate'),
('customer'),
('demo'),
('disabled'),
('reseller');

如您所见,4 个表中有 3 个使用它们的“名称”作为主键。我知道这些将永远是独一无二的。在两种情况下(*_status 表),我基本上使用 ENUM 的动态替换,因为状态选项将来可能会改变,对于“网站”表,我知道网站的“名称”将始终是独一无二的。

我想知道这是否是合理的逻辑,当我知道名称将始终是唯一标识符或灾难的秘诀时摆脱表 ID?我不是经验丰富的 DBA,所以任何反馈、批评等都会非常有帮助。

感谢您抽出宝贵时间阅读本文!

【问题讨论】:

欢迎来到一场从你出生之前就开始的圣战。 :) 更严肃的一点是,您必须考虑的一件事是索引中 VARCHAR 列的影响。我也不是经验丰富的 DBA,所以我不知道答案。但我强烈建议您填写一个测试表,使用 VARCHAR 作为主键,包含几百万条记录并进行一些测试。然后用一个 INT 作为主键做同样的事情,看看会发生什么。 Jordan 担心像 varchar(126) 这样的“宽”键是正确的。请参阅下面的答案。 作为一个旁注给正在使用外键阅读本文的任何人,关于您传递的数据量。在上面的例子 'website_status'.'name' -> 'website'.'website_status' 中,每条记录最多 5 个字节,最坏 10 个字节。如果此设计使用 SMALLINT,它将是一致的 2 字节 (mysql)。 【参考方案1】:

我总是将 ID 号添加到查找/ENUM 表有两个原因:

    如果您使用名称引用单列表,那么使用约束可能会更好地为您服务 如果您想重命名其中一个 client_status 条目会发生什么?例如如果您想将名称从“附属”更改为“附属用户”,则需要更新客户端表,这不是必需的。 ID 号作为参考,名称作为描述。

在网站表中,如果您确信名称是唯一的,则可以用作主键。就我个人而言,我仍然会分配一个数字 ID,因为它减少了外键表中使用的空间,而且我发现它更易于管理。

编辑: 如上所述,如果重命名网站名称,您将遇到问题。通过将此作为主键,您将很难在以后更改它。

【讨论】:

上面第二个是不使用名称作为主键的最佳理由。 好答案。这里还有一个原因:字符串比较操作比数字比较需要更长的时间。 主键绝不能携带可在数据库外使用的数据。也就是说,使用键的唯一原因是引用数据库中的记录,而不是将键的值呈现给用户。这样做的原因是键是不可变的,而数据不是。姓名、性别、年龄、员工类型、员工编号等都是可变的,绝对不能用作密钥。没有使用 bigint(或其他)序列,而是使用 guid 的参数:bit.ly/gSIkOG【参考方案2】:

在制作自然的PRIMARY KEY 时,请确保它们的独特性在您的控制之下。

如果您绝对确定永远不会违反唯一性,那么可以将这些值用作PRIMARY KEY's。

由于website_statusclient_status 似乎是由您并且仅由您生成和使用的,因此可以将它们用作PRIMARY KEY,尽管长键可能会影响性能。

website 名称似乎在外部世界的控制之下,这就是为什么我将其设为普通字段的原因。如果他们想重命名website 怎么办?

反例是SSNZIP 代码:生成它们的不是你,也不能保证它们永远不会被复制。

【讨论】:

我听说有两个人被分配了同一个 SSN。它不应该发生,但无论如何它已经发生了,除非我被误导了。一个更大的问题是人们使用“假冒”社会安全号码来获得工作。现在您的数据源已损坏,即使社会保障局正确管理 SSN。 @Walter:我的观点完全正确。 SSN 不应用作主键 我喜欢你可以控制的独特性!我们甚至从客户那里得到了所谓的唯一整数 id 字段,但当他们重复使用它们或转到新系统时,这些字段并不是唯一的。【参考方案3】:

Kimberly Tripp 有关于创建聚簇索引和选择主键(相关问题,但并不总是完全相同)。她的建议是聚集索引/主键应该是:

    唯一(否则无用作为键) (键用于所有非聚集索引和外键关系) 静态(您不想更改所有相关记录) 总是增加(所以新记录总是被添加到表的末尾,而不必插入到中间)

使用“名称”作为您的键,虽然它似乎满足#1,但不满足其他三个中的任何一个。

即使对于您的“查找”表,如果您的老板决定将所有附属公司改为合作伙伴,该怎么办?您必须修改数据库中使用该值的所有行。

从性能的角度来看,我可能最担心键是。如果您的网站名称实际上是一个长 URL,那么这可能会使任何非聚集索引以及使用它作为外键的所有表的大小膨胀。

【讨论】:

【参考方案4】:

除了已经提出的所有其他优秀观点之外,我还要再提一句警告,不要在 SQL Server 中使用大字段作为集群键(如果您不使用 SQL Server,那么这可能不适用给你)。

我添加这个是因为在 SQL Server 中,默认情况下表上的主键也是集群键(如果你想知道它,你可以更改它,但大多数情况下,它没有完成)。

确定 SQL Server 表的物理顺序的聚集键也被添加到该表的每个非聚集索引中。如果您只有几百到几千行和一两个索引,那没什么大不了的。但是,如果您有数百万行的非常大的表,并且可能有很多索引来加速查询,这确实会导致大量磁盘空间和服务器内存被不必要地浪费。

例如如果你的表有 1000 万行、10 个非聚集索引,并且你的聚集键是 26 个字节而不是 4 个字节(对于 INT),那么你就浪费了 10 个 mio。 10 x 22 字节,总共 22 亿字节(或大约 2.2 GB)——这不再是小菜一碟了!

再次重申 - 这仅适用于 SQL Server,并且仅适用于您的表非常大且包含大量非聚集索引的情况。

马克

【讨论】:

“你可以改变它,如果你想知道它,但大多数情况下,它还没有完成” - 仅当设计数据库的人不是一个好的数据库设计师时。经验丰富且合格的设计师在决定应在聚簇索引中使用什么之前会考虑很多因素。 Tom H:是的,当然——但根据我自己的个人经验,大多数应用程序开发人员同时并不是伟大的数据库设计师。许多应用程序开发人员将数据库视为“愚蠢的存储转储”,他们可以将对象放入其中 - 并且对数据库设计考虑不足。【参考方案5】:

“如果您绝对确定永远不会违反唯一性,那么可以将这些值用作主键。”

如果您绝对确定永远不会违反唯一性,那么请不要费心定义键。

【讨论】:

【参考方案6】:

就个人而言,我认为您使用这个想法会遇到麻烦。当您最终建立更多的父子关系时,当名称更改时,您最终会做大量的工作(因为它们迟早会发生)。当网站名称更改时,必须更新包含数千行的子表时,性能可能会受到很大影响。你必须计划如何确保这些变化发生。否则,网站名称会更改(哎呀,我们让名称过期并且其他人购买了它。)要么由于外键约束而中断,要么您需要以自动方式(级联更新)以通过系统传播更改。如果您使用级联更新,那么您可能会在处理大量更改时突然让您的系统死机。这不被认为是一件好事。将 id 用于关系然后在 name 字段上放置唯一索引以确保它们保持唯一确实更有效和高效。数据库设计需要考虑数据完整性的维护以及这将如何影响性能。

要考虑的另一件事是,网站名称往往比几个字符长。这意味着使用 id 字段进行连接和使用连接名称之间的性能差异可能非常显着。您必须在设计阶段考虑这些事情,因为当您的生产系统有数百万条记录超时并且解决方法是完全重构数据库并重写所有 SQL 时,更改为 ID 为时已晚代码。不是您可以在十五分钟内解决的问题,以使该站点再次运行。

【讨论】:

【参考方案7】:

这似乎是一个非常糟糕的主意。如果您需要更改枚举的值怎么办?这个想法是让它成为一个关系数据库,而不是一组平面文件。此时,为什么要有client_status表呢?此外,如果您在应用程序中使用数据,通过使用 GUID 或 INT 等类型,您可以验证类型并避免错误数据(就验证类型而言)。因此,它是阻止黑客入侵的众多措施中的另一条。

【讨论】:

【参考方案8】:

我会争辩说,即使运行速度稍慢一点,抗损坏的数据库也比不抗损坏的数据库要好。

一般来说,代理键(例如任意数字标识符)会破坏数据库的完整性。主键是识别数据库中行的主要方式;如果主键值没有意义,则约束没有意义。因此,任何引用代理主键的外键也是可疑的。每当您必须检索、更新或删除单个行(并保证只影响一个行)时,您必须使用主键(或另一个候选键);当存在有意义的替代键时,必须弄清楚代理键值是什么,这对于用户和应用程序来说是一个多余且具有潜在危险的步骤。

即使这意味着使用复合键来确保唯一性,我也建议尽可能使用有意义、自然的属性集作为主键。如果您无论如何都需要记录属性,为什么还要添加另一个?也就是说,当没有自然的、稳定的、简洁的、保证唯一的键(例如,对于人)时,代理键很好。

如果您的 DBMS 支持,您也可以考虑使用索引键压缩。这可能非常有效,特别是对于复合键上的索引(想想trie 数据结构),尤其是如果选择性最低的属性可以首先出现在索引中。

【讨论】:

【参考方案9】:

我认为我同意 cheduardo。我学习数据库设计课程已经 25 年了,但我记得有人告诉我,数据库引擎可以更有效地管理和加载使用字符键的索引。关于数据库必须在更改密钥时更新数千条记录并且所有添加的空间被较长的密钥占用然后必须跨系统传输的cmets假设密钥实际上存储在记录中并且无论如何,它不必跨系统传输。如果您在表的列上创建索引,我认为该值不会存储在表的记录中(除非您设置了一些选项来这样做)。

如果您有一个表的自然键,即使它偶尔更改,创建另一个键也会产生冗余,这可能会导致数据完整性问题,并且实际上会创建更多需要跨系统存储和传输的信息。我为一个决定将本地应用程序设置存储在数据库中的团队工作。它们对每个设置都有一个标识列、一个部分名称、一个键名和一个键值。他们有一个存储过程(另一场圣战)来保存确保它不会出现两次的设置。我还没有找到使用设置 ID 的案例。但是,我最终得到了多个具有相同部分和键名的记录,导致我的应用程序失败。是的,我知道可以通过在列上定义约束来避免这种情况。

【讨论】:

拥有char 键与数字键相比没有效率优势(也不一定有任何惩罚)。键值 存储在“记录”中 - 所有值都存储在(特殊的批量值列除外 - 但无论如何您都不会为它们编制索引)。索引重复它们覆盖的数据。这些字段永远不会仅仅因为它们被索引而从数据中删除。 我怀疑数据是否存储在表中是一个选项。 “聚集索引的底部或叶级包含表的实际数据行。表或视图一次允许一个聚集索引。”我从我的数据库理论课中模糊地记得,您可以将索引列的值存储在索引中,这样这些数据就不会在表的每一行中重复。【参考方案10】:

在决定表中的键之前应该考虑以下几点

数字键更适合你 使用引用(外键),因为 你不使用外键,没关系 你的情况下使用非数字键。

非数字键使用的空间多于 数字键,可以递减 性能。

数字键使 db 看起来更简单 明白(你很容易知道不 仅通过查看最后一行)

【讨论】:

最后一行的数字键值并不能很好地指示数据库中的总行数,除非您从不删除数据库中的记录。【参考方案11】:

你永远不知道你工作的公司什么时候突然爆发式增长,你必须在一夜之间雇佣 5 名开发人员。最好的选择是使用数字(整数)主键,因为它们会让整个团队更容易使用,并且在数据库增长时有助于提高性能。如果您必须打破记录并对它们进行分区,您可能需要使用主键。如果您要添加带有日期时间戳的记录(每个表都应该如此),并且代码中的某处错误地更新了该字段,那么确认记录是否以正确的顺序输入的唯一方法是检查主要键。使用 INT 主键可能还有 10 多个 TSQL 或调试原因,其中最重要的是编写一个简单的查询来选择输入到表中的最后 5 条记录。

【讨论】:

以上是关于数据库设计和非数字主键的使用的主要内容,如果未能解决你的问题,请参考以下文章

数据库中主键和外键的作用?

数据库设计 - 是不是应将日期用作主键的一部分

从一个审批需求看数据库设计——联合主键的使用

关于主键的设计primary key

数据库主键

数据库设计中主键问题