【问题标题】:Optimizing slow performing greatest-n-per-group query优化执行缓慢的每组最大 n 个查询
【发布时间】:2018-12-12 19:31:12
【问题描述】:

我有下表:

id | query   | update_date | website_id | device  | page    | impressions | clicks | position | is_brand
---+---------+-------------+------------+---------+---------+-------------+--------+----------+---------
1  | kitchen | 2018-05-01  | 2          | desktop | http... | 11000       | 50     | 3        | 1
2  | table   | 2018-05-01  | 2          | desktop | http... | 7000        | 40     | 3        | 0
3  | kitchen | 2018-05-02  | 2          | desktop | http... | 11500       | 55     | 3        | 1
4  | table   | 2018-05-02  | 2          | desktop | http... | 7100        | 35     | 3        | 0

在此表中,我需要一个过程,对于每个唯一查询,我都可以在给定时间段内的点击方面为我提供表现最佳的行。这导致了以下过程:

create or alter procedure get_best_website_querys
    @from as date,
    @to as date,
    @website_id as int
as
begin
    WITH    cte
          AS (SELECT    *
              ,         ROW_NUMBER() OVER (PARTITION BY query ORDER BY clicks DESC) RN
              FROM      search_console_query
              where 
                update_date >= @from and 
                update_date <= @to and 
                website_id = @website_id 
             )
    SELECT  cte.id
     ,      cte.query
     ,      cte.update_date
     ,      cte.website_id
     ,      cte.device
     ,      cte.page
     ,      cte.impressions
     ,      cte.clicks
     ,      cte.POSITION
     ,      cte.is_brand
    FROM    cte
    WHERE   RN = 1
end;

现在,这有效并给了我正确的结果。我的问题是这个表变得非常大并且这个查询执行得相当慢(一年> 3分钟)。该查询给出以下执行计划:

在表上,clicks 上有一个非聚集索引,(website_id, update_date) 上有一个聚集索引。

我想要一些关于什么是让它表现更好的最佳方法的意见。任何意见将不胜感激。

【问题讨论】:

  • query 列上的索引可能会有所帮助。
  • 您是否尝试在query, clicks 上添加索引(按此顺序)?
  • BEGIN之后也使用SET NOCOUNT ON
  • 我会尝试在query 上创建一个非聚集索引,在clicks 上创建另一个。

标签: sql sql-server database tsql greatest-n-per-group


【解决方案1】:

首先,尝试在search_console_query scq(website_id, update_date, query, clicks) 上添加索引。

然后建议尝试这个版本:

select scq.*
from search_console_query scq
where scq.update_date >= @from and 
      scq.update_date <= @to and 
      scq.website_id = @website_id and
      scq.clicks = (select max(scq2.clicks)
                    from search_console_query scq2
                    where scq2.website_id = scq.website_id and
                          scq2.query = scq.query and
                          scq2.update_date >= @from and
                          scq2.update_date <= @to
                    );

此版本可以利用两个索引:search_console_query(website_id, query, update_date, clicks)search_console_query(website_id, update_date, query, clicks)

这略有不同,因为如果出现平局,它将为查询返回多行。如果性能显着提高(这是一个问题),则可以修复。

编辑:

为第二个版本删除重复项的最简单方法是假设该表具有唯一的id 列:

select scq.*
from search_console_query scq
where scq.update_date >= @from and 
      scq.update_date <= @to and 
      scq.website_id = @website_id and
      scq.sqc_id = (select top (1) sqc2.id
                    from search_console_query scq2
                    where scq2.website_id = scq.website_id and
                          scq2.query = scq.query and
                          scq2.update_date >= @from and
                          scq2.update_date <= @to
                    order by scq2.clicks desc);

【讨论】:

  • 感谢您的意见。我已经创建了一个聚集索引(website_id, update_date, query, clicks) 而不是原来的索引,以及一个在query 和一个(query, clicks).. 但是您的查询比原来的查询要好很多。但是有很多重复的行。这是可以在不牺牲太多性能的情况下解决的问题吗?
  • @FrederikHansen 查看您的原始查询是否能够使用此索引。
  • @SalmanA 执行计划说正在使用这个索引,但是性能还是不是很好。
  • @FrederikHansen。 . .使用order bytop 查看编辑。
【解决方案2】:

似乎 select 子句中的所有列都是可索引的;您可以尝试使用包含的列创建一个庞大的覆盖索引:

CREATE INDEX TEST_0001 ON search_console_query (
    website_id,
    update_date,
    query,
    clicks
) INCLUDE (
    id,
    device,
    page,
    impressions,
    position,
    is_brand
)

当你这样做的时候,试试下面的更多变体,看看 SQL Server 选择了哪一个,然后删除不必要的:

  • 更改website_id, update_date的顺序
  • 在包含的列中移动 query, clicks

【讨论】:

  • 所以我做了3个索引,它似乎更喜欢最后一个(包含query, clicks)。我应该根据这些信息更改我的聚集索引吗?而且,我的表现并没有增加太多。正如我所看到的,我唯一的选择是升级我的数据库。这是一个正确的假设吗?
  • 我不会更改聚集索引。 SQL server 也不应该直接从索引中获取结果(执行计划应该会告诉)。我认为应该尽可能好。
【解决方案3】:

另一种方法来做到这一点。但不确定性能,通常此模式用于查找选择列表中未分组列的最新记录。

SELECT          a.id,
                a.query,
                a.update_date,
                a.website_id,
                a.device,
                a.page,
                a.impressions,
                a.clicks,
                a.POSITION,
                a.is_brand
FROM            search_console_query a
LEFT JOIN       search_console_query b  ON b.website_id = a.website_id
                                       AND a.query = b.query
                                       AND a.clicks > b.clicks
WHERE           update_date >= @from
                AND update_date <= @to
                AND website_id = @website_id
                AND b.clicks IS NULL

【讨论】:

    【解决方案4】:

    我建议使用上面建议的索引。其次,参数嗅探也可能发生在这里。我建议您在存储过程中重新声明变量,如下所示,以免发生参数嗅探:

    create or alter procedure get_best_website_querys    
        @from as date,
        @to as date,
        @website_id as int
    as
    begin
    DECLARE @StartDate AS DATE = @from
           ,@EndDate AS DATE = @to
           ,@WebsiteID AS INT = @website_id
    
          WITH    cte
          AS (SELECT    *
              ,         ROW_NUMBER() OVER (PARTITION BY query ORDER BY clicks DESC) RN
              FROM      search_console_query
              where 
                update_date >= @StartDate and 
                update_date <= @EndDate and 
                website_id = @WebsiteID
             )
    SELECT  cte.id
     ,      cte.query
     ,      cte.update_date
     ,      cte.website_id
     ,      cte.device
     ,      cte.page
     ,      cte.impressions
     ,      cte.clicks
     ,      cte.POSITION
     ,      cte.is_brand
    FROM    cte
    WHERE   RN = 1
    end;
    

    【讨论】:

      猜你喜欢
      • 2012-08-06
      • 2022-01-06
      • 2015-05-13
      • 2010-10-16
      • 2014-08-06
      • 1970-01-01
      • 2014-08-04
      • 2011-11-22
      • 1970-01-01
      相关资源
      最近更新 更多