【问题标题】:SQL SERVER - Query optimization 'like' causing most cpu uses 100%SQL SERVER - 查询优化“喜欢”导致大多数 cpu 使用 100%
【发布时间】:2016-02-08 14:26:14
【问题描述】:

我在数据库 ProductsFilters 中有两个表。

架构:

我创建了一个查询,从过滤器表中查找所有记录,循环每个记录并调用一个为 Products 表设置类别 ID 的过程。

过滤表数据如下。

过滤器选择查询如下..

DECLARE @TotalRecords INT, @Start INT, @Limit INT, @CatId INT, @Merchants NVARCHAR(max), @NotMatch NVARCHAR(max), @WillMatch NVARCHAR(max);
SELECT @TotalRecords = COUNT(*) FROM filters;

SET @Limit = 1;
SET @Start = 0;

WHILE(@TotalRecords > 0)
BEGIN       
    SELECT @CatId = category_id, @Merchants = merchant_name, @NotMatch = not_match, @WillMatch = will_match FROM 
    (
        SELECT TOP (@Start + @Limit) *, ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS rnum 
        FROM filters
    ) a
    WHERE rnum > @Start;

    -- call filter procedure.
    exec procSetProductCategory @CatId = @CatId, @Merchants = @Merchants, @WillMatch = @WillMatch, @NotMatch = @NotMatch;

    SET @Start += 1;
    SET @TotalRecords -= 1;
END

procSetProductCategory如下..

CREATE PROC [dbo].[procSetProductCategory]
(
    @CatId INT = NULL,
    @Merchants NVARCHAR(max),
    @NotMatch NVARCHAR(max),
    @WillMatch NVARCHAR(max)
)
AS
BEGIN
SET NOCOUNT ON

    declare @query nvarchar(max), @orToken nvarchar(max), @andToken nvarchar(max);
     set @query = 'UPDATE Products SET category_id = '+ convert(nvarchar(20), @CatId) + ' WHERE category_id IS NULL AND merchant_name IN(' + @Merchants + ')';

    if(@WillMatch is not null AND LTRIM(RTRIM(@WillMatch)) != '')
    BEGIN

        set @andToken = '%'' AND product_name LIKE ''%';
        set @WillMatch = REPLACE(@WillMatch, '+', @andToken);

        set @orToken = '%'') OR (product_name LIKE ''%';
        set @query = @query + ' AND ((product_name LIKE '''+ '%' + REPLACE(@WillMatch, ',', @orToken) + '%''))';
    END

    if(@NotMatch is not null AND LTRIM(RTRIM(@NotMatch)) != '')
    BEGIN
        set @andToken = '%'' AND product_name NOT LIKE ''%';
        set @NotMatch = REPLACE(@NotMatch, '+', @andToken);

        set @orToken = '%'') OR (product_name NOT LIKE ''%';
        set @query = @query + ' AND ((product_name NOT LIKE '''+ '%' + REPLACE(@NotMatch, ',', @orToken) + '%''))';
    END

    EXECUTE sp_executesql @query;
END

它会生成如下的 sql 查询...

Query #1
-------------------------------------------------------------------------------------------------------
UPDATE Products SET category_id = 101 WHERE merchant_name IN('merchant 1','merchant 4','merchant 3') AND 
 (
    (product_name LIKE '%abcd%' AND product_name LIKE '%efhg%')
 ) AND (
    (product_name NOT LIKE '%3258%')
     OR (product_name NOT LIKE '%yxzs%')
)


Query #2
-------------------------------------------------------------------------------------------------------
UPDATE Products SET category_id = 102 WHERE merchant_name IN('merchant 3', 'merchant 4') AND 
(
    (product_name LIKE '%1258%') OR (product_name LIKE '%abcd%')
)

注意这里使用了一些技巧。

[,] 用于区分匹配短语。 [+] 在用于两个带有 AND 条件的匹配短语的匹配字段中。

这些查询与我需要的相同..

问题是,当我使用 500 000 个产品运行此查询时,它使用了大约 100% 的 CPU。

我们如何优化不影响结果但可以降低CPU使用率的查询?

【问题讨论】:

  • 你指的是哪个查询?
  • 优化查询..用于填充/迭代过滤器设置和查询 procSetProductCategory..
  • 您到底为什么将商家存储为逗号分隔列表?为什么不为每个商家、类别单独记录?而且我根本不理解将匹配和不匹配字段中的数据。但总的来说,您的设计有很大缺陷,这就是为什么很难查询的原因。
  • 我们这里的条件很少。比如某个过滤器将应用于一组商家提供的产品。 'WillMatch' 字段表示至少有一个以逗号分隔的单词应与产品名称匹配。 'NotMatch' 表示每个单词都不应该与产品名称匹配(不应存在于产品名称中的任何位置)。两个单词与 + 相邻表示这两个单词都应该存在于产品名称中,顺序无关紧要。所以这里的并发症很少。

