【问题标题】:SQL LIKE with parameter : huge performance hit带参数的 SQL LIKE:巨大的性能损失
【发布时间】:2013-11-04 10:06:47
【问题描述】:

我有这个问题:

select top 100 id, email, amount from view_orders
    where email LIKE '%test%' order by created_at desc

运行时间不到一秒。

现在我想参数化它:

declare @m nvarchar(200)
set @m = '%test%'
SELECT TOP 100 id, email, amount FROM view_orders
    WHERE email LIKE @m ORDER BY created_at DESC

5 分钟后,它仍在运行。对于任何其他类型的参数测试(如果我将“like”替换为“=”),它会下降到性能的第一个查询级别。

我使用的是 SQL Server 2008 R2。

我尝试过 OPTION(RECOMPILE) ,它下降到 6 秒,但仍然慢得多(非参数化查询是瞬时的)。由于这是一个我预计会经常运行的查询,因此这是一个问题。

表的列有索引,视图没有,不知道能不能有所作为。

视图连接了 5 个表:一个有 3,154,333 行(用户),一个有 1,536,111 行(订单),3 个最多有几十行(订单类型等)。搜索在“用户”表(有 3M 行)上完成。

硬编码值:

参数:

更新

我已使用SET STATISTICS IO ON 运行查询。结果如下(抱歉,我不知道怎么读):

硬编码值:

表“货币”。扫描计数 1,逻辑读取 201。

表“订单状态”。扫描计数 0,逻辑读取 200。

表“付款”。扫描计数 1,逻辑读取 100。

表“礼物”。扫描计数 202,逻辑读取 404。

表“订单”。扫描计数 95,逻辑读取 683。

表“用户”。扫描计数 1,逻辑读取 7956。

参数:

表“货币”。扫描计数 1,逻辑读取 201。

表“订单状态”。扫描计数 1,逻辑读取 201。

表“付款”。扫描计数 1,逻辑读取 100。

表“礼物”。扫描计数 202,逻辑读取 404。

表“用户”。扫描计数 0,逻辑读取 4353067。

表“订单”。扫描计数 1,逻辑读取 4357031。

更新 2

此后我看到了“强制索引使用”提示:

SELECT TOP 100 id, email, amount
FROM view_orders with (nolock, index=ix_email)
WHERE email LIKE @m
ORDER BY created_at DESC

不确定它是否会起作用,我不再在这个地方工作了。

【问题讨论】:

  • 您应该指定您正在使用的 SQL 数据库(以及当有多个可供选择时使用的存储引擎),因为运行时行为和优化能力取决于此。
  • 谢谢你说得对,我加了(我在SQL 2008 R2下)
  • 试试OPTION (RECOMPILE) sommarskog.se/dyn-search-2008.html
  • 请注意,当您使用 '=' 时,通配符 ('%') 不能用作通配符
  • 您可以通过使用 SET STATISTICS IO ON 运行每个查询来更深入地了解导致不同行为的原因,以查看哪些表被命中、多少次、多少页以及多少预读.

标签: sql-server performance tsql sql-server-2008-r2


【解决方案1】:

这可能是参数嗅探问题。 更好的索引或全文搜索是可行的方法,但您可能可以获得可行的折衷方案。 试试:

SELECT TOP 100 A, B, C FROM myview WHERE A LIKE '%' + @a + '%'
OPTION (OPTIMIZE FOR (@a = 'testvalue'));

(就像 Sean Coetzee 建议的那样,我不会在参数中传入通配符)

【讨论】:

  • OPTION RECOMPILE, OPTIMIZE FOR 将执行时间缩短至大约 35 秒,而 OPTIMIZE FOR 将其缩短至 30 秒。如何获得“更好的索引”?我会研究全文搜索。
  • 您是否尝试过重建大型表/索引的统计信息?
  • 我已经重建了整个数据库的索引(使用ALTER INDEX ALL ON ' + @TableName + ' REBUILD WITH (FILLFACTOR = 80))。奇怪的是,执行现在需要更长的时间(现在是 1 分钟并且还在计数)
  • 您是否可以添加其他条件作为 where 子句以减少行数?
  • 并非如此。另外,问题仅出现在参数上,而不是在硬编码值时出现。目前,我已经修改了查询生成,每次都对值进行硬编码,虽然很丑,但它可以工作。
【解决方案2】:

当您向 A 列添加索引时,您肯定会赢。 有时索引建议可以被 SQL Server 管理工作室借用。粘贴您的查询并按“显示估计的执行计划”按钮

