问题 SQL通配符搜索 - 效率?


最近以最有效的方式使用搜索MS SQL数据库进行了辩论 LIKE 和通配符。我们正在比较使用 %abc%%abc,和 abc%。一个人说你应该在学期末总是有通配符(abc%)。因此,根据他们的说法,如果我们想找到以“abc”结尾的东西,那么使用`reverse(column)LIKE reverse('%abc')是最有效的。

我使用SQL Server 2008(R2)设置了一个测试来比较以下每个语句:

select * from CLMASTER where ADDRESS like '%STREET'
select * from CLMASTER where ADDRESS like '%STREET%'   
select * from CLMASTER where ADDRESS like reverse('TEERTS%')  
select * from CLMASTER where reverse(ADDRESS) like reverse('%STREET')

CLMASTER拥有大约500,000条记录,大约有7,400个地址以“Street”结尾,大约8,500个地址包含“Street”,但不一定在最后。每次测试运行耗时2秒,并且它们都返回相同数量的行,除了 %STREET%,它发现额外的900左右的结果,因为它选择了最后有公寓号码的地址。

由于SQL Server测试没有显示执行时间的任何差异,我移动到PHP,我使用以下代码,切换每个语句,快速运行多个测试:

<?php

    require_once("config.php");
    $connection = odbc_connect( $connection_string, $U, $P );

    for ($i = 0; $i < 500; $i++) {
    $m_time = explode(" ",microtime());
    $m_time = $m_time[0] + $m_time[1];

    $starttime = $m_time;

    $Message=odbc_exec($connection,"select * from CLMASTER where ADDRESS like '%STREET%'");
    $Message=odbc_result($Message,1);

    $m_time = explode(" ",microtime());
    $m_time = $m_time[0] + $m_time[1];

    $endtime = $m_time;

    $totaltime[] = ($endtime - $starttime);

}

odbc_close($connection);

echo "<b>Test took and average of:</b> ".round(array_sum($totaltime)/count($totaltime),8)." seconds per run.<br>";
echo "<b>Test took a total of:</b> ".round(array_sum($totaltime),8)." seconds to run.<br>";

?>

此测试的结果与在SQL Server中测试时的结果一样模糊。

%STREET 以166.5823秒(每个查询平均值.3331)完成,平均在.0228中找到500个结果。

%STREET% 在149.4500秒内完成(每个查询平均值为.2989),在.0177中找到平均500个结果。 (每个结果的更快时间,因为它在相似的时间内找到比其他结果更多的结果。)

reverse(ADDRESS) like reverse('%STREET') 完成时间为134.0115秒(每次查询平均值为.2680),平均值为500秒,结果为.0183秒。

reverse('TREETS%') 在167.6960秒完成(每个查询的平均值为.3354),在.0229中找到平均500个结果。

我们期望这个测试能够证明这一点 %STREET% 这将是最慢的整体,而它实际上是最快的运行,并有最好的平均时间返回500结果。虽然建议 reverse('%STREET') 整体运行速度最快,但返回500个结果的速度要慢一些。

额外的乐趣:当我们运行测试时,同事在服务器上运行探查器,发现使用双通配符会显着增加CPU使用率,而其他测试则相互之间的1-2%。

是否有任何SQL效率专家可以解释为什么在搜索字符串末尾使用通配符比开头更好的做法,也许为什么在字符串的开头和结尾使用通配符进行搜索比使用通配符更快刚刚开始?


4594
2017-08-03 12:29


起源

您是否在每次测试前清除缓冲区和缓存? - Tom Chantler
是的,在每个查询测试之前,我们重新启动服务器以确保它是一个公平的测试。 - Jeremy1026
reverse()方法将强制进行表扫描,因为每行必须反转,它通常与前缀 - 通配符+预先计算的反向列一起使用 - Alex K.
即使模式以通配符开头,索引也可以减少I / O,因为不需要扫描表行。覆盖指数也可以提高性能。 - HABO


答案:


在字符串的末尾加上通配符,比如 'abc%', 有助于 如果 该列已编入索引,因为它可以直接查找以该列开头的记录 'abc' 并忽略其他一切。在开头使用外卡意味着它必须查看每一行,无论索引如何。

好文章 这里 有更多解释。


17
2017-08-03 12:35



通过扩展,这意味着做类似的事情 reverse(col) like 'abc%'是个坏主意。 - Adam Robinson
是, REVERSE 或任何其他更改索引列的计算意味着您失去了sargability。 - Bort
感谢您提供的答案/评论 - Jeremy1026


只有通配符在一个结尾 Like 字符串将使用索引。

你应该看看使用FTS Contains 如果你想提高字符串前面和后面的通配符的速度。也 请参阅有关Contains vs. Like的相关SO帖子


2
2017-08-03 12:37



感谢您提供的答案,遗憾的是,切换到Contains对我们来说不是一个可行的解决方案,因为我们需要完整的文本索引(相当于数百个)表,以使其成为可行的解决方案。我们经常搜索特定的子串和其他项目。 - Jeremy1026


微软 保留关闭通配符更有效,因为它可以(如果存在)使用索引而不是执行扫描。想想搜索可能如何工作,如果你不知道它之前是什么,那么你必须扫描所有内容,但是如果你只搜索尾部,那么你可以订购行甚至可能(取决于你在寻找什么) )做一个准二分法搜索。

连接或谓词中的某些运算符往往会产生资源密集型操作。带有通配符(“%a value%”)的值的LIKE运算符几乎总是会导致表扫描。由于前面的通配符,这种类型的表扫描是非常昂贵的操作。仅具有关闭通配符的LIKE运算符可以使用索引,因为索引是B +树的一部分,并且通过从左到右匹配字符串值来遍历索引。

所以,上面的引用也解释了为什么有一个 巨大 运行两个通配符时处理器出现峰值。它仅通过偶然事件更快地完成,因为有足够的马力来掩盖低效率。在尝试确定查询的性能时,您希望查看查询的执行而不是服务器的资源,因为这些可能会产生误导。如果我有一台具有足够马力的服务器来满足天气的需求,并且我在小到500,000行的桌面上运行查询,那么结果将会产生误导。

减少微软引用你的答案的事实,在进行性能分析时,考虑学习如何阅读执行计划。这是一项投资和 非常 干,但从长远来看,它是值得的。

简而言之,无论谁表明尾随通配符只是更有效,都是正确的。


1
2017-08-03 12:35



@ Jeremy1026 - 我更新了我的答案,更详细地说明了服务器性能使用的结果。 - Mike Perrenoud
谢谢你提供的答案。 - Jeremy1026
@ Jeremy1026 - 不是问题。 - Mike Perrenoud


在MS SQL中,如果你想拥有以'ABC'结尾的名字,那么你可以拥有如下的查询(假设表名是 student

select * from  student where student_name like'%[ABC]'

所以它会给那些以'A','B','C'结尾的名字。

2)如果您想要以'ABC'开头的名称,则表示 -

select * from student where student_name like '[ABC]%'

3)如果你想在中间有'ABC'的名字

select * from student where student_name like '%[ABC]%' 

-4
2018-02-25 04:31