sql 索引笔记
以下资料来自MSDN。
设计索引时,应考虑以下数据库准则:
- 一个表如果建有大量索引会影响 INSERT、UPDATE 和 DELETE 语句的性能,因为在表中的数据更改时,所有索引都须进行适当的调整。
-
避免对经常更新的表进行过多的索引,并且索引应保持较窄,就是说,列要尽可能少。
-
使用多个索引可以提高更新少而数据量大的查询的性能。大量索引可以提高不修改数据的查询(例如 SELECT 语句)的性能,因为查询优化器有更多的索引可供选择,从而可以确定最快的访问方法。
-
避免对经常更新的表进行过多的索引,并且索引应保持较窄,就是说,列要尽可能少。
-
对小表进行索引可能不会产生优化效果,因为查询优化器在遍历用于搜索数据的索引时,花费的时间可能比执行简单的表扫描还长。因此,小表的索引可能从来不用,但仍必须在表中的数据更改时进行维护。
-
视图包含聚合、表联接或聚合和联接的组合时,视图的索引可以显著地提升性能。若要使查询优化器使用视图,并不一定非要在查询中显式引用该视图。有关详细信息,请参阅设计索引视图。
-
使用数据库引擎优化顾问来分析数据库并生成索引建议。有关详细信息,请参阅数据库引擎优化顾问概述。
设计索引时,应考虑以下查询准则:
-
为经常用于查询中的谓词和联接条件的所有列创建非聚集索引。
重要提示:
避免添加不必要的列。添加太多索引列可能对磁盘空间和索引维护性能产生负面影响。 -
涵盖索引可以提高查询性能,因为符合查询要求的全部数据都存在于索引本身中。也就是说,只需要索引页,而不需要表的数据页或聚集索引来检索所需数据,因此,减少了总体磁盘 I/O。例如,对某一表(其中对列 a、列 b 和列 c 创建了组合索引)的列 a 和列 b 的查询,仅仅从该索引本身就可以检索指定数据。
-
将插入或修改尽可能多的行的查询写入单个语句内,而不要使用多个查询更新相同的行。仅使用一个语句,就可以利用优化的索引维护。
-
评估查询类型以及如何在查询中使用列。例如,在完全匹配查询类型中使用的列就适合用于非聚集索引或聚集索引。有关详细信息,请参阅查询类型和索引。
设计索引时,应考虑以下列准则:
-
对于聚集索引,请保持较短的索引键长度。另外,对唯一列或非空列创建聚集索引可以使聚集索引获益。有关详细信息,请参阅聚集索引设计指南。
-
不能将 ntext、text、image、varchar(max)、nvarchar(max) 和 varbinary(max) 数据类型的列指定为索引键列。不过,varchar(max)、nvarchar(max)、varbinary(max) 和 xml 数据类型的列可以作为非键索引列参与非聚集索引。有关详细信息,请参阅具有包含性列的索引。
-
xml 数据类型的列只能在 XML 索引中用作键列。有关详细信息,请参阅 xml 数据类型列的索引。
-
检查列的唯一性。在同一个列组合的唯一索引而不是非唯一索引提供了有关使索引更有用的查询优化器的附加信息。有关详细信息,请参阅唯一索引设计指南。
-
在列中检查数据分布。通常情况下,为包含很少唯一值的列创建索引或在这样的列上执行联接将导致长时间运行的查询。这是数据和查询的基本问题,通常不识别这
种情况就无法解决这类问题。例如,如果物理电话簿按姓的字母顺序排序,而城市里所有人的姓都是 Smith 或
Jones,则无法快速找到某个人。有关数据分布的详细信息,请参阅索引统计信息。
-
如果索引包含多个列,则应考虑列的顺序。用于等于 (=)、大于 (>)、小于 (<) 或 BETWEEN 搜索条件的 WHERE 子句或者参与联接的列应该放在最前面。其他列应该基于其非重复级别进行排序,就是说,从最不重复的列到最重复的列。
例如,如果将索引定义为LastName
、FirstName
,则该索引在搜索条件为WHERE LastName = 'Smith'
或WHERE LastName = Smith AND FirstName LIKE 'J%'
时将很有用。不过,查询优化器不会将此索引用于基于FirstName (WHERE FirstName = 'Jane')
而搜索的查询。
-
考虑对计算列进行索引。有关详细信息,请参阅为计算列创建索引。
在确定某一索引适合某一查询之后,可以选择最适合具体情况的索引类型。索引包含以下特性:
- 聚集还是非聚集
-
唯一还是非唯一
-
单列还是多列
-
索引中的列是升序排序还是降序排序
您也可以通过设置选项(例如 FILLFACTOR)自定义索引的初始存储特征以优化其性能或维护。有关详细信息,请参阅设置索引选项。而且,通过使用文件组或分区方案可以确定索引存储位置来优化性能。有关详细信息,请参阅在文件组上放置索引。
在 SQL Server 2005 中,可以通过将非键列添加到非聚集索引的叶级别来扩展非聚集索引的功能。通过包含非键列,可以创建覆盖更多查询的非聚集索引。这是因为非键列具有下列优点:
- 它们可以是不允许作为索引键列的数据类型。
-
在计算索引键列数或索引键大小时,数据库引擎不考虑它们。
当查询中的所有列都作为键列或非键列包含在索引中时,带有包含性非键列的索引可以显著提高查询性能。这样可以实现性能提升,因为查询优化器可以在索引中找到所有列值;不访问表或聚集索引数据,从而减少磁盘 I/O 操作。
![]() |
---|
当索引包含查询引用的所有列时,它通常称为“覆盖查询”。 |
键列存储在索引的所有级别中,而非键列仅存储在叶级别中。有关索引级别的详细信息,请参阅表组织和索引组织。
可以将非键列包含在非聚集索引中,以避免超过当前索引大小的限制(最大键列数为 16,最大索引键大小为 900 字节)。数据库引擎计算索引键列数或索引键大小时,不考虑非键列。
例如,假设要为 AdventureWorks
示例数据库的 Document
表中的以下列建立索引:
Title nvarchar(50)
Revision nchar(5)
FileName nvarchar(400)
因为 nchar 和 nvarchar 数据类型的每个字符需要 2 个字节,所以包含这三列的索引将超出 900 字节的大小限制 10 个字节 (455 * 2)。使用 CREATE INDEX
语句的 INCLUDE
子句,可以将索引键定义为 (Title, Revision
),将 FileName
定义为非键列。这样,索引键大小将为 110 个字节 (55 * 2),并且索引仍将包含所需的所有列。下面的语句就创建了这样的索引。
USE AdventureWorks; GO CREATE INDEX IX_Document_Title ON Production.Document (Title, Revision) INCLUDE (FileName);
设计带有包含性列的非聚集索引时,请考虑下列准则:
- 在 CREATE INDEX 语句的 INCLUDE 子句中定义非键列。
-
只能对表或索引视图的非聚集索引定义非键列。
-
除 text、ntext 和 image 之外,允许所有数据类型。
-
精确或不精确的确定性计算列都可以是包含性列。有关详细信息,请参阅为计算列创建索引。
-
与键列一样,只要允许将计算列数据类型作为非键索引列,从 image、ntext 和 text 数据类型派生的计算列就可以作为非键(包含性)列。
-
不能同时在 INCLUDE 列表和键列列表中指定列名。
-
INCLUDE 列表中的列名不能重复。
-
必须至少定义一个键列。最大非键列数为 1023 列。也就是最大的表列数减 1。
-
索引键列(不包括非键)必须遵守现有索引大小的限制(最大键列数为 16,总索引键大小为 900 字节)。
-
所有非键列的总大小只受 INCLUDE 子句中所指定列的大小限制;例如,varchar(max) 列限制为 2 GB。
修改已定义为包含性列的表列时,要受下列限制:
-
除非先删除索引,否则无法从表中删除非键列。
-
除进行下列更改外,不能对非键列进行其他更改:
-
将列的为空性从 NOT NULL 改为 NULL。
-
增加 varchar、nvarchar 或 varbinary 列的长度。
注意:
这些列修改限制也适用于索引键列。
-
将列的为空性从 NOT NULL 改为 NULL。
重新设计索引键大小较大的非聚集索引,以便只有用于搜索和查找的列为键列。将覆盖查询的所有其他列设置为包含性非键列。这样,将具有覆盖查询所需的所有列,但索引键本身较小,而且效率高。
例如,假设要设计覆盖下列查询的索引。
USE AdventureWorks; GO SELECT AddressLine1, AddressLine2, City, StateProvinceID, PostalCode FROM Person.Address WHERE PostalCode BETWEEN N'98000' and N'99999';
若要覆盖查询,必须在索引中定义每列。尽管可以将所有列定义为键列,但键大小为 334 字节。因为实际上用作搜索条件的唯一列是 PostalCode
列(长度为 30 字节),所以更好的索引设计应该将 PostalCode
定义为键列并包含作为非键列的所有其他列。
下面的语句创建了一个覆盖查询的带有包含性列的索引。
USE AdventureWorks; GO CREATE INDEX IX_Address_PostalCode ON Person.Address (PostalCode) INCLUDE (AddressLine1, AddressLine2, City, StateProvinceID);
避免添加不必要的列。添加过多的索引列(键列或非键列)会对性能产生下列影响:
- 一页上能容纳的索引行将更少。这样会使 I/O 增加并降低缓存效率。
-
需要更多的磁盘空间来存储索引。特别是,将 varchar(max)、nvarchar(max)、varbinary(max) 或 xml 数据类型添加为非键索引列会显著增加磁盘空间要求。这是因为列值被复制到了索引叶级别。因此,它们既驻留在索引中,也驻留在基表中。
-
索引维护可能会增加对基础表或索引视图执行修改、插入、更新或删除操作所需的时间。
您应该确定修改数据时在查询性能上的提升是否超过了对性能的影响,以及是否需要额外的磁盘空间要求。有关评估查询性能的详细信息,请参阅查询优化。
当引用表的查询包含用以指定索引中键列的不同方向的 ORDER BY 子句时,指定键值存储在该索引中的顺序很有用。在这些情况下,索引就无需在查询计划中使用 SORT 运算符。因此,使得查询更有效。例如,Adventure Works Cycles 采购部门的买方不得不评估他们从供应商处购买的产品的质量。买方倾向于查验那些由具有高拒绝率的供应商发送的产品。检索数据以满足此条件需要将 Purchasing.PurchaseOrderDetail
表中的 RejectedQty
列按降序(由大到小)排序,并且将 ProductID
列按升序(由小到大)排序,如下列查询所示。
USE AdventureWorks; GO SELECT RejectedQty, ((RejectedQty/OrderQty)*100) AS RejectionRate, ProductID, DueDate FROM Purchasing.PurchaseOrderDetail ORDER BY RejectedQty DESC, ProductID ASC;
此查询的下列执行计划显示了查询优化器使用 SORT 运算符按 ORDER BY 子句指定的顺序返回结果集。
如果使用与查询的 ORDER BY 子句中的键列匹配的键列创建索引,则无需在查询计划中使用 SORT 运算符,从而使查询计划更有效。
CREATE NONCLUSTERED INDEX IX_PurchaseOrderDetail_RejectedQty ON Purchasing.PurchaseOrderDetail (RejectedQty DESC, ProductID ASC, DueDate, OrderQty);
SQL Server 2005 数据库引擎 可以在两个方向上同样有效地移动。对于一个在 ORDER BY 子句中列的排序方向倒排的查询,
仍然可以使用定义为(RejectedQty DESC, ProductID ASC)
的索引。
例如,包含 ORDER BY 子句ORDER BY RejectedQty ASC, ProductID DESC
的查询可以使用该索引。
列谓词为下列之一的查询 | 查询说明和示例 | 考虑的索引 |
---|---|---|
与特定值完全匹配 |
搜索与特定值完全匹配的项,其中,查询使用 WHERE 子句指定列项。例如: SELECT EmployeeID, Title FROM HumanResources.Employee WHERE EmployeeID = 228; |
|
与 IN (x,y,z) 列表中的某个值完全匹配 |
搜索与指定值列表中的某个值完全匹配的项。例如: SELECT EmployeeID, Title FROM HumanResources.Employee WHERE EmployeeID IN (288, 30, 15); |
|
值范围 |
搜索某个值范围,其中,查询指定的任何项的值在两个值之间。例如: SELECT ProductModelID, Name FROM Production.ProductModel WHERE ProductModelID BETWEEN 1 and 5; 或 WHERE ProductModelID >= 1 AND ProductModelID <= 5 |
|
表之间的联接 |
基于联接谓词,在一个表中搜索与另一个表中的某个行匹配的行。例如: SELECT a.ProductAssemblyID, b.Name, a.PerAssemblyQty FROM Production.BillOfMaterials AS a JOIN Production.Product AS b ON a.ProductAssemblyID = b.ProductID WHERE b.ProductID = 900; |
|
LIKE 比较 |
搜索以特定字符串(如 abc%)开头的匹配行。例如: SELECT CountryRegionCode, Name FROM Person.CountryRegion WHERE Name LIKE N'D%' |
|
排序或聚合 |
需要隐式或显式排序顺序或聚合 (GROUP BY)。例如: SELECT a.WorkOrderID, b.ProductID, a.OrderQty, a.DueDate FROM Production.WorkOrder AS a JOIN Production.WorkOrderRouting AS b ON a.WorkOrderID = b.WorkOrderID ORDER BY a.WorkOrderID; |
排序列或聚合列的非聚集索引或聚集索引。 对于排序列,考虑为列指定 ASC 或 DESC 顺序。 |
PRIMARY KEY 或 UNIQUE 约束 |
搜索与插入和更新操作中的新索引键值重复的值,以强制 PRIMARY KEY 和 UNIQUE 约束。例如: INSERT INTO Production.UnitMeasure (UnitMeasureCode, Name, ModifiedDate) VALUES ('OZ1', 'OuncesTest', GetDate()); |
约束中定义的列的聚集索引或非聚集索引。 |
PRIMARY KEY/FOREIGN KEY 关系中的 UPDATE 或 DELETE 操作 |
在列参与 PRIMARY KEY/FOREIGN KEY 关系(无论带不带 CASCADE 选项)的更新或删除操作中搜索行。 |
外键列的非聚集索引或聚集索引。 |
列在选择列表中,但不在谓词中。 |
包含选择列表中未用于搜索和查找的一列或多列。例如: SELECT Title, Revision, FileName FROM Production.Document WHERE Title LIKE N'%Maintenance%' AND Revision >= 0'; |
在 INCLUDE 子句中指定了 |
“麻烦”是自己“处理”不当的结果
“困难”是自己“学习”不够的反射
“挫折”是自己“努力”不足的代价