【问题标题】:TSQL wildcard performance: results of variable or specific length queryTSQL通配符性能:可变或特定长度查询的结果
【发布时间】:2018-09-01 20:48:43
【问题描述】:

在一个表中,我有一个关于列 ID (VARCHAR) 的索引。

鉴于存在索引,我希望

Select Top 1 * from table where ID like 'abc%' order by ID desc

要非常快。

然后我做了一个

set statistics io on

比较发现

Select Top 1 * from table where ID like 'abc___' order by ID desc

也非常快(读取次数相同,在我的情况下为 5)。实际执行计划也显示完全相同,索引查找和键查找在这两种情况下。

据我了解,它应该无法使用索引,因此读取次数要多得多。

随着索引的排序,使用“abc%”它应该能够跳转到索引的末尾,那里有以 abc 开头的匹配项。但是当我用 'abc___' 询问特定长度时,它应该不能直接跳转到索引中的某个位置,而应该扫描所有以 'abc' 开头的条目的长度。

表格中有数 1000 个采用该格式的条目。

为什么查询“abc___”和查询“abc%”一样快?

【问题讨论】:

    标签: performance tsql indexing wildcard


    【解决方案1】:

    为了复制这一点,我设置了您的环境的简单复制:

    IF OBJECT_ID(N'tempdb..#T', 'U') IS NOT NULL DROP TABLE #T;
    CREATE TABLE #T (ID VARCHAR(8) NOT NULL PRIMARY KEY);
    INSERT #T (ID)
    SELECT TOP 100000 123000 + CONVERT(VARCHAR(6), ROW_NUMBER() OVER(ORDER BY a.object_id))
    FROM sys.all_objects a, sys.all_objects b
    UNION ALL
    SELECT TOP 100000 12300000 + CONVERT(VARCHAR(6), ROW_NUMBER() OVER(ORDER BY a.object_id))
    FROM sys.all_objects a, sys.all_objects b;
    
    SELECT COUNT(*)
    FROM #T
    WHERE ID LIKE '123%';
    
    SELECT COUNT(*)
    FROM #T
    WHERE ID LIKE '123___';
    

    查看执行计划时,正如您所说,两者都使用相同的索引搜索:

    如果您进一步检查计划,您会发现两者都以相同的范围搜索开始:

    <SeekPredicateNew>
        <SeekKeys>
            <StartRange ScanType="GE">
            <RangeColumns>
                <ColumnReference Database="[tempdb]" Schema="[dbo]" Table="[#T]" Column="ID" />
            </RangeColumns>
            <RangeExpressions>
                <ScalarOperator ScalarString="'123'">
                <Const ConstValue="'123'" />
                </ScalarOperator>
            </RangeExpressions>
            </StartRange>
            <EndRange ScanType="LT">
            <RangeColumns>
                <ColumnReference Database="[tempdb]" Schema="[dbo]" Table="[#T]" Column="ID" />
            </RangeColumns>
            <RangeExpressions>
                <ScalarOperator ScalarString="'124'">
                <Const ConstValue="'124'" />
                </ScalarOperator>
            </RangeExpressions>
            </EndRange>
        </SeekKeys>
    </SeekPredicateNew>
    

    两种搜索幕后的唯一区别是第二个,其中一个进一步过滤 LIKE '123%' 和另一个 LIKE '123___'

    我认为您的误解可能是索引搜索必须一次正确识别记录。情况并非如此,在第二次查询的情况下,索引查找会找到正确的范围,然后进一步的过滤器会从这个范围中识别出相关的行。

    【讨论】:

    • 谢谢。我知道“123”范围可以通过搜索获得。但我原以为长度(不是按字母顺序排列的??)它必须扫描而不是搜索。
    • 它确实必须扫描而不是寻找长度,但它可以在初始寻找之后将扫描作为辅助操作进行,因此它只扫描已经通过索引寻找找到的范围
    • 是的.. 我再次与同事讨论过这个问题.. 很可能,第二次扫描很短。毕竟,如果它向后扫描(从排序方向开始),它很快就会找到匹配的长度,至少在我的情况下。
    【解决方案2】:

    为什么你认为'abc%'应该只是能够跳转到索引的末尾。

    索引确实是有序的,但引擎仍然需要检查各个值以找到第一个不匹配“abc%”的值。它不知道那个值是什么,直到它找到它。下一个值可能是“abd”、“abf”、“ax”。

    【讨论】:

    • 遍历索引树,在第一个值 != abc 上分支应该很容易,因此寻找不以 abc 开头的第一个值。虽然树的遍历应该稍微复杂一点:从找到的叶子到第一个祖先,然后再从那里下去。但结构包含所有信息。
    • @AndreasReiff 不会和你争论。寻找已知值 = abc 与寻找未知值 '!=' 不同。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-09-21
    • 2017-09-23
    相关资源
    最近更新 更多