【问题标题】:understanding how a primary key works & how to use it了解主键的工作原理以及如何使用它
【发布时间】:2015-11-17 21:05:37
【问题描述】:

我正在使用 SQL Server 并创建一个表(示例在这个问题的最底部)。但是,我在理解主键的实际工作方式以及如何正确使用它们时遇到了一些问题。

所以我知道主键可以确保表中的所有行都是唯一的,并且主键不能为空。我还阅读了这个页面index basics - simple talk,关于索引以及索引是如何在 b 树结构中组织的。

因此,在我的表中,要使一行具有唯一值,我必须使用前 3 列(UploadDate、SecID 和 FundCode 类型的 datetime、varchar(12) 和 varchar(6))。此表将仅使用选择查询,而 where 子句将使用刚才提到的三个字段中的一个或多个。

所以我知道可以在多个列上创建一个主键,所以在我的情况下,它将是上面的 3 个。虽然在我的表上有一个主键有助于提高选择查询的性能?因此,我认为主键会使用列的值(或在我的情况下为 3 列)创建索引或某种排序,我看不出这将有何帮助,因为我的值将是日期时间和两位文本?

有人提到我应该只创建一个带有递增数字的整数列并将其作为主键 - 我看不出这在运行选择查询时有什么帮助,因为新字段没有任何意义 & 不会' t 用于任何选择查询或查询的 where 子句?

type             column name
-------------    ------------
datetime         UploadDate
varchar(12)      SecID
varchar(6)       FundCode
varchar(100)     Name
float            Price
float            Nominal
int              SourceCode
datetime         PriceDate

一些行的例子

UploadDate   SecID    FundCode   Name   Price   Nominal   SourceCode   PriceDate
2015-08-20   A045     ABCVPL     Joe    1.3434  1000.33   3
2015-08-20   A563     ABCVPL     Bob    1.5961  10.33     3
2015-08-20   A045     DEFGHJ     Joe    1.3434  856.41    3
2015-08-20   XC45     PLMNOI     Pip    2.3654  25.52     3
2015-08-20   KMM5     ABCVPL     Nit    6.9565  1532      3
2015-08-21   A045     ABCVPL     Joe    4.3434  1112      3
2015-08-21   GH45     DEFGHJ     Joe    3.3434  16532     3
2015-08-21   PL34     DEFGHJ     Joe    7.3434  635       3
2015-08-21   ER33     ABCVPL     Joe    8.3434  6320      3

【问题讨论】:

标签: sql-server database-design sql-server-2012 primary-key


【解决方案1】:

这个问题似乎混淆了两个不同的概念。第一个是主键,第二个是聚集索引。第一个是逻辑概念,后者是物理概念,指的是数据的实际存储方式。在某些情况下,将主键和集群键解耦是有用的,但在大多数情况下,它们是相同的,并且默认情况下,您的主键将是您的集群键。尽管如此,这是一个重要的区别。

我认为人们可以(并且已经)争论到奶牛们回家是否要use a natural or surrogate primary key。我不会过多地涉及这一点,但基本是您在使用定义唯一行的 3 列时建议的内容是自然键(即您的数据中已经存在),另一种方法是使用身份列,这将为每一行赋予一个唯一值,这是一个代理键,因为它除了唯一标识您的行之外没有任何实际意义。

所以我知道可以在多个列上创建一个主键,所以在我的情况下,它将是上面的 3 个。虽然有一个 我表上的主键有助于提高选择查询的性能?

它没有,根据您的查询,拥有索引可能会有所帮助。给定正确的索引,数据库引擎能够直接导航到所需的数据。

有人提到我应该只创建一个带有递增数字的整数列并将其作为主键 - 我看不出这在运行选择查询时有什么帮助,因为新字段没有任何意义 & 不会'不能在任何选择查询或查询的 where 子句中使用?

这是聚类键的一个很好的候选者。根据索引金伯利·特里普的女王clustered index should be

  • 独特
  • 静态
  • 不断增加的模式

