在索引中包含列的硬性和快速性规则


38

是否有任何硬性规定来决定应将哪些列以及应以什么顺序放入非聚集索引中。我只是在阅读这篇文章https://stackoverflow.com/questions/1307990/why-use-the-include-clause-when-creating-an-index ,我发现对于以下查询:

SELECT EmployeeID, DepartmentID, LastName
FROM Employee
WHERE DepartmentID = 5

张贴者建议制作这样的索引:

CREATE NONCLUSTERED INDEX NC_EmpDep 
  ON Employee(EmployeeID, DepartmentID)
  INCLUDE (Lastname)

这是我的问题,为什么我们不能像这样做索引

CREATE NONCLUSTERED INDEX NC_EmpDep 
      ON Employee( EmployeeID, DepartmentID, LastName)

要么

    CREATE NONCLUSTERED INDEX NC_EmpDep 
          ON Employee( EmployeeID, LastName)
INCLUDE (DepartmentID)

什么原因导致发布者决定保留“姓氏”列。为什么不另列?以及如何决定应以什么顺序将列保留在那里?


3
在找到记录后,INCLUDE通常应该具有您需要的字段,从而为您节省了往返时间以获取更多数据。INCLUDE中字段的顺序并不重要。
Jimbo

Ryk,我个人认为这篇文章很有帮助。
杰森·杨

我觉得这个问题也有帮助。让我们专注于好的问题和好的答案,而不是跟踪个人....
团藻

Answers:


47

marc_s的索引建议是错误的。我添加了一条评论。(这也是我接受的答案!)

该查询的索引为

CREATE NONCLUSTERED INDEX NC_EmpDep 
  ON Employee(DepartmentID)
  INCLUDE (Lastname, EmployeeID)

索引通常是

CREATE INDEX <name> ON <table> (KeyColList) INCLUDE (NonKeyColList)

哪里:

  • KeyColList =关键列=用于行限制和处理
    WHERE,JOIN,ORDER BY,GROUP BY等
  • NonKeyColList =非关键列=在选择/限制之后用于SELECT和聚合(例如SUM(col))

+1-我同意(请参阅我的回答),OP中的样本索引对于查询毫无价值!
JNK

大!仅一件事,什么将决定KeyColList和NonKeyColList的顺序。你能用我的例子解释一下吗?现在假设我的查询是SELECT EmployeeID,DepartmentID,LastName FROM EmployeeWHERE DepartmentID = 5,StateID = 4现在索引如何了?

@Rocky- NonKeyColList顺序无关紧要。 KeyColListorder应该按照您希望它们在查询中使用的频率顺序。请在下面的答案中查看我的笔记,但这就像Last Name, First Name, Middile Initial在电话簿中一样。您需要第一个字段才能找到第二个字段。
JNK

@gbn我们是否真的需要在包含列表中使用EmployeeID?就像如果我们在EmployeeID列上有一个聚集索引,并且如果我们在DeptId列上创建非聚集索引一样,那么非聚集索引已经引用了包含在非聚集索引结构中的聚集关键字,包括INCLUDE列表中的聚集关键字并没有。增加任何好处。
Viswanathan Iyer

1
@ViswanathanIyer不会将它两次添加到实际的磁盘存储中:SQL Server会检测到它。因此它不是必需的,但是它使事情变得更清晰。但是,我们不知道问题中是否存在任何聚簇索引,因此假设它们都不安全。
gbn

19

