【问题标题】:Query Index for Dictionary Based Queries基于字典的查询的查询索引
【发布时间】:2013-04-11 21:35:45
【问题描述】:

查询和索引以下内容的最有效方法是什么:

SELECT * Persons.LastName A-D
SELECT * Persons.LastName E-L
SELECT * Persons.LastName M-R
SELECT * Persons.LastName S-Z

我正在使用以下效率极低且难以索引的方法:

WHERE LastName LIKE '[a-d]%'

有什么更好的方法来解决这个问题吗?我认为这对于过滤索引来说可能是一个很好的场景,但是 where 子句需要更加可搜索。

谢谢

【问题讨论】:

  • 没有前导 % 的 LIKE 是 sargable。我会看看你当前的索引。

标签: sql-server


【解决方案1】:

你的谓词是 sargable。

如果您在索引字段上运行此查询:

SELECT  *
FROM    persons
WHERE   last_name >= 'a'
        AND last_name < 'e'

它产生以下计划:

  |--Nested Loops(Inner Join, OUTER REFERENCES:([MYDB].[dbo].[PERSONS].[ID]) OPTIMIZED)
       |--Index Seek(OBJECT:([MYDB].[dbo].[PERSONS].[IX_PERSONS_LAST_NAME]), SEEK:([MYDB].[dbo].[PERSONS].[LAST_NAME] >= 'a' AND [MYDB].[dbo].[PERSONS].[LAST_NAME] < 'E'),  WHERE:([MYDB].[dbo].[PERSONS].[LAST_NAME] like '[a-d]%') ORDERED FORWARD)
       |--Clustered Index Seek(OBJECT:([MYDB].[dbo].[PERSONS].[IX_PERSONS_LAST_NAME]), SEEK:([MYDB].[dbo].[PERSONS].[ID]=[MYDB].[dbo].[PERSONS].[ID]) LOOKUP ORDERED FORWARD)

相当于运行这个查询:

SELECT  *
FROM    persons
WHERE   last_name >= 'a'
        AND last_name < 'e'

【讨论】:

  • 在运行 LIKE 或 运算符时,我得到了相同的解释计划。似乎 SQL 在幕后做了几乎相同的事情,因为它应该这样做。
  • 因此,为了澄清您的答案,您可能会删除没有任何帮助的替代谓词。还是您认为 执行得更快?
  • @Sam:实际上,原始谓词的行为与应有的完全一致,因此可能值得保留
【解决方案2】:

我会查看您的解释计划并打开 STATISTICS IO 和 STATISTICS 时间,看看是否有什么突然出现在您身上。

【讨论】:

    【解决方案3】:

    正如 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 包括SELECTFROMWHERE 子句所需的所有列: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 #1Example #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 #3Example #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 *

    【讨论】:

    • 我认为您可以在评论中总结整个答案 - “我希望您没有在生产代码中使用 SELECT *。它会导致性能问题。”
    • @Sam:说起来容易,难以置信……没有 (some) 参数。互联网上到处都是好的和(大多数)坏/错误的 cmets。
    • 看起来你做了很多工作,但不一定是问题;)
    • @Sam:我做了很多工作,因为我喜欢这个主题。这对我很重要。
    • @Sam: on topic > 我想说一些 cmets 有一个共同的问题:森林与树木。
    猜你喜欢
    • 1970-01-01
    • 2018-01-02
    • 2011-04-15
    • 2021-11-22
    • 2015-07-01
    • 2011-10-30
    • 1970-01-01
    • 1970-01-01
    • 2021-10-13
    相关资源
    最近更新 更多