16

我目前正在为客户和网站管理应用程序设计数据库表。我的问题是关于使用主键作为表的功能部分(而不是仅仅因为)为每个表分配“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 个使用它们的“名称”作为主键。我知道这些将永远是独一无二的。在 2 种情况下(*_status 表)我基本上使用 ENUM 的动态替换,因为状态选项将来可能会改变,对于“网站”表,我知道网站的“名称”将始终是独一无二的。

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

感谢您抽时间阅读!

4

11 回答 11

18

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

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

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

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

于 2009-05-29T10:07:05.557 回答
13

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

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

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

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

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

于 2009-05-29T10:06:34.543 回答
9

Kimberly Tripp 有一系列关于创建聚簇索引和选择主键(相关问题,但并不总是完全相同的问题)的优秀系列博客文章(GUIDs as PRIMARY KEYs and/or the clustering key and The Clustered Index Debate Continues ) )。她的建议是聚集索引/主键应该是:

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

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

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

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

于 2009-05-29T14:44:12.190 回答
3

除了已经提出的所有其他优秀观点之外,我还要提醒大家不要在 SQL Server 中使用大字段作为集群键(如果您不使用 SQL Server,那么这可能不适用于您) .

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

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

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

再说一遍——这只适用于 SQL Server,并且只有当你有非常大的表时,它们上有很多非聚集索引。

马克

于 2009-05-29T11:06:57.167 回答
2

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

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

于 2009-07-02T21:41:19.157 回答
1

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

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

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

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

于 2009-05-30T07:15:31.123 回答
1

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

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

于 2009-05-29T14:31:52.043 回答
1

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

于 2009-05-29T14:43:09.057 回答
1

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

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

于 2012-12-27T15:56:04.063 回答
0

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

  • 当您使用引用(外键)时,数字键更合适,因为您不使用外键,所以在您的情况下可以使用非数字键。

  • 非数字键比数字键使用更多空间,会降低性能。

  • 数字键使 db 看起来更易于理解(您只需查看最后一行即可轻松了解行数)
于 2009-05-29T10:20:15.060 回答
-1

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

于 2018-04-24T18:03:20.980 回答