【问题标题】:Add all primary key constraints in non clustered indexes在非聚集索引中添加所有主键约束
【发布时间】:2021-02-16 22:59:18
【问题描述】:

我创建了一个包含以下列的表格。所有列都是唯一键(列)我的表中没有主键。

Product:

Bat_Key,
product_no,
value,
pgm_name,
status,
industry,
created_by,
created_date

我已经更改了我的表格以添加约束

ALTER TABLE [dbo].[Product] 
    ADD CONSTRAINT [PRODUCT_PK] 
        PRIMARY KEY NONCLUSTERED ([Bat_Key] ASC, [product_no] ASC,
                                  [value] ASC, [pgm_name] ASC, )
                    WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF) ON [PRIMARY]
GO

如果我创建如下索引:

CREATE NONCLUSTERED INDEX [PRODUCT_BKEY_PNO_IDX] 
ON [dbo].[PRODUCT] ([Bat_Key] ASC, [product_no] ASC, [value], [pgm_name])
INCLUDE ([status], [industry]) 
         WITH (STATISTICS_NORECOMPUTE = OFF, DROP_EXISTING = OFF, ONLINE = OFF) ON [PRIMARY]
GO

这种设计是否适合以下选择查询:

select * 
from Product 
where Bat_Key = ? and product_no=? 
order by product_no, pgm_name;

select * 
from Product 
where Bat_Key = ? and product_no=? and pgm_name = ? and value = ?

select * 
from Product 
where Bat_Key = ? and product_no=?

delete from Product 
where Bat_Key = ? and product_no=?

或者我应该根据我的where 子句创建不同的索引?

【问题讨论】:

  • 如果我没记错的话,主键会自动创建一个聚集索引。可能没有必要在相同字段上创建非聚集索引。
  • 主键约束总是由​​唯一索引支持,无论是否聚集。 PRODUCT_BKEY_PNO_IDX 索引将是多余的。 (它不能在您的查询中使用,因为它没有覆盖;created_* 字段仍然需要在任何 SELECT * 中查找。)您通常需要一个聚集索引,即使它只在一个 IDENTITY 字段上你不会以其他方式使用。这样的字段也可以作为在数据库中使用的更实用的代理键,而不是包括文本​​在内的四个字段。
  • 您使用的是哪个 dbms? (这是一个产品特定的问题。)
  • @jarlh - 我正在使用 Azure SQL 数据库
  • 没有绝对的性能。想知道?您测试您的查询并检查执行计划。没有什么是免费的。维护额外的索引是有成本的。只关注几个 SELECT 语句可能会给您带来误导性的方向。更重要的是 - 您应该仔细考虑堆是否适合此表及其用途。

标签: sql-server indexing ddl non-clustered-index


【解决方案1】:

聚集索引与非聚集索引非常不同。实际上,两个索引的类型都包含根据您指定的列排序的数据。然而,

  • 聚集索引还包含表中的其余数据(除了 nvarchar(max) 等少数数据)。您可以认为这是它在数据库中的保存方式
  • 非聚集索引仅包含您已包含在索引中的列

如果您没有聚集索引,则您有一个“堆”。它们内置了一个行标识符,而不是 PK。

在您的情况下,由于您的主键是非聚集的,因此 用相同的字段创建另一个索引是没有意义的。要读取数据,它必须从您的 PK 中获取行标识符,然后从堆中读取数据。

另一方面,如果您的主键是聚集(这是默认设置),则在某些情况下,在字段上使用非聚集索引可能会很有用。但请注意,您添加的每个非聚集索引也会减慢更新、插入和删除的速度(因为索引也必须维护)。

在您的示例中 - 假设您有一个字段,该字段是包含大量信息的行上的 varchar(8000)。甚至要从聚集索引中读取一行,它必须从其他字段读取(例如)100 个字节,并且从该新字段读取最多 8000 个字节。换句话说,它将您需要阅读的数量乘以 80 倍。

我倾向于看到包含两种类型数据的表格

  • 您汇总的数据
  • 您只关心逐行级别的数据

例如,在一个事务表中,您可能有 transaction_id、transaction_date、transaction_amount、transaction_description、transaction_entered_by_user_id。

  • 在大多数情况下,每当您获取总计等信息时,您在查看总计时经常需要交易金额、日期(例如,本周的交易总额是多少?)
  • 另一方面,description 和 user_id 仅在您引用特定行时使用(例如,谁进行了此特定交易?)
  • 在这些情况下,我经常在聚合中使用的字段上放置一个非聚集索引,即使它们与聚集索引重叠。它只是减少了所需的读取量。

Brent Ozar 的一个非常好的视频是 How to think like the SQL Server Engine - 我强烈推荐它,因为它对我理解如何使用索引有很大帮助。


关于您的具体示例 - 在索引中有两件事要查找:

  1. “寻找”到数据集中特定点的能力(基于索引的类型)。
  2. 能够减少阅读量。

在允许搜索方面,您需要以最合适的方式对索引进行排序。在进行过滤时(例如,WHERE 子句、JOIN,一个经验法则是首先查找“精确”匹配项。对于这些,它们的顺序无关紧要,只要它们全部匹配即可到那时。

在你的情况下,你有

where Bat_Key = ? and product_no=? 
where Bat_Key = ? and product_no=? and pgm_name = ? and value = ?

这表明您的前两个字段应该是 Bat_Key 和 product_no(按任意顺序)。然后你也可以拥有 pgm_name 和 value(也可以按任意顺序)。

你也有

where Bat_Key = ? and product_no=? 
order by product_no, pgm_name;

这向我建议第三个字段应该是 pgm_name(作为 Bat_Key 的索引,product_no 和 pgm_name 将提供您需要的内容)。

但是 - 这是一个很大的问题 - 你有很多 * 在那里,例如,

select * 
from Product 
where Bat_Key = ? and product_no=?

因为您选择 *,所以任何不是聚集索引的索引也需要返回到实际行以获取包含在 * 中的其余内容。

由于这些需要表中的所有字段(不仅仅是索引中的字段),因此需要返回堆(在您的情况下)。如果您在上面的字段上有一个聚集索引以及一个非聚集索引,那么无论如何它都必须从聚集索引中读取,因为那里有您查询所需的信息。

再一次 - 上面的视频 - 比我更好地解释了这一点。

因此,在您的情况下,我建议使用以下主键

ADD CONSTRAINT [PRODUCT_PK] 
        PRIMARY KEY CLUSTERED ([Bat_Key] ASC, [product_no] ASC,
                                  [pgm_name] ASC, [value] ASC)

区别

  • 它是集群的而不是非集群的
  • 重新排列第 3 和第 4 字段的顺序以帮助按 pgm_name 进行排序
  • 实际上不需要第二个非聚集索引,因为没有太多其他要读取的内容。

【讨论】:

    猜你喜欢
    • 2012-12-30
    • 2014-08-08
    • 1970-01-01
    • 2011-05-15
    • 2021-12-14
    • 1970-01-01
    • 2016-08-30
    • 2013-12-02
    • 2018-02-17
    相关资源
    最近更新 更多