【问题标题】:Why use an auto-incrementing primary key when other unique fields exist?当存在其他唯一字段时,为什么要使用自增主键?
【发布时间】:2011-05-05 10:38:36
【问题描述】:

我正在学习一门名为“数据库系统”的课程,为了我们的课程项目,我必须设计一个网站。

这是我创建的表的示例:

CREATE TABLE users
(
  uid INT NOT NULL AUTO_INCREMENT,
  username VARCHAR(60),
  passhash VARCHAR(255),
  email VARCHAR(60),
  rdate DATE,
  PRIMARY KEY(uid)
);

教授告诉我“uid”(用户 id)完全没用且没有必要,我应该使用用户名作为主键,因为没有两个用户可以拥有相同的用户名。

我告诉他我使用用户 ID 很方便,因为当我调用类似 domain.com/viewuser?id=5 的东西时,我只需检查参数:is_numeric($_GET['id'])... 不用说他不相信.

由于我在大量教程中看到了 user_id 和其他类似属性(thread_id、comment_id 等),并查看了流行软件(例如 vbulletin)的数据库模式,因此肯定有很多其他(更强大的)原因。

所以我的问题是:与使用用户名等其他属性相比,您如何证明需要非空自动递增 id 作为主键?

【问题讨论】:

  • Joe Celko 总是说同样的话,“你应该尽可能使用有意义的 PK。”
  • 你的教授是不是读漫画书比读 PHP/MySQL 多?
  • 如果你不是很在乎你的成绩,告诉你的老师,这就是他教理论而不是在行业工作的原因:p
  • 您的教授指出您应该使用户名唯一且不可为空,如果要求用户名应该是唯一的,那么您的教授正在做正确的事情。 uid 也可以是一个键,但除非您在某处实际使用它,否则不需要它。设计中更重要的方面应该是实现自然键。所以我同意你教授的评论。
  • 我确实担心 USERNAME 的大小。如果你打算让人们使用他们的电子邮件地址作为用户名,那么 60 个字符比可能的最长电子邮件地址要小得多。如果您希望他们使用像 user186909 这样的名称,那么 60 似乎太长了,特别是如果您要将它用作自然键,您希望它尽可能小。

标签: sql database database-design data-modeling


【解决方案1】:

自动递增的主键之所以有用有几个原因:

  • 它们允许在 Stack Overflow 上使用重复的用户名
  • 它们允许(轻松)更改用户名(或电子邮件地址,如果用于登录)
  • 选择、连接和插入比 varchar 主键更快,因为它维护数字索引要快得多
  • 正如您所说,验证变得非常简单:if ((int)$id > 0) { ... }
  • 输入的卫生是微不足道的:$id = (int)$_GET['id']
  • 由于外键不必复制可能很大的字符串值,因此开销要少得多

当自动递增的数字键如此容易获得时,我会说尝试使用任何字符串信息作为记录的唯一标识符是一个坏主意。

具有唯一用户名的系统适用于极少数用户,但 Internet 已从根本上破坏了它们。当您考虑到可能必须与网站交互的大量名为“john”的人时,要求他们每个人都使用唯一的显示名称是荒谬的。这导致了我们经常看到的糟糕的系统,用随机数字和字母装饰用户名。

但是,即使在您强制使用唯一用户名的系统中,主键仍然是一个糟糕的选择。想象一个有 500 个帖子的用户:posts 表中的外键将包含用户名,重复 500 次。即使在您考虑到某人最终可能需要更改其用户名之前,开销也令人望而却步。

【讨论】:

  • 另一点 - ORM 讨厌非整数键。虽然我觉得这是大多数 ORM 的普遍失败,但您确实学会了使用工具
  • @meagar 我讨厌访问需要我提供唯一用户名才能登录的网站。我想完全放弃“用户名”进行身份验证,而只使用电子邮件地址。跨度>
  • @Phil 我知道,我的意思是,一旦您的电子邮件遭到入侵,就会有人“拥有”与之相关的所有帐户。他们可以访问每个站点并发送密码请求电子邮件。因此,单点故障成为您电子邮件帐户的密码。
  • @meagar:如果用户没有唯一的名称,您如何唯一地识别用户?我不知道有多少系统需要我用一个数字来标识自己。通常我有一个唯一的登录名。
  • 非唯一显示的用户名有助于社会工程 - 我不会称这是个好主意。