JNK和gbn给出了很好的答案,但也有必要考虑一下大局-而不是只关注单个查询。尽管此特定查询可能会受益于索引(#1):

Employee(DepartmentID) INCLUDE (Lastname, EmployeeID)

如果查询稍有变化,则该索引根本没有帮助,例如:

SELECT EmployeeID, DepartmentID, LastName
FROM Employee
WHERE DepartmentID = 5 AND LastName = 'Smith'

这将需要索引(#2):

Employee(DepartmentID, LastName) INCLUDE (EmployeeID)

想象一下,您在部门5中有1,000名员工。使用索引#1来查找所有Smiths,您需要遍历部门5中的所有1,000行,因为所包含的列不是键的一部分。使用索引2,您可以直接查找部门5,LastName Smith。

因此,索引#2在为更广泛的查询提供服务时更有用-但代价是索引键更加膨胀,这会使索引的非叶子页变大。每个系统都会有所不同,因此这里没有经验法则。


附带说明一下,值得指出的是,如果EmployeeID是该表的集群键-假设是聚集索引-那么您不需要包含EmployeeID-它存在于所有非聚集索引中,这意味着索引#2可以是

Employee(DepartmentID, LastName)

2
+1获取更多有用的信息。对于您的最后一点,我对此进行了测试,并且如果EmployeeID是聚集索引,则实际上会忽略INCLUDE中EmployeeID的显式使用(基于索引的大小)。尽管我认为这更加明显,并且没有空间不足。
gbn

1
我完全同意-明确表示总是更好,特别是如果不花钱的话!

1
以防万一...我的意思是我已经在INCLUDE中测试了集群键(不是显式的EmployeeID),并且它不增加任何空间。在关键列中。
gbn

@gbn是的,集群键只需要驻留在索引的叶级,即INCLUDE列所在的位置。将其移入索引键将意味着它也将存在于非叶子页中。这将导致一点点膨胀,但数量却不菲(在中间级页面上,假设为Integer,您将在每个叶级页面上再添加4个字节)。

这是一个很好的答案,其中包括本文中所述的某些效果:sqlperformance.com/2014/07/sql-indexes / ...如果您的查询发生更改,则索引要求也将更改。吉姆的答案可能会更好,但@gbn答案可能会更好。
约翰aka hot2use

7

我不确定你是怎么得到的。对于我来说,对于该查询,我将使用:

CREATE NONCLUSTERED INDEX NC_EmpDep 
  ON Employee(DepartmentID)
  INCLUDE (EmployeeID, Lastname)

对于SQL中的几乎所有内容,没有“硬性规定”。

但是,对于您的示例,索引将使用的唯一字段是DepartmentID因为它位于WHERE子句中。

其他字段仅需要从那里轻松访问。您基于选择,DepartmentID然后INCLUDE在索引的叶节点上具有这些字段。

您不想使用其他示例,因为它们不适用于此索引。

可以将索引想像成电话簿。大多数电话簿按姓,名,中间名首字母排序。如果您知道某人的名字,但不知道他们的姓氏,则电话簿对您不利,因为您无法根据电话簿索引的顺序搜索名字。

这些INCLUDE字段类似于电话号码,地址等,用于书中每个条目的其他信息。

编辑:

为了进一步说明为什么不使用:

CREATE NONCLUSTERED INDEX NC_EmpDep 
          ON Employee( EmployeeID, LastName)
INCLUDE (DepartmentID)

如果您有任何该指数是唯一有用的EmployeeIDBOTH EmployeeIDLastName您的WHERE条款。这是相当多的OPPOSITE你需要什么此查询。


@ajbeaven是正确的,这就是为什么我在编辑中添加注释说您需要EITHER employeeID或同时使用两列的原因。
JNK

durr对不起,误读了:(
ajbeaven

0

我认为您可能仍然可以使用(employee_id,department_id)索引,但您必须在where短语中包含“虚拟”行,例如:“ employee_id = employee_id)

  • 在(employee_id,departemnent_id)上具有索引,
  • 只需搜索/限制一个department_id
  • 知道由于错误的顺序(或者现在情况已经改变,并且不再需要以下“技巧”。我是“老手”了?),它将不再使用索引。
  • 使用“旧” tricK?

    从员工emp
    中选择*,其中emp.employee_id = emp.employee_id
    和emp.department_id = 5

(因此,我不是在关注姓氏的包含部分,而是在是/或不使用键的地方。)

亲切的问候,

米格尔


2
不,那是没有用的,效率不高。
ypercubeᵀᴹ

具体来说,它仍然必须进行索引扫描以搜索每个员工id来找到department_id 5的所有实例。如果有1000个员工和5个部门,SQL必须遍历所有1000名员工以查找特定部门的所有行。
Mark Sowul

现在考虑相反的情况(索引在department_id,employee_id上​​)。显然,现在找到特定部门很容易,但是还要注意,要找到特定雇员,SQL只需要扫描5个部门即可找到特定雇员的所有行。
Mark Sowul
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.