【问题标题】:Use email address as primary key?使用电子邮件地址作为主键?
【发布时间】:2011-04-17 18:35:59
【问题描述】:

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

我们的 Web 应用程序需要电子邮件地址在系统中是唯一的。所以,我想到了使用电子邮件地址作为主键。但是我的同事建议字符串比较会比整数比较慢。

不使用电子邮件作为主键是否有正当理由?

我们正在使用PostgreSQL

【问题讨论】:

  • “主要”是什么意思?如果电子邮件地址需要是唯一的,那么它是一个键并且需要一个唯一的约束。您是否决定将其“推广”为“主要”是任意的,除非有实际原因,例如优化性能不佳的系统。
  • 如果您希望您的数据库强制使用唯一的电子邮件地址,请创建具有唯一索引的列,但不要将其用作主键。
  • @robert 如果有人想更改他的电子邮件地址怎么办?你也要改变所有的外键吗?
  • @onedaywhen - 几乎没有任何区别,但默认情况下主键将被聚集,而唯一索引不会。您仍然需要定义将作为默认单记录查找键的主键,唯一索引仅在普通索引上强制列的唯一性
  • @James Westgate:仅供参考,PostgreSQL 中没有自动集群之类的东西。 PRIMARY KEY 在磁盘上的实现与 UNIQUE INDEX 完全相同,其中所有字段都不为 NULL。

标签: sql database database-design postgresql


【解决方案1】:

您可以通过使用整数主键来提高性能。