【讨论】:

  • 这如何解释为什么第一个查询运行得很快而第二个查询却没有呢?如果您将字符串与LIKE '%....' 进行比较,那么索引还有什么用处
  • 我认为在第一种情况下,查询是硬编码的,在这种情况下,服务器可以在后台进行优化。
  • 只有当通配符不在搜索字符串的开头myitforum.com/cs2/blogs/jnelson/archive/2007/11/16/108354.aspx时才能这样做
  • 该列已编入索引(我已将该精度添加到问题中)。
  • @DrCopyPaste:有趣的文章,但是当我看到我写的一个示例的执行计划时,我看到索引确实使用了。一个有趣的细节。当我将索引放在 A 列时,带有参数化 like 子句的执行计划似乎与带有硬编码 like 子句的计划完全一样。在索引的情况下,我有完全不同的执行计划。在这个计划中,我看到了针对索引的 Index Seek 活动。
【解决方案3】:
CREATE INDEX index_name ON myview (A);
CREATE INDEX index_name ON myview (B);
CREATE INDEX index_name ON myview (C);

declare @a nvarchar(200)
set @a = '%testvalue%'
SELECT TOP 100 A, B, C FROM myview WHERE A LIKE @a

【讨论】:

  • 这无济于事,除了where中使用的一列索引太多
  • 该表的列已被索引(我已将该精度添加到问题中)。
  • 那该怎么办呢? @DrCopyPaste,我尝试在本地 sqlfiddle 上工作,但没有注意到我在自动生成的 20 行左右有什么不同?
  • 如果我确定问题所在,我会发布一个答案。我和你一样好奇。 :D
【解决方案4】:

如果你尝试会发生什么:

set @a = 'test'
select top 100 A, B, C 
  from myview 
 where A like '%' + @a + '%'

我尝试了一些虚拟数据的测试,看起来它可能会更快。

【讨论】:

  • 实际上这就是查询之前的样子。这与设置@a = '%test%' 的执行时间完全相同。该视图连接了 2 个几百万行的表,以及一个最多几十行的 3 个表。
【解决方案5】:

参数化版本的估计执行计划显然不对。我不相信我见过两次估计成本为 100% 的查询!因为成本应该是100%。有趣的是,它认为当您清楚地按用户表上的某些内容进行过滤时,它需要从订单开始。

我会重建视图中引用的所有表的统计信息。

update statistics <tablename> with resample

对涉及的每个表执行其中一项。

您可以尝试直接运行 sql(将粘贴视图主体复制到 sql 中),既参数化又不查看是否是视图 sql 有问题。

最终,即使您解决了这个问题,它实际上也只是一个权宜之计。您有 300 万用户,每次运行查询 sql 都必须遍历所有 300 万条记录(顶部查询中的 75% 扫描)才能找到所有可能的记录。您获得的用户越多,查询速度就越慢。非全文索引不能用于前面带有通配符的类似查询。

在这种情况下,您可以将 sql 索引视为书籍索引。您可以使用带有单词“部分”的书籍索引来快速找到任何内容吗?不,您必须扫描整个索引才能找出所有可能性。

你真的应该考虑在你的视图上使用全文索引。

【讨论】:

  • 感谢您的建议。但是,您如何解释参数化和非参数化之间的区别?
  • 您是根据执行时间来判断性能。您需要确保还查看查询成本。我想更新统计信息应该使它们内联。如果没有,这只是让编写 sql 内部代码的人比我更聪明的事情之一:)
  • 我已经更新了统计数据,但没有任何改变。您建议使用全文索引,但非参数化查询似乎不需要它们,那么除了更多维护之外,添加一些索引还能获得什么?我选择使用非参数化方法,它完全是零维护、低风险(对于我的用例)和高性能。 编辑,是的,我是根据执行时间来判断性能,因为还有什么对最终用户很重要?
  • 随着用户群的增加和/或记录数的增加,执行时间会变慢,因为类似 %texttosearch% 的搜索并不便宜。只是为了好玩,创建一个全文索引并检查成本差异。我相信你也会注意到它更快。
  • 是的,但是现在,'%search%' 很便宜,而 '%' + @search + '%' 不便宜。这是我遇到的问题。该网站正在 Rails/mongodb 中重新制作,所以我不想在我们的流程中引入额外的维护以获得不存在的收益(因为它使用肮脏的方法工作),仅仅几个月。但如果不是这样,我会使用全文索引。谢谢!
猜你喜欢
  • 1970-01-01
  • 2014-09-17
  • 1970-01-01
  • 1970-01-01
  • 2018-04-15
  • 1970-01-01
  • 1970-01-01
  • 2018-08-22
  • 1970-01-01
相关资源
最近更新 更多