使用电子邮件地址作为主键?

249

与自动增加数字相比,电子邮件地址是否不适合作为主键?

我们的Web应用程序需要在系统中唯一使用电子邮件地址。因此,我想将电子邮件地址用作主键。然而,我的同事建议字符串比较速度较慢于整数比较。

这是不使用电子邮件作为主键的有效理由吗?

我们正在使用PostgreSQL


6
“primary”是什么意思?如果电子邮件地址需要是唯一的,那么它就是一个键,需要一个唯一约束。将其“提升”为“primary”是任意的,除非有实际原因,例如优化性能差的系统。 - onedaywhen
114
如果有人想要更改他的电子邮件地址,那么你会同时更改所有外键吗? - systempuntoout
2
主键通常用于被子实体引用。可能还会将子数据导出到辅助系统/数据库中。由于电子邮件往往随时间而变化,因此需要级联更改。作为主键,我会使用比电子邮件地址更不容易更改的内容。 - MikeD
25个回答

302

字符串比较比整数比较慢。但是,如果您仅使用电子邮件地址从数据库中检索用户,则这并不重要。如果您有包含多个连接的复杂查询,则这很重要。

如果您将有关用户的信息存储在多个表中,则用户表的外键将是电子邮件地址。这意味着您会多次存储电子邮件地址。


18
问题不在于电子邮件地址被多次存储,尽管这显然效率低下,但是今天谁还在乎硬盘空间呢。大多数企业没有像谷歌那样的规模,所以这并不重要。问题在于电子邮件地址不能被后续更改,因为它既是主键又作为外键引用。 - Stefan Steiger
如果有人像我一样好奇,GUID密钥应该相当于电子邮件密钥。 - tofutim
@StefanSteiger 謝謝您提供的信息,那麼將 uuid 保存為 varbinary 主鍵會更好,對吧? - MADforFUNandHappy
@MADforFUNandHappy:是的,uuid可能更好。但你可以使用任何你想要的作为主键(自增int/bigint/int128、uuid、varbinary)。只是如果你把数据放到主键中,数据就变成不可更改的了,因为你会有外键引用主键(也就是数据)。一旦发生这种情况,你就不能再更改你的数据了(例如电子邮件地址)。 - Stefan Steiger
@StefanSteiger,如果邮箱改变,是否可以在用户表中创建一行新的数据,复制自原始数据,然后将所有外键引用更改为新的电子邮件地址(新行)?我认为这样做没有任何不利之处 - undefined
1
@André Alçada Padez:你也可以将外键标记为可更新的。 如果你将每个外键都标记为可更新,只要该表中的电子邮件尚未存在,你就可以通过更新语句来更改主键。 - undefined

193

我还要指出,将电子邮件作为唯一字段是不明智的选择,因为有些人甚至小型企业共用一个电子邮件地址。而且,像电话号码一样,电子邮件也可能被重复使用。 比如jsmith@somecompany.com可能一年后属于John Smith,两年后属于Julia Smith。

另一个问题是,电子邮件经常发生变化。如果您将其作为键与其他表连接,那么您还需要更新其他表,当整个客户公司更改其电子邮件时,这可能会对性能产生相当大的影响(我见过这种情况发生)。


10
啊,我根本不喜欢这句话...代理键也可能是问题的根源;是的,应用程序将更加强大,可以适应业务和/或完整性规则的变化,但信息会更容易丢失,记录的身份也变得不那么清晰。因此,我不建议在这里有一个经验法则... - Unreason
4
在我从事数据库工作的30年中,由于使用代理键,我从未丢失过任何记录。 - HLGEM
12
@onedaywhen和@jay,只是因为你们认为它应该是独一无二的,并不意味着它是独一无二的。而且,夫妻可能是不同的客户。仅仅因为你以前没有遇到过这种情况,并不意味着它不会发生。我已经遇到过,并且确实会发生,这就是为什么电子邮件永远不应被视为唯一,无论你是否认为它应该是这样。这是你要反对的那种要求,因为它本质上是错误的。 - HLGEM
17
@HLGEM:我不想陷入无休止的争论,但在不了解上下文的情况下,不能仅凭假设来说一个提议的密钥不是唯一的。例如,从电话公司的角度来看,电话号码根据定义唯一标识客户。是的,你可以说:“但如果有两个或三个人在你拨打那个号码时可能会回答呢?”但这是无关紧要的。从电话公司的角度来看,这是一个客户,根据定义只能有一个。(接下来...) - Jay
15
同样地,如果你正在构建一个主要涉及电子邮件通信的系统——比如消息分发系统或通知转发系统——那么很可能根据定义,一个电子邮件地址唯一地标识了一个用户。如果多个人共享同一个电子邮件地址,则这是无关紧要的。它们是单一的消息目的地,因此它们是单个用户。“用户”和“客户”不必成为“个人人类”的同义词。 - Jay
显示剩余3条评论