【解决方案2】:

如果用户名是主键并且用户更改了他/她的用户名,您将需要更新所有对用户表具有外键引用的表。

【讨论】:

  • 如果用户可以更改他/她/其用户名,则用户名根据定义不是候选主键。
  • 引用动作ON UPDATE CASCADE 是这个“稻草人”论点的常见答案。
  • @Bob Jarvis:一个好键的属性,它的值是稳定的。不可变是理想的,但只有很少的变化也是好的。
  • @onedaywhen:如果您使用ON UPDATE CASCADE,而不是在一个地方更改用户名,您可能会在大量索引字段中更改它。意味着巨大的性能冲击。当然,您会希望在事务中执行此操作,以免有人看到与不再存在的用户名相关联的记录。在许多数据库中,这意味着锁定相关行。当然,用户不应该经常更改他们的用户名。但是,如果两个人同时这样做,并且他们共享任何共同的记录(例如,站点内消息),那么您就会发现潜在的问题无处不在。
  • 代理键并不能消除与键更改相关的问题——它们使某些问题更易于管理,但使其他问题更复杂。改变属性不是候选键的说法肯定是错误的。任何属性都可以改变,没有例外——即使是代理。根据定义,候选密钥只需要唯一且不可约 - 稳定性是一种有用且重要的属性,但不是必不可少的。
【解决方案3】:

如果你已经向你的教授证明,为每个用户分配一个唯一的任意整数对你的应用程序是有价值的,那么他当然会说它“完全无用和不必要”是错误的。

但是,也许你错过了他的意思。如果他告诉你要求是“两个用户不能有相同的用户名”,那么你就没有满足这个要求。

衷心感谢您发布您的 SQL DDL,它非常有用,但大多数人并不关心 SO。

使用你的桌子,我可以做到:

INSERT INTO users (username) VALUES (NULL);
INSERT INTO users (username) VALUES (NULL);
INSERT INTO users (username) VALUES (NULL);
INSERT INTO users (username) VALUES (NULL);
INSERT INTO users (username) VALUES (NULL);

结果如下:

SELECT uid, username, passhash, email, rdate 
FROM users;

uid   username   passhash   email   rdate
1     <NULL>     <NULL>     <NULL>  <NULL>
2     <NULL>     <NULL>     <NULL>  <NULL>
3     <NULL>     <NULL>     <NULL>  <NULL>
4     <NULL>     <NULL>     <NULL>  <NULL>

我认为这是您的教授试图提出的观点:如果不对 username 强制执行自然密钥,您根本就没有任何数据完整性。

如果我是教授,我也会敦促您从设计中删除可为空的列。

【讨论】:

  • 谢谢,我确实忘记了 NOT NULL 和 UNIQUE 约束。这是一个更新版本: CREATE TABLE users( uid INT NOT NULL AUTO_INCREMENT, username VARCHAR(30) NOT NULL, passhash VARCHAR(255) NOT NULL, email VARCHAR(60) NOT NULL, rdate DATE NOT NULL, PRIMARY KEY(uid),唯一(用户名,电子邮件));
  • UNIQUE (username, email) 不会像您认为的那样做。您可以尝试测试自己的代码。
  • 是的,那是给@CarlosMarx 的。很抱歉没有说得更清楚。
【解决方案4】:

这通常称为surrogate key,它有很多好处。其中之一是将您的数据库关系与应用程序数据隔离开来。更多细节和相应的缺点可以在上面提供的 wiki 链接中找到。

