正如 Sam 所说,LIKE '[a-d]%' 是 SARGable(几乎)。几乎是因为没有优化的Predicate(更多信息见下文)。
示例 #1:如果您在 AdventureWorks2008R2 数据库中运行此查询
SET STATISTICS IO ON;
SET NOCOUNT ON;
PRINT 'Example #1:';
SELECT p.BusinessEntityID, p.LastName
FROM Person.Person p
WHERE p.LastName LIKE '[a-a]%'
然后,您将获得基于Index Seek 运算符的执行计划(优化谓词:绿色矩形,非优化谓词:红色矩形):
SET STATISTICS IO 的输出是
Example #1:
Table 'Person'. Scan count 1, logical reads 7
这意味着服务器必须从缓冲池中读取 7 个页面。此外,在这种情况下,索引IX_Person_LastName_FirstName_MiddleName 包括SELECT、FROM 和WHERE 子句所需的所有列:LastName 和 BusinessEntityID。如果表有聚集索引,那么所有非聚集索引都将包含聚集索引键中的列(BusinessEntityID 是 PK_Person_BusinessEntityID 聚集索引的键)。
但是:
1) 您的查询必须显示所有列,因为 SELECT *(这是一种错误 做法):BusinessEntityID、LastName、FirstName、MiddleName、PersonType、...、ModifiedDate。
2) 索引(前面示例中的IX_Person_LastName_FirstName_MiddleName)不包括所有必需的列。这就是为什么对于这个查询,这个索引是一个非覆盖索引的原因。
现在,如果您执行下一个查询,那么您将获得差异。 [实际] 执行计划(SSMS,Ctrl + M):
SET STATISTICS IO ON;
SET NOCOUNT ON;
PRINT 'Example #2:';
SELECT p.*
FROM Person.Person p
WHERE p.LastName LIKE '[a-a]%';
PRINT @@ROWCOUNT;
PRINT 'Example #3:';
SELECT p.*
FROM Person.Person p
WHERE p.LastName LIKE '[a-z]%';
PRINT @@ROWCOUNT;
PRINT 'Example #4:';
SELECT p.*
FROM Person.Person p WITH(FORCESEEK)
WHERE p.LastName LIKE '[a-z]%';
PRINT @@ROWCOUNT;
结果:
Example #2:
Table 'Person'. Scan count 1, logical reads 2805, lob logical reads 0
911
Example #3:
Table 'Person'. Scan count 1, logical reads 3817, lob logical reads 0
19972
Example #4:
Table 'Person'. Scan count 1, logical reads 61278, lob logical reads 0
19972
执行计划:
另外:查询将为您提供在“Person.Person”上创建的每个索引的页数:
SELECT i.name, i.type_desc,f.alloc_unit_type_desc, f.page_count, f.index_level FROM sys.dm_db_index_physical_stats(
DB_ID(), OBJECT_ID('Person.Person'),
DEFAULT, DEFAULT, 'DETAILED' ) f
INNER JOIN sys.indexes i ON f.object_id = i.object_id AND f.index_id = i.index_id
ORDER BY i.type
name type_desc alloc_unit_type_desc page_count index_level
--------------------------------------- ------------ -------------------- ---------- -----------
PK_Person_BusinessEntityID CLUSTERED IN_ROW_DATA 3808 0
PK_Person_BusinessEntityID CLUSTERED IN_ROW_DATA 7 1
PK_Person_BusinessEntityID CLUSTERED IN_ROW_DATA 1 2
PK_Person_BusinessEntityID CLUSTERED ROW_OVERFLOW_DATA 1 0
PK_Person_BusinessEntityID CLUSTERED LOB_DATA 1 0
IX_Person_LastName_FirstName_MiddleName NONCLUSTERED IN_ROW_DATA 103 0
IX_Person_LastName_FirstName_MiddleName NONCLUSTERED IN_ROW_DATA 1 1
...
现在,如果您比较 Example #1 和 Example #2(均返回 911 行)
`SELECT p.BusinessEntityID, p.LastName ... p.LastName LIKE '[a-a]%'`
vs.
`SELECT * ... p.LastName LIKE '[a-a]%'`
然后你会看到两个差异:
a) 7 次逻辑读取与 2805 次逻辑读取和
b) Index Seek (#1) 与 Index Seek + Key Lookup (#2)。
您可以看到SELECT * (#2) 查询的性能最差(7 页与 2805 页)。
现在,如果您比较 Example #3 和 Example #4(均返回 19972 行)
`SELECT * ... LIKE '[a-z]%`
vs.
`SELECT * ... WITH(FORCESEEK) LIKE '[a-z]%`
然后你会看到两个差异:
a) 3817 次逻辑读取 (#3) 与 61278 次逻辑读取 (#4) 和
b) Clustered Index Scan(PK_Person_BusinessEntityID 有 3808 + 7 + 1 + 1 + 1 = 3818 个页面)与 Index Seek + Key Lookup。
您可以看到 Index Seek + Key Lookup (#4) 查询的性能最差(3817 页与 61278 页)。 在这种情况下,您可以看到并且Index Seek on IX_Person_LastName_FirstName_MiddleName 加上 Key Lookup on PK_Person_BusinessEntityID(聚集索引)将提供比“聚集索引扫描”更低的性能。
所有这些糟糕的执行计划都是可能的,因为SELECT *。