106

主键应该是唯一不变的。

电子邮件地址像季节一样变化。虽然作为查找的辅助键很有用,但却不是主键的好选择。


19
一个好的密钥应该是稳定的,但不一定是不可变的。 - onedaywhen
20
如果你有选择的话,选择常量/不可变的键;这样将来你的工作会更少。仅仅因为SQL支持级联更新,并不意味着它总是一个好主意! - Steven A. Lowe
@StevenA.Lowe,您能详细说明为什么这不是一个好主意吗?您的评论看起来非常聪明(我承认),但对于像我们这样的凡人来说,它缺少完整的解释。 ;) - Antonin GAVREL
@AntoninGAVREL 邮箱地址不是常量;例如,当用户更改电子邮件提供商时,它们可能会更改。主键永远不应更改。 - Steven A. Lowe
谢谢Steven,确实如此。@Robert的答案也很好地解释了为什么这不是一个好主意。 - Antonin GAVREL

69

使用电子邮件地址作为主键的缺点:

  1. 在进行连接操作时速度较慢。

  2. 任何带有已发布外键的其他记录现在都具有更大的值,占用更多磁盘空间。(考虑到当今磁盘空间的成本,这可能是一个微不足道的问题,除非记录现在需要更长时间来读取。参见#1)

  3. 电子邮件地址可能会更改,这将迫使使用此作为外键的所有记录进行更新。由于电子邮件地址并不经常更改,因此性能问题可能不大。更大的问题是您必须确保提供它。如果您必须编写代码,则需要更多工作,并引入出错的可能性。如果您的数据库引擎支持“on update cascade”,那么这只是一个小问题。

使用电子邮件地址作为主键的优势:

  1. 您可以完全消除一些连接。如果您从“主记录”中所需的仅是电子邮件地址,则使用抽象整数键,您必须进行连接才能检索它。如果键是电子邮件地址,则您已经拥有它,连接是不必要的。这是否有所帮助取决于该情况有多常发生。

  2. 在进行自由查询时,人类可以轻松看到正在引用哪个主记录。这在尝试跟踪数据问题时可能非常有帮助。

  3. 您几乎肯定需要在电子邮件地址上建立索引,因此将其作为主键消除了一个索引,从而改善了插入的性能,因为现在只需更新一个索引而不是两个。

在我看来,这并不是一个显然的选择。当存在实用的自然键时,我倾向于使用它们,因为它们更容易使用,并且缺点在大多数情况下并不真正重要。


@Conrad:虽然他指出如果你有一个支持ON UPDATE CASCADE的引擎,那么这不是一个麻烦事。在代码方面,这已经不是问题了;唯一的真正问题是更新有多广泛和关键字有多宽。电子邮件地址可能有点过头了,但对于一个2个字符国家代码的主键来说,CASCADE UPDATE并不是什么大问题。 - Matthew Wood
5
在我看来,这仍然是一个很麻烦的问题。举个例子,假设你设计国家表时只有两个引用它的表,那没什么大不了的。但随着时间的推移,会有20个表引用它,每个表都有数十万条记录,一次逻辑写操作就会变成数万次写操作,而且并不是所有的表都能更新到,因为当添加表时可能会忘记引用。这正是我在一个仅包含两个字符的国家代码表上遇到的问题,我可不是开玩笑。 - Conrad Frix
@Wood & Conrad: 最糟糕的情况是没有内置的数据库支持。那么,您必须为每个带有发布引用的表编写代码,这只会带来麻烦和错误的入口。使用级联,您只需记住在每个表上添加一个子句,不是什么大问题。 - Jay
2
优势1和3是过早的优化,优势2是非常微小的好处,并且完全可以被任何体面的查询工具克服。 - Ash
4
@Ash: “优化”和“过早优化”是有区别的。但是,按照同样的推理,我看到任何人提到的所有缺点都是过早优化。那你怎么办呢?至于第二点,当我尝试进行临时查询时,在键入额外连接时会遇到很大的麻烦。记录通常有多个外键,因此您可能需要多个连接才能获取可理解的数据。如果“体面的查询工具”是指一个可以在没有告诉它数据之前就自动找出您想要查看的数据并自动连接的工具,我想看看它是如何工作的。 - Jay

15
没有人提到电子邮件地址可能被视为私人信息的潜在问题。如果电子邮件地址是主键,那么个人资料页面的URL很可能会像这样:..../Users/my@email.com。如果你不想公开用户的电子邮件地址,你就需要找到其他识别用户的方式,可能是通过唯一的整数值来创建URL(如..../Users/1)。然后,你最终还是得到了一个唯一的整数值。

12

这很糟糕。假设某个电子邮件提供商停业了,用户将希望更改他们的电子邮件地址。如果您使用电子邮件作为主键,所有用户的外键都会复制该电子邮件地址,使更改变得非常困难...

...而且我还没有开始讨论性能方面的考虑。


更改电子邮件地址会导致重复吗?除非用户A更改了他的电子邮件地址,然后用户B将其电子邮件更改为与用户A的旧值相同,并且您的更新不是按顺序完成的。我猜这种情况很少见。 - Jay
2
外键引用根据定义包含所指行主键的值。换句话说,它复制了主键的值。(因此,这种重复不是由于更改值引起的。但由于这种重复和强制执行约束条件,更改变得更加困难)。 - meriton
这不是问题。外键级联存在的目的就是解决这个问题。如果一个用户修改了他们的电子邮件,这个变化将会级联到所有使用它作为外键的表中。 - Rafa
1
@rafa,我向你保证,如果你使用级联更新,并且整个提供者倒闭或更改名称(例如Yahoo.com变成HooYa.com),你的数据库将被锁定数小时甚至数天,直到系统中的级联操作完成。这是一个非常真实的问题(也是为什么在有大量数据和关键字可能会更改的情况下使用级联更新是一个不好的想法的原因之一)。 - HLGEM

12

我不知道在你的设置中是否可能出现问题,但根据你的关系型数据库管理系统(RDBMS)不同,列的值可能是区分大小写的。PostgreSQL文档说:“如果您将某个列声明为UNIQUE或PRIMARY KEY,则隐式生成的索引区分大小写”。换句话说,如果您接受用户输入来搜索具有电子邮件作为主键的表,并且用户提供了“John@Doe.com”,那么您将找不到“john@doe.com”。


7
值得一提的是,John@Doe.comjohn@Doe.com可能是同一个邮箱,也可能是不同的邮箱,你无法判断 - 规范中没有说明本地部分是否区分大小写。 - telent

9
逻辑级别,电子邮件是自然键。在物理级别上,如果您使用关系型数据库,则自然键不适合作为主键。原因主要是其他人提到的性能问题。
因此,设计可以进行调整。自然键成为备用键(UNIQUE,NOT NULL),并且您使用一个代理/人工/技术键作为主键,在您的情况下可以是自动递增的。
systempuntoout问道,

如果有人想更改他的电子邮件地址怎么办?您会更改所有外键吗?

这就是级联的作用。
使用数字代理键作为主键的另一个原因与平台中索引的工作方式有关。例如,在MySQL的InnoDB中,表中的所有索引都在它们之前附加了主键,因此您希望PK尽可能小(出于速度和大小的考虑)。此外,与此相关的是,当主键按顺序存储时,InnoDB更快,并且字符串在这里没有帮助。
使用字符串作为备用键时需要考虑的另一件事是,使用所需字符串的哈希可能会更快,跳过某些字母的大小写等内容。(实际上,我在查找参考以确认我刚才说的话时来到了这里;仍在寻找……)

5

使用整数会更好。您还可以将电子邮件列设置为唯一约束。

示例:

CREATE TABLE myTable(
    id integer primary key,
    email text UNIQUE
);

9
为什么它更好?有原因或来源吗? - Sjoerd
21
你能详细说明一下吗? - Sjoerd

5

是的,这是一个糟糕的主键,因为你的用户会想要更新他们的电子邮件地址。


1
我想指出,现在我们有级联,这不再是一个问题。 - malhal

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接