【讨论】:

    【解决方案5】:

    因为有人可能想更改他们的用户名(或任何名称)。

    【讨论】:

      【解决方案6】:

      您的教授指出您应该使用户名唯一且不可为空,如果要求用户名应该是唯一的,那么您的教授正在做正确的事情。 uid 也可以是一个键,但除非您在某处实际使用它,否则不需要它。设计中更重要的方面应该是实现自然键。所以我同意你教授的评论。

      【讨论】:

      • 我同意他的观点,它应该有一个唯一的约束,但使用像 PK 这样多变的用户名几乎总是错误的。众所周知,对于连接而言,整数比这种大小的 varchar 更快,并且当用户名更改时可能会更新数百万个 FK 记录,这可能会使您的系统停止运行。教授设计的人应该知道,不要为这种多变的东西想要一个自然的钥匙。
      • @HLGM:我从事网络等工作已经近 3 年了。我不记得我的用户名曾经更改过。
      • @HLGEM 谁说用户名是可变的?
      • 可能需要更改用户名,例如例如,如果一个人因结婚而改名。不过这种情况比较少见。键值有时会发生变化只是生活中的一个事实,但这并不会降低它们作为键的重要性或有用性。
      【解决方案7】:

      我需要有更多数据库知识的人来支持我,但我相信你会在外键查找时间内得到更快的响应。

      此外,您稍后可能会决定要更改用户名,或者用户名的要求可能会更改(可能是更长的字符串?)。使用 ID 可以避免更改所有外键。

      让我们面对现实吧,大多数项目都不会那么扩展太多,但你真的想冒着 12 个月后头痛的风险,那时你现在可以符合良好的编程标准吗?

      【讨论】:

      • VARCHAR(4) 占用的字节数与 INT 一样多;小于 INT,但根据规则,它并不可行。
      • 当然这是 varchar (60) 所以它几乎肯定会更慢。
      【解决方案8】:

      例如,整数搜索 (?id=5) 比字符串搜索 (?username=bob) 快得多并且具有更高的基数。 另一个例子,uid 是 auto_increment,因此您不必显式插入它,但它会在每个插入查询中自动递增。

      PS:你的教授说错了:D

      【讨论】:

      • 如果它们是唯一的,那么字符串或数字的肉体性将是相同的。如果在每种情况下都使用哈希索引,那么在每种情况下查找性能将大致相同。因此,根据给出的信息,没有理由说一个比另一个快。无论如何,问题的重点是正确性,而不是性能。教授说的太对了!
      • @dportas,你是那个教授吗?教授大错特错,如果他试图对我的数据库做如此愚蠢的事情,我会解雇他。
      • @HLGEM:我不是他,但我可能会考虑解雇一个愚蠢到允许用户表中存在重复用户的人——或者至少我会通过让他们定义来重新教育他们代理之前的自然键。我希望教授也有同感。
      【解决方案9】:

      我们使用ID来防止重复数据,它可以使一些过程变得不复杂(如果我们想更新或删除数据),如果我们使用ID更简单。

      如果您不想使用 ID,您可以使用其他字段。但不要忘记让它们变得独一无二。它可以使您的数据免受重复数据的影响。

      PRIMARY 之外的另一种方式是 UNIQUE。

      【讨论】:

        【解决方案10】:

        我同意以上所有答案。我想说一个 ID 很容易实现,在索引方面,与 varchar 相比,Int 总是首选。你的教授应该更清楚,他为什么要拒​​绝Int id在我之上!

        【讨论】:

          【解决方案11】:

          因为 userid 应该是唯一的(不能重复)并且有时是索引。

          【讨论】:

            【解决方案12】:

            您想以明文形式存储您的用户名以供任何人窃取吗?我永远不会考虑使用我将来可能想要加密(或现在想要加密)的自然密钥。

            【讨论】:

            • 如果安全是一个问题,那么您不会散列用户名而不是加密它吗?哈希应该是一个好的密钥。如果您不同意,那么您将使用什么自然键来唯一标识用户?我认为我们同意原始设计是错误的,但您对 OP 有什么建议?
            猜你喜欢
            • 2013-09-27
            • 2012-01-31
            • 2020-11-04
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-06-12
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多