您已经勾选了唯一的框,您的 3 列,这不是那么窄,但无论如何也不宽。第二个我无法回答,如果UploadDate 是在创建时输入的默认值,那么您的模式可能会不断增加,我不知道您的三列是静态的还是可以更改的。如果这最后两个中的任何一个为真,那么您应该使用代理标识列来进行聚类。

我个人可能已经将其作为基于 with(26 字节)的集群键的候选者而消除了。您在聚集索引中每行有额外的 4 个字节,但在所有后续索引中每行节省了 22 个字节。

因此,在包含 10,000,000 行的表中,由于标识列,您获得了额外的 38.1 MB,但是对于每个非聚集索引,您获得了 209.8MB,虽然磁盘空间很便宜,但这不是不必要地浪费它的理由。不仅仅是所有的索引都获得了这 22 个字节,而且所有的引用表都带有外键,这就引出了我的下一点,即编写查询时的便利性。您是否真的希望每次引用密钥时都必须输入此联接:

SELECT  *
FROM    Parent AS p
        INNER JOIN Child AS c
            ON c.UploadDate = p.UploadDate
            AND c.SecID = p.SecID
            AND c.FundCode = p.FundCode;

或者你宁愿简单地写:

SELECT  *
FROM    Parent AS p
        INNER JOIN Child AS c
            ON c.ParentID = p.ParentID;

出于这个原因,即使我已经确定逻辑上的主键不是集群键的良好候选者,我仍然倾向于将集群键作为主键以便于关系表中的引用。例如,我有一个以 XML 格式向我发送订单详细信息的外部 API:

<orders>
    <order ID="12B47EF2-B9F5-4CD7-811F-2E7EC1A67E59">
        <orderdetail>
            <product>Some Product</product>
            <quantity>1</quantity</quantity>
        </orderdetail>
        <orderdetail>
            <product>Some Other Product</product>
            <quantity>2</quantity</quantity>
        </orderdetail>
    </order>
    <order ID="3A819217-49CA-4B4C-8AD5-CAD297FCA3F3">
        <etc />
    </order>
</orders>

如果我设置我的表来存储它,虽然来自 XML 的 ID 将是我的 Orders 表的逻辑主键,但这将是一个糟糕的集群键,所以我会添加一个代理标识字段以避免与 GUID 上的集群相关的碎片:

CREATE TABLE dbo.Orders
(
        OrderID INT IDENTITY NOT NULL,
        SupplierOrderID UNIQUEIDENTIFIER NOT NULL,

    CONSTRAINT PK_Orders__SupplierOrderID PRIMARY KEY NONCLUSTERED (SupplierOrderID)
);
CREATE UNIQUE CLUSTERED INDEX UQ_Orders__OrderID ON dbo.Orders (OrderID);

GUID仍然是主键,所以我的订单明细表可以参考这个,但我一般认为如果我不认为键足够好集群,我为什么还要把同一个键放到另一个表作为外键。我已经在OrderID 中定义了一个更窄的键,为什么不在订单详细信息中使用它作为我的外键,并为自己节省 12 个字节。所以我最终会得到:

CREATE TABLE dbo.Orders
(
        OrderID INT IDENTITY NOT NULL,
        SupplierOrderID UNIQUEIDENTIFIER NOT NULL,

    CONSTRAINT PK_Orders__OrderID PRIMARY KEY CLUSTERED (OrderID)
);
CREATE UNIQUE NONCLUSTERED INDEX UQ_Orders__SupplierOrderID ON dbo.Orders (SupplierOrderID);

与所有事情一样,也有例外,在某些情况下,我会选择 3 列作为复合(聚集)主键,如果我知道不会有子表,并且我所有的选择查询仍然需要我选择 UploadedDateSecIDFundCode。例如,如果您在 Name 上有索引:

CREATE NONCLUSTERED INDEX IX_YourTable__Name ON dbo.YourTable (Name);

SELECT  UploadDate, SecID, FundCode, Name
FROM    dbo.YourTable
WHERE   Name = 'Bob';

如果您有代理键,那么您将在名称索引中查找并仅在第 2 行找到 Bob,然后在聚集索引上查找第 2 行以获取 UploadedDateSecID 和 @ 的对应值987654338@。如果这三列是您的集群键,那么您就不需要查找,因为您已经在 name 索引中拥有数据。为了避免这些查找操作,每个索引上额外的 209.8MB 是值得的。

总而言之(像往常一样),这取决于 - 这取决于您的个人偏好(我相信 Aaron Bertrand 和 Joe Celko 在自然键与替代键的辩论中仍然存在争执,如果这两位伟大的思想不能达成一致,那么答案必须是个人喜好),还有你的确切情况,在某些情况下你会想要一个复合主键,在某些情况下你会想要一个代理键,在某些情况下你会想要你的主键和你的集群键是同一件事,在其他情况下你不会。

【讨论】:

  • 感谢您提供非常详细的回答。非常有帮助可能需要阅读几次才能掌握所有内容。是的,日期列是上传记录的日期,因此总是在增加。静态我认为你的意思是三列的值不会改变?实际上,对于表中的所有字段,它们的值都不会改变。也没有子表。查询将为某个 UploadDate 或某个日期范围内的某个 FundCode 选择 secID。
  • 是的,静态我的意思是永远不会改变,主键一旦创建就不应该改变。听起来,代理键可能有点过头了,您可能应该只使用您拥有的三列。由于第一列将是 UploadDate,因此您不太可能找到为未在 UploadDate 上过滤的查询创建的唯一索引,因此您可能需要更多的非聚集索引。
【解决方案2】:

由于您的 UploadDate 带有隐式排序,因此拥有一个聚集的多列主键可能是明智之举。任何其他键都可以将其用作查找键,因为聚集键确实隐式覆盖了所有列。因此,您应该先创建它...

不应将带有随机数的附加 int 键聚集在一起(它会非常碎片化!)。

嗯,很多人说,(几乎在所有情况下)PK 不应该携带任何信息。这真的取决于你的需要。

【讨论】:

    【解决方案3】:

    主键本身不会加速不依赖它的查询。其他 DBMS 可能不依赖于主键的存在(例如 Oracle 的默认值是堆表)。

    这只是 MSSQL 的“功能”——它“喜欢”主键。它针对它进行了优化(实际上是针对那个树结构)。其他引擎可能无法从简单的自动增量字段中受益。

    【讨论】:

    • 如果您使用集群 PK,它将成为所有其他索引的查找键(因为它隐式覆盖所有列)。但是您永远不应该将聚集键与未在插入操作中排序的列一起使用!
    • @Shnugo 这使得它在 90% 的情况下无用。加速select 一点会使insert 死。
    • 绝对不是...在我的一个项目(非常旧)中,我必须处理 UNIQUEIDENTIFIER 类型的集群 PK,这在这种情况下是最糟糕的。 INSERT 不会“死亡”。索引的碎片率为 99,9%...顺便说一句:在日常使用中,读取操作比插入操作要多...
    • @Shnugo 我说 90% 没用,而不是 100%。每天浪费半个小时总比一年少一周要好。
    • 不,我完全不同意...我不知道你在处理什么样的数据。大多数情况下,我必须编写经典的 OLTP 应用程序。加快查询速度是我每天的工作。在保存操作上等待更长的时间一点也不麻烦!我生活在精心设计的指数上!一个好的集群PK可以让事情变得更容易!
    猜你喜欢
    • 2018-09-23
    • 1970-01-01
    • 2022-12-01
    • 2018-12-03
    • 2016-07-12
    • 1970-01-01
    • 2012-08-21
    • 1970-01-01
    • 2014-01-27
    相关资源
    最近更新 更多