NVARCHAR 列作为 PRIMARY KEY 或 UNIQUE 列

Van*_*nel 16 sql-server primary-key unique-constraint

我正在开发一个 SQL Server 2012 数据库,我对 nvarchar 列作为主键有疑问。

我有这张桌子:

CREATE TABLE [dbo].[CODES]
(
    [ID_CODE] [bigint] IDENTITY(1,1) NOT NULL,
    [CODE_LEVEL] [tinyint] NOT NULL,
    [CODE] [nvarchar](20) NOT NULL,
    [FLAG] [tinyint] NOT NULL,
    [IS_TRANSMITTED] [bit] NOT NULL DEFAULT 0,
     CONSTRAINT [PK_CODES] PRIMARY KEY CLUSTERED 
    (
        [CODE_LEVEL] ASC,
        [CODE] ASC
    )
)
Run Code Online (Sandbox Code Playgroud)

但现在我想使用[CODE]列作为主键并删除[ID_CODE]列。

如果我有一个NVARCHAR专栏,有什么问题或惩罚PRIMARY KEY吗?

[CODE]列值必须是唯一的,所以我认为我可以UNIQUE为该列设置约束。

我是否必须用[CODE]作主键,还是UNIQUE[CODE]列上设置约束更好?

Sol*_*zky 16

是的,对于主键使用字符串而不是数字类型绝对会产生负面影响,如果该 PK 是集群的(在您的情况下确实如此),则更是如此。但是,您看到使用字符串字段的效果的程度取决于 a) 该表中有多少行,以及 b) 其他表中有多少行是外键到这个 PK 的。如果您在该表中只有 10k 行,而在通过该字段 FK 到该表的其他几个表中只有 100k 行,那么它可能不会那么明显。但随着行数的增加,这些影响肯定会变得更加明显。

您需要考虑将聚集索引中的字段转移到非聚集索引。因此,您不只是查看每行最多 40 个字节,而是查看 (40 * some_number) 个字节。并且在任何 FK 表中,您在行中都有相同的 40 个字节,而且该字段上通常会有一个非聚集索引,因为它在 JOIN 中使用,所以现在它在 FK 到的任何表中实际上翻了一番这个。如果有人倾向于认为 40 字节 * 100 万行 * 10 个副本没什么可担心的,请参阅我的文章磁盘便宜!奥利?其中详细说明了受此决定影响的所有(或至少大部分)领域。

另一件要考虑的事情是对字符串进行过滤和排序,特别是在不使用二进制排序规则时(我假设您使用的是数据库默认值,通常不区分大小写)比使用INT/时效率低得多(即需要更长的时间)BIGINT。这会影响在此字段上过滤/加入/排序的所有查询。

因此,CHAR(5)对于集群 PK使用类似的东西可能没问题,但主要是如果它也定义为COLLATE Latin1_General_100_BIN2(或类似的东西)。

价值会[CODE]永远改变吗?如果是,则更有理由不将其用作 PK(即使您确实将 FK 设置为ON UPDATE CASCADE)。如果它不能或永远不会改变,那很好,但仍然有足够的理由不将其用作集群 PK。

当然,该问题的措辞可能不正确,因为您目前的 PK 中似乎已经有此字段。

无论如何,到目前为止,您最好的选择是[ID_CODE]用作集群 PK,在相关表中使用该字段作为 FK,并保留[CODE]为 a UNIQUE INDEX(这意味着它是一个“备用键”)。



在对此答案的评论中更新基于此问题的更多信息:

如果我使用 [CODE] 列查找表,[ID_CODE] 作为 PRIMARY KEY 是最好的选择吗?

这一切都取决于很多因素,其中一些我已经提到过,但将重申:

主键是识别单个行的方式,无论它是否被任何外键引用。您的系统在内部识别行的方式与您的用户识别自己/该行的方式有关,但不一定相同。任何具有唯一数据的 NOT NULL 列都可以工作,但需要考虑实用性问题,尤其是当 PK 实际上被任何 FK 引用时。例如,GUID 是独一无二的,有些人出于各种原因真的很喜欢使用它们,但它们对于聚集索引来说非常糟糕(NEWSEQUENTIALID更好,但并不完美)。另一方面,GUID 作为备用键很好,应用程序使用它来查找行,但 JOIN 仍然使用 INT(或类似)PK 完成。

