SQL Server - 高度倾斜数据分布的查询优化

And*_*rei 5 performance sql-server statistics sql-server-2008-r2 query-performance

我正在尝试优化一个与此类似的查询:

select top(1)
    t1.Table1ID,
    t1.Column1,
    t1....
    ....
    t2.Table2ID,
    t2....
    ....
    c.FirstName,
    c.LastName,
    c....
from BigTable1 t1
join BigTable2 t2
    on t1.Table1ID = t2.Table1ID
join Customer c
    on t2.CustomerID = c.CustomerID
join Table4 t4
    on t4.Table4ID = t2.Table4ID
join Table5 t5
    on t5.Table5ID = t1.Table5ID
join Table6 t6
    on t6.Table6ID = t5.Table6ID
where 
    t4.Column1 = @p1
    and t1.Column1 = @p2
    and t3.FirstName = @FirstName
    and t3.LastName = @LastName
    and t6.Column1 = @p5
    and (@p6 is null or t2.Column6 = @p6)
order by t2.Table2ID desc
option(recompile);
Run Code Online (Sandbox Code Playgroud)

BigTable1、BigTable2、Customer - 是大型事务表(数亿行),Table4、Table5、Table6 是相对静态的小型查找表。问题是那些大表中的数据分布很不均衡,因此这个查询的性能通常很差(执行计划中的估计行数与实际行数非常不同)。更新这些大表的统计数据无济于事(直方图中的 200 步不足以覆盖数据分布中的所有偏差)。例如,在 Customer 表中,有一些(FirstName、LastName)组合对应于大约 500k 条记录。

我看到 2 个选项可以提高此类查询的性能:

  1. 将此查询拆分为较小的查询并将中间结果保存到临时表中。通过这种将中间结果物化到临时表中的方法,我们可以为优化器提供更好的基数估计机会,但是我们在 tempdb 上增加了大量的附加负载(因为此查询执行得相当频繁,每秒最多执行几次)。另一个缺点是它不是对所有参数值都更快,对于非典型参数值似乎明显更好(当原始查询可能需要几分钟时,这只需几秒钟),但对于对应于更多或更少的参数唯一(或不频繁)行 使用临时表的方法较慢。
  2. 为尖峰值创建过滤统计信息,例如对于 Customer 表,它看起来像这样:

    declare @sql nvarchar(max) = N'', @i int, @N int;
    select top (1000)
    identity(int,1,1) as id,
    FirstName,
    LastName,
    count(*) as cnt
    into #FL
    from Customer
    where 
    FirstName is not null 
    and LastName is not null
    group by
    FirstName,
    LastName
    order by cnt desc;
    
    set @N = @@ROWCOUNT;
    set @i = 1;
    
    while @i <= @N
    begin
        select @sql = 'CREATE STATISTICS Customer_FN_LN_' + cast(id as varchar(10)) + ' ON dbo.Customer(CustomerID) WHERE FirstName = ''' + FirstName + ''' AND LastName = ''' + LastName + ''' WITH FULLSCAN, NORECOMPUTE'
    from #FL
    where id = @i;
    exec sp_executesql @sql;
    set @i = @i + 1;
    end;
    
    Run Code Online (Sandbox Code Playgroud)

因此,如果我们为这 3 个大表创建此过滤统计信息并重新创建这些统计信息,比如说,每周,我们应该可以在原始查询中进行行估计。

根据我以前的经验,我通常使用临时表的方法,但在这种情况下,过滤统计数据的方法看起来更有吸引力。不过,我还没有在生产中使用它,我很好奇它的缺点是什么。

所以我的问题是:

  1. 有没有其他方法可以帮助优化器处理高度倾斜的数据分布?
  2. 在第二种方法中手动创建和处理过滤统计的缺点是什么?

Pau*_*ite 5

  1. 是否有其他方法可以帮助优化器处理高度倾斜的数据分布?

过滤统计信息和使用中间临时表分解查询(正确!)是主要选项,但您也可以考虑如何使用索引视图来提供帮助。如果正确实现,索引视图应该与基表上的额外非聚集索引具有大致相同的影响。

使用WITH (NOEXPAND)而不是依赖自动匹配(仅限企业版)将允许优化器在索引视图上创建和使用统计信息。

更一般地说,如果您能够提前识别“安全”或“不安全”值,则可以考虑采用混合方法(包括动态 SQL),如Kimberly L. Tripp 的《构建高性能存储过程》中详细介绍的那样。

您还可以考虑针对不同情况优化多个单独的过程,在每个过程中使用适当的方法,包括诸如OPTIMIZE FOR.

最后,您可以通过查询存储(如果可用)获得计划指南和/或强制计划。

  1. 在第二种方法中手动创建和处理过滤统计数据有哪些缺点?

主要问题是过滤后的统计数据没有像人们希望的那样频繁更新。您可以通过手动刷新这些统计信息来解决此问题。

您已经在重新编译,因此应该解决使用参数化查询过滤统计信息的问题。


Mat*_*ino 0

当我读到你的问题时,我立即想到了过滤统计数据。问题是,如果查询被参数化,则无法使用它们,除非您提示重新编译。

查看 Erik Darling 的这篇好文章:https://www.brentozar.com/archive/2016/12/filtered-statistics-follow/

  • 感谢您的链接,但此查询确实有一个重新编译提示,以避免参数嗅探问题(对于存储过程的情况),以及在作为临时调用时的情况下的“优化未知”行为。 (2认同)