标签: sql sql-server performance query-optimization cpu-usage


【解决方案1】:

首先,正如已经指出的那样:这里的逻辑确实有问题。也就是说,假设你坚持下去,你可能想尝试一些事情。 我的第一个问题是:这个东西运行多长时间?您不必太担心它会占用 100% 的 CPU;问题是完成需要多长时间。

查询1:

您似乎正在filters 表上创建一个循环,逐一获取每一行。

  • SQL 未针对逐行操作进行优化;您真的应该考虑将逻辑更改为基于集合的逻辑
  • 如果您真的想逐行做某事,请使用CURSOR 而不是当前方法
    • 首先查看整个表格,计算有多少过滤器
    • 然后遍历整个表并按SELECT 1 对记录进行排序
    • 从排序后的列表中,您选择rnum 大于您的计数器的列表

=>这在很多方面都是错误的,它实际上很痛苦=(

  • 如果您按SELECT 1 排序/排序,则它可以第一次按ABCD 顺序返回记录,第二次按BADC 顺序返回记录;并且两个答案都是正确的,因为您是按常量排序的:记录的实际顺序无关紧要!
  • 每次循环时,服务器都必须对整个表进行排序,然后才能判断哪些rnum 值符合大于@start 的要求;每一次!
  • 将有许多记录适合rnum > @start,用于填充记录的返回记录可以是其中任何一条!

要“解决”这个问题,我建议使用以下方法:

DECLARE @TotalRecords INT, 
        @Start INT, 
        @Limit INT, 
        @CatId INT, 
        @Merchants NVARCHAR(max), 
        @NotMatch NVARCHAR(max), 
        @WillMatch NVARCHAR(max);

DECLARE filter_loop CURSOR LOCAL FAST_FORWARD
    FOR SELECT category_id, 
               merchant_name,
               not_match,
               will_match
          FROM filters
         ORDER BY id -- not required but makes debugging easier
OPEN filter_loop 
FETCH NEXT FROM filter_loop INTO @CatId, @Merchants, @NotMatch, @WillMatch
WHILE @@FETCH_STATUS = 0
    BEGIN

        -- call filter procedure.
        exec procSetProductCategory @CatId = @CatId, @Merchants = @Merchants, @WillMatch = @WillMatch, @NotMatch = @NotMatch;

        -- get next filter
        FETCH NEXT FROM filter_loop INTO @CatId, @Merchants, @NotMatch, @WillMatch
    END
CLOSE filter_loop 
DEALLOCATE filter_loop 

查询2:

乍一看,我对存储过程本身几乎无能为力。有一些动态 sql 字符串构建可能会进行一些优化,但我非常怀疑它会产生多大影响。因为它现在是相当可读的,所以我会保持原样。 生成的查询确实看起来像这样:

UPDATE Products 
   SET category_id = 101 
 WHERE merchant_name IN ('merchant 1','merchant 4','merchant 3') 
   AND ((product_name LIKE '%abcd%' AND product_name LIKE '%efhg%') ) 
   AND ((product_name NOT LIKE '%3258%') OR (product_name NOT LIKE '%yxzs%'))

我建议为此创建以下索引:

CREATE INDEX idx_test ON Products (merchant_name) INCLUDE product_name)

事后思考

即使进行了上述更改,当处理 100k+ 条记录时,它仍会运行相当长的一段时间。唯一真正的解决方案是使用基于集合的方法,但需要一个庞大的动态 sql 字符串;或者对数据本身有更好的了解。例如。您可以尝试组合具有相同 Merchants value 但不同 Match/NoMatch 的不同 Filters 记录...可能不太难,但我建议从上面的建议,然后看看你最终的结果。

【讨论】:

  • 感谢您分享您的想法。它有很大帮助。我正在按照您的建议寻找解决方案。
【解决方案2】:

如果没有查询计划,很难确定,但我猜这是因为您在 '%something%' 上进行匹配,这意味着查询必须检查每一行。

这总是很慢,而且您无能为力帮助建立索引。

如果您要进行文本比较,则可以通过使用 SQL Server 的 full text matching 功能获得更好的性能。

【讨论】:

  • 我也试过了.. 但有些问题,因为我需要与否定和肯定短语(will_match,not_match)匹配的令牌。
猜你喜欢
  • 2014-03-30
  • 1970-01-01
  • 2017-07-22
  • 1970-01-01
  • 2013-08-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-10-23
相关资源
最近更新 更多