到目前为止,您还没有告诉我们该[CODE]字段如何从各个角度融入系统,除了现在提到这是您查找行的方式之外,但这是针对所有查询还是仅针对某些查询?因此:

  • 关于[CODE]价值:

    • 它是如何产生的?
    • 它是增量的还是伪随机的?
    • 是等长还是变长?
    • 使用了哪些字符?
    • 如果使用字母字符:是区分大小写还是不区分大小写?
    • 插入后它会改变吗?
  • 关于这张表:

    • 这张桌子还有其他桌子吗?或者这些字段([CODE][ID_CODE])是否在其他表中使用,即使没有明确外键?
    • 如果 [CODE]是用于获取单个行的唯一字段,那么该[ID_CODE]字段的用途是什么?如果不使用它,为什么首先要使用它(这可能取决于“该[CODE]领域是否会改变?”的答案)?
    • 这个表有多少行?
    • 如果其他表引用这个表,每个表有多少行?
    • 这个表的索引是什么?

这个决定不能纯粹根据“NVARCHAR 是还是否?”的问题做出。我再说一遍,总的来说,我认为这不是一个好主意,但肯定有一些时候它是好的。鉴于此表中的字段很少,因此不太可能有更多索引,或者至少没有很多索引。因此,您可以使用任何一种方式[CODE]作为聚集索引。如果没有其他表引用此表,那么您也可以将其设为 PK。但是,如果其他表确实引用了这个表,那么我会选择该[ID_CODE]字段作为 PK,即使是非聚集的。


Rem*_*anu 6

您必须将概念分开:

  • 主键是一种设计理念,是表中条目的逻辑属性。它在表条目的生命周期内应该是不可变的,并且应该是应用程序中用来引用条目的键。

  • 聚集索引是一种存储概念,一种物理属性。它应该是查询最常见的访问路径,它应该满足大多数情况下的覆盖索引,并尽可能多地满足范围查询。

主键不需要是聚集索引。您可以拥有ID_CODEPK 和(CODE_LEVEL, CODE)集群密钥。或者反过来。

较大的聚集键会产生一些负面影响,因为较宽的键意味着索引页上的密度较低,并且所有非聚集索引消耗的大小较大。在这个话题上已经有大量的墨水,例如。从更多关于聚簇键的考虑开始——聚簇索引的争论还在继续!.

但问题的要点是,聚集索引键的选择主要是一种权衡。一方面,您对存储大小有要求,这会对性能产生普遍影响(更大的密钥 -> 更大的尺寸 -> 更多的 IO,并且 IO 带宽可能您拥有最稀缺的资源)。另一方面,以节省空间的名义选择错误的聚集键可能会产生查询性能后果,通常比宽键导致的问题更糟糕。

至于主键的选择,它甚至不应该是一个问题:您的数据模型、您的应用程序逻辑应该决定主键是什么。

话虽这么说,我的2C型:NVARCHAR(20)不是宽。是完全可以接受的聚集键大小,即使对于大表也是如此。

  • 我认为必须考虑整个表和所有索引的确切 DDL、引用它的外键、估计的行数、预期的查询工作量、应用程序预期的 SLA,而不是至少可用的硬件和许可预算。 (2认同)

小智 3

除了在不知道的情况下使用 nvarchar/varchar 时使用宽键的风险之外,不应该有任何固有/明显的惩罚。特别是当您开始将它们组合成复合键时。

但在你的 (20) 长度的例子中,你应该没问题,我不会太担心。因为如果 CODE 是您主要查询数据的方式 - 聚集索引听起来非常明智。

但是,您应该考虑您是否真的希望将其作为主键或只是唯一(聚集)索引。聚集索引和主键之间存在(小)差异(基本上 - 主键标识您的数据,但索引是您查询数据的方式),因此,如果您愿意,您可以轻松地将 ID_Code 作为主键,通过 CODE 创建唯一的聚集索引。(注意:SQL Server会自动将你的主键变成聚集索引,除非你自己手动创建了聚集索引)

还要考虑现在您是否确实需要 ID_Code,因为您已经有了唯一的 CODE。

  • 实际上,“NVARCHAR(20)”的大小(最大)为 **40** 字节,并且由于它是*可变长度*列,因此它实际上并不是聚集索引的最佳选择。“ID_CODE”作为“BIGINT IDENTITY”将是**更好的**选择! (2认同)