【讨论】:

    【解决方案2】:

    是的,最好使用整数代替。您还可以将您的电子邮件列设置为唯一约束。

    像这样:

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

    【讨论】:

    • 为什么“更好”?有什么原因或来源?
    • 你能详细说明一下吗?
    【解决方案3】:

    您应该使用整数主键。如果您需要电子邮件列是唯一的,为什么不简单地在该列上设置一个唯一索引?

    【讨论】:

      【解决方案4】:

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

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

      【讨论】:

      • @Sjoerd:问题不在于电子邮件地址被多次存储,尽管这绝对是低效的,但今天谁在乎硬盘空间。大多数企业没有谷歌规模,这很重要。问题是之后无法更改电子邮件地址,因为它既是主键又被引用为外键。
      • @StefanSteiger 谁说过硬盘空间?您存储的任何内容都会占用 RAM 中的空间。
      • 如果有人像我一样想知道,我认为 GUID 密钥将等同于电子邮件密钥。
      • @StefanSteiger 感谢您提供这些信息,所以保存为 varbinary 主键的 uuid 会更好,对吧?
      • @MADforFUNandHappy:是的,uuid 会更好。但是您可以使用任何您想要的作为主键(自动递增 int/bigint/int128、uuid、varbinary)。只是如果您将数据放入主键中,则数据将变得不可更改,因为您将拥有引用主键(也称为数据)的外键。一旦发生这种情况,您将无法再更改您的数据(例如电子邮件地址)。
      【解决方案5】:

      整数主键更好的另一个原因是当您在不同的表中引用电子邮件地址时。如果地址本身是主键,那么在另一个表中您必须将其用作键。因此,您可以多次存储电子邮件地址。

      【讨论】:

        【解决方案6】:

        你的同事是对的:为你的主键使用一个自动递增的整数。

        您可以在应用程序级别实现电子邮件唯一性,也可以将您的电子邮件地址列标记为唯一,并在该列上添加索引。

        将字段添加为唯一字段只会在插入该表时花费字符串比较,而不是在执行连接和外键约束检查时。

        当然,您必须注意,在数据库级别向您的应用程序添加任何约束都会导致您的应用程序变得不灵活。在您将任何字段设置为“唯一”或“非空”之前,请始终给予适当的考虑,因为您的应用程序需要它是唯一的或非空的。

        【讨论】:

        • "仅仅因为您的应用程序需要需求 x,所以在实现需求 x 之前始终给予应有的考虑。" -- 很长一段时间以来我读过的最糟糕的建议。
        • 我不相信你的“论点”——在现实生活中,经常会出现一些基本数据(例如电话号码)无法立即获得的情况。如果这样的字段在数据库中被标记为 NOT NULL,它将要求用户使用虚拟字段(如 123)污染数据,而不是将其留空。让应用程序处理约束会更实际(在这种情况下,应用程序可以将空字段标记为操作项)。
        • 我同意定义一个“非空”字段应该谨慎。应仔细考虑诸如“我们总是需要客户的电话号码”之类的要求。即使我们现在不知道电话号码,有时也可能不希望创建客户记录,然后再回去获取它?但是“这个字段必须是唯一的”是一个不同的类别。我无法想象会说“两个员工拥有相同的社会安全号码是可以的,我们稍后会弄清楚。”您将如何整理数据?
        • Be Wolves:我认识一个没有自己电话号码的女人。那你怎么办?
        • @DavidThornley 听起来你应该多锻炼,或者换一种更友好的举止。
        【解决方案7】:

        我对 postgres 不太熟悉。主键是一个很大的话题。我在这个网站 (stackoverflow.com) 上看到了一些很好的问题和答案。

        我认为通过使用数字主键并在电子邮件列上使用 UNIQUE INDEX,您可能会获得更好的性能。电子邮件的长度往往不同,可能不适合主键索引。

        一些阅读herehere.

        【讨论】:

          【解决方案8】:

          我还要指出,电子邮件是创建一个独特领域的错误选择,有些人甚至小型企业共享一个电子邮件地址。就像电话号码一样,电子邮件可以重复使用。 Jsmith@somecompany.com 一年后很容易属于 John Smith,两年后属于 Julia Smith。

          电子邮件的另一个问题是它们经常变化。如果您要以它作为键加入其他表,那么您还必须更新其他表,当整个客户公司更改他们的电子邮件时(我已经看到这种情况发生),这可能会对性能造成很大影响。

          【讨论】:

          • +1 用于提及级联更新问题。这就是为什么朋友让朋友只使用代理键;-)。
          • 啊,我一点也不喜欢这种说法……代理键也​​可能是问题的根源;是的,应用程序对业务和/或完整性规则的更改将更加健壮,但是信息可能会更容易丢失,并且记录的身份变得不那么清晰。所以我会在这里推荐一个经验法则...
          • @onedaywhen 和@jay,仅仅因为你认为它应该是独一无二的,不要让它独一无二。是的,丈夫和妻子可能是不同的客户。只是因为你以前没有遇到过这并不意味着它不会发生。我遇到了它,它确实发生了,这就是为什么无论您认为是否应该允许电子邮件被视为独特的原因。这是你推回的那种要求,因为它本质上是错误的。
          • @HLGEM:我不想陷入无休止的争论,但你不能说在不了解上下文的情况下,基于假设的建议密钥不是唯一的。例如从电话公司的角度来看,根据定义,电话号码唯一地标识了客户。是的,你可以说,“但是如果你拨打那个号码时有两三个人可能会接听呢?”但这无关紧要。从电话公司的角度来看,根据定义,这是一位客户。 (续...)
          • (续)同样,如果您正在构建一个主要关注电子邮件通信的系统——可能是消息发送系统或通知转发系统——那么根据定义,很可能电子邮件地址唯一标识用户。如果多人共享该电子邮件地址,则无关紧要。它们是单个消息目的地,因此,它们是单个用户。 “用户”和“客户”不必是“个人”的同义词。
          【解决方案9】:

          这很糟糕。假设某个电子邮件提供商倒闭了。然后用户会想要更改他们的电子邮件。如果您使用电子邮件作为主键,则用户的所有外键都会复制该电子邮件,因此很难更改...

          ...我什至还没有开始谈论性能方面的考虑。

          【讨论】:

          • 更改电子邮件地址会如何导致重复?除非用户 A 更改了他的电子邮件地址,然后用户 B 将他的电子邮件更改为与用户 A 的旧值相同,并且您的更新不是按顺序完成的。我猜是远程可能的。
          • 外键引用,根据定义,包含它所引用行的主键值。换句话说,它复制了主键的值。 (因此重复不是由更改值引起的。但是由于这种重复以及强制执行它的约束,更改更加困难)。
          • +1 表示“假设某些电子邮件提供商倒闭了。”
          • 这不是问题。存在外键级联来解决这个问题。如果用户更改了他们的电子邮件,则更改将级联到使用它作为外键的所有表。
          • @rafa,我向您保证,如果您使用级联更新并且整个提供商停业或更改名称(Yahoo.com 变为 HooYa.com),您的数据库将被锁定给所有用户几个小时甚至几天,而这通过系统级联。这是一个非常有效的问题(如果您有大量数据并且密钥可能会更改,那么使用级联更新不是一个好主意的原因。)
          【解决方案10】:

          主键应该是唯一的常量

          电子邮件地址随季节变化。可用作查找的辅助键,但作为主键的选择不佳。

          【讨论】:

          • 一个好的密钥的一个属性是它应该是稳定的,但不一定是不可变的。
          • @onedaywhen:是的!否则 SQL 为什么要支持级联更新?
          • 如果您有选择,请选择常量/不可变键;减少你的工作量;仅仅因为 SQL 支持级联更新并不意味着它总是一个好主意!
          • @Vincent Malgrat:“级联更新...刹车数据库规范化”——我认为你误解了规范化的概念!
          • @Vincent Malgrat:感谢您确认您确实误解了规范化的概念。 “你不应该在多行上重复相同的信息”——你真的是想说“信息”吗?!复合键通常会涉及在多行上重复的值。对于外键,值是引用而不是“重复”,差别很大。具有两个值(例如“是”和“否”)的单列域如果具有三行或更多行,则在引用表中的多行上将具有相同的值。这真的是基本的东西!
          【解决方案11】:

          如果您有一个非 int 值作为主键,那么在大数据上插入和检索将非常慢。

          【讨论】:

          • 不,插入它会,因为您需要两个唯一索引:一个在生成的主键上,另一个在电子邮件地址上.
          【解决方案12】:

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

          1. 连接时变慢。

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

          3. 电子邮件地址可能会更改,这会强制更新使用此地址作为外键的所有记录。由于电子邮件地址不会经常更改,因此性能问题可能很小。更大的问题是你必须确保提供它。如果您必须编写代码,这是更多的工作,并引入了错误的可能性。如果您的数据库引擎支持“更新级联”,这是一个小问题。

          使用电子邮件地址作为主键的好处:

          1. 您也许可以完全消除某些连接。如果您从“主记录”中需要的只是电子邮件地址,那么您必须使用抽象整数键进行连接才能检索它。如果密钥是电子邮件地址,那么您已经拥有它并且不需要加入。这是否对您有帮助取决于这种情况出现的频率。

          2. 当您进行临时查询时,人们很容易看到正在引用的主记录。在尝试追踪数据问题时,这可能会有很大帮助。

          3. 您几乎可以肯定无论如何都需要在电子邮件地址上建立索引,因此将其设为主键会消除一个索引,从而提高插入的性能,因为它们现在只需更新一个索引而不是两个。

          以我的拙见,无论哪种方式,这都不是灌篮。当有实用的可用时,我倾向于使用自然键,因为它们更容易使用,而且在大多数情况下,缺点往往并不重要。

          【讨论】:

          • @Conrad:虽然,他确实指出,如果你有一个支持 ON UPDATE CASCADE 的引擎,它就不是 PITA。在代码方面,这不是问题。唯一真正的问题是更新有多广泛以及关键有多广泛。电子邮件地址可能有点多,但对于 2 个字符的国家/地区代码 PK 的 CASCADE UPDATE 没什么大不了的。
          • @Matthew 恕我直言,它仍然是 PITA。例如假设当你设计你的国家表时,只有两个表引用它,没什么大不了的,但随着时间的推移,它变成了 20 个表,每个表都有数十万条记录。有的有参考,有的没有。这使得单个逻辑写入最终成为数万次写入,并且它不会写入所有表,因为有人在添加表时忘记了引用。这正是我在 2 字符国家代码表上发生的事情,我不骗你。
          • @Wood & Conrad:最坏的情况是没有内置数据库支持。然后你必须为每个表编写代码并附上引用,这只是一个痛苦和一个让错误溜进来的门。使用级联,你只需要记住在每个表上添加一个子句,而不是这样很重要。
          • 优势 1 和 3 是过早的优化,优势 2 是一个非常小的优势,任何体面的查询工具都可以完全克服。
          • @Ash:这是“优化”和“过早优化”之间的区别。但是好吧,出于同样的原因,我看到任何人提到的所有缺点都是过早的优化。那么,这让你何去何从?至于#2,我发现在尝试进行临时查询时输入额外的连接是一个主要的痛苦。记录通常有多个外键,因此您可能需要多个连接才能获得可理解的数据。如果您所说的“体面的查询工具”是指一种无需您告诉它就可以计算出您想要查看的数据并神奇地为您执行连接的工具,我想看看它是如何工作的。
          【解决方案13】:

          这取决于表。如果表中的行代表电子邮件地址,则电子邮件是最佳 ID。如果不是,那么电子邮件不是一个好的 ID。

          【讨论】:

            【解决方案14】:

            使用 GUID 作为主键...这样您就可以在执行 INSERT 时从程序中生成它,而无需从服务器获得响应来找出主键是什么。它在表和数据库中也是唯一的,如果有一天你截断表并且自动增量重置为 1,你不必担心会发生什么。

            【讨论】:

            • 除非您几乎不关心性能,否则请使用 GUID。如果您正在构建一个需要扩展的系统,那就不行了#1
            • 以真正的 Microsoft-Kool-Aid-drinking 方式说!
            【解决方案15】:

            如果只是要求电子邮件是唯一的,那么您可以使用该列创建唯一索引。

            【讨论】:

              【解决方案16】:

              就我个人而言,我在设计数据库时不会使用任何信息作为主键,因为以后很可能需要更改任何信息。我提供主键的唯一原因是,从客户端执行大多数 SQL 操作很方便,我一直选择自增整数类型。

              【讨论】:

                【解决方案17】:

                我不知道这是否是您设置中的问题,但根据您的 RDBMS,列的值可能区分大小写。 PostgreSQL 文档说:“如果将列声明为 UNIQUE 或 PRIMARY KEY,则隐式生成的索引区分大小写”。换句话说,如果您接受用户输入在以电子邮件作为主键的表中进行搜索,并且用户提供“John@Doe.com”,您将找不到“john@doe.com”。

                【讨论】:

                • 在这方面值得一提的是,John@Doe.com 和 john@Doe.com 可能是同一个邮箱,也可能是不同的邮箱,您无法判断 - 规范中没有什么可说的本地部分是否区分大小写。
                • 这是一个更普遍的问题,与电子邮件地址的唯一性强制执行有关,而不是它们是否应该用作主键 - 无论哪种方式都存在相同的问题。 +1 因为它仍然是一个非常有用的点
                【解决方案18】:

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

                【讨论】:

                • 我想我会指出,现在我们有级联,这不是问题
                【解决方案19】:

                似乎没有人提到电子邮件地址可能被视为私密的问题。如果电子邮件地址是主键,则个人资料页面 URL 很可能类似于 ..../Users/my@email.com。如果您不想公开用户的电子邮件地址怎么办?您必须找到一些其他方式来识别用户,可能通过一个唯一的整数值来制作像..../Users/1 这样的 URL。那么你最终会得到一个唯一的整数值。

                【讨论】:

                  【解决方案20】:

                  电子邮件是一个很好的唯一索引候选,但不适用于主键,如果它是主键,您将无法更改联系人的电子邮件地址。 我认为您的联接查询也会变慢。

                  【讨论】:

                    【解决方案21】:

                    logical level,电子邮件是自然键。 在 物理 级别,假设您使用的是关系数据库,自然键与主键不太适合。原因主要是别人提到的性能问题。

                    因此,可以调整设计。自然键变为alternate key(唯一,非空),您使用surrogate/artificial/technical key 作为主键,在您的情况下可以是自动增量。

                    systempuntoout 询问,

                    如果有人想更改他的电子邮件地址怎么办?是否也要更改所有外键?

                    这就是cascading 的用途。

                    使用数字代理键作为主键的另一个原因与索引在您的平台中的工作方式有关。例如,在 MySQL 的 InnoDB 中,表中的所有索引都预先添加了主键,因此您希望 PK 尽可能小(为了速度和大小)。同样与此相关的是,当主键按顺序存储时,InnoDB 速度会更快,而字符串在那里无济于事。

                    使用字符串作为备用键时要考虑的另一件事是,使用您想要的实际字符串的哈希可能会更快,跳过某些字母的大写和小写之类的内容。 (实际上我是在寻找参考资料以确认我刚才所说的话时来到这里的;还在寻找……)

                    【讨论】:

                      【解决方案22】:

                      我知道这有点晚了,但我想补充一点,人们放弃了电子邮件帐户,服务提供商恢复了允许其他人使用它的地址。

                      正如@HLGEM 指出的那样,“Jsmith@somecompany.com 一年后很容易属于 John Smith,两年后属于 Julia Smith。”在这种情况下,如果 John Smith 需要您的服务,您要么必须拒绝使用他的电子邮件地址,要么删除您与 Julia Smith 有关的所有记录。

                      如果您必须删除记录,并且根据当地法律,它们与企业的财务历史相关,您可能会陷入困境。

                      因此,我永远不会使用电子邮件地址、车牌等数据作为主键,因为无论它们看起来多么独特,它们都超出了您的控制范围,并且可能会带来一些您可能没有时间处理的有趣挑战.

                      【讨论】:

                        【解决方案23】:

                        主键应该选择一个静态属性。由于电子邮件地址不是静态的,并且可以由多个候选人共享,因此将它们用作主键并不是一个好主意。此外,电子邮件地址通常是具有一定长度的字符串,它可能大于我们想要使用的唯一 ID [len(email_address)>len(unique_id)],因此它需要更多空间,甚至最糟糕的是它们被多次存储为外键.因此会导致性能下降。

                        【讨论】:

                          【解决方案24】:

                          不要使用电子邮件地址作为主键,保持电子邮件唯一但不要使用它作为主键,使用用户 ID 或用户名作为主键

                          【讨论】:

                            【解决方案25】:

                            您可能需要考虑任何适用的数据法规。电子邮件是个人信息,如果您的用户是欧盟公民,例如,根据 GDPR,他们可以指示您从您的记录中删除他们的信息(请记住,无论您位于哪个国家/地区,这都适用)。

                            如果您出于参照完整性或历史原因(例如审计)需要将记录本身保存在数据库中,则使用代理键将允许您将所有个人数据字段设为 NULL。如果他们的个人数据是主键,这显然不是那么容易

                            【讨论】:

                              猜你喜欢
                              • 2020-08-12
                              • 2013-12-08
                              • 1970-01-01
                              • 1970-01-01
                              • 2012-12-09
                              • 2011-04-16
                              • 2013-08-10
                              • 1970-01-01
                              • 1970-01-01
                              相关资源
                              最近更新 更多