有没有办法将基数估计“注入”到 SQL Server 优化器(任何版本)?
即类似于 Oracle 的基数提示。
我的动力来自这篇文章,查询优化器真的有多好?[1],他们在其中测试基数估计器对选择不良计划的影响。因此,如果我可以强制 SQL Server 为复杂查询精确地“估计”基数就足够了。
[1] Leis, Viktor, et al. “查询优化器有多好,真的吗?” VLDB 捐赠论文集 9.3 (2015):204-215。
CARDINALITY您可以通过策略性地使用Adam Machanic 开发TOP的用户定义函数来获得类似于 Oracle提示的内容。让我们来看几个例子。我使用的是免费提供的 AdventureWorks 数据库。假设我真的需要在下面的查询中控制派生表返回的行数:MANY()th
您可以通过策略性地使用Adam Machanic 开发
的用户定义函数来获得类似于 Oracle提示的内容。让我们来看几个例子。我使用的是免费提供的 AdventureWorks 数据库。假设我真的需要在下面的查询中控制派生表返回的行数:
SELECT p.Name , th.ProductId , th.Quantity , th.ActualCost FROM Production.Product p INNER JOIN ( SELECT ProductId, Quantity, ActualCost FROM Production.TransactionHistory ) th ON p.ProductID = th.ProductID;
照原样,我估计有 113443 行:
如果我需要降低估计值,th我可以使用TOP查询OPTIMIZE FOR提示来设置行目标。这是一种方法:
th
TOP
OPTIMIZE FOR
DECLARE @row_goal BIGINT = 9223372036854775807; SELECT p.Name , th.ProductId , th.Quantity , th.ActualCost FROM Production.Product p INNER JOIN ( SELECT TOP (@row_goal) ProductId, Quantity, ActualCost FROM Production.TransactionHistory ) th ON p.ProductID = th.ProductID OPTION (OPTIMIZE FOR (@row_goal = 1));
我们可以看到估计只有 1 行:
我设置@row_goal为最大可能的BIGINT值以避免改变结果。查询提示指示优化器OPTIMIZE FOR优化查询,就好像@row_goal等于 1 一样。我将得到相同的结果,但查询将以不同方式优化。
@row_goal
BIGINT
增加基数估计比较棘手。我们不能仅仅增加 for 的值,TOP因为优化器会意识到它不会返回足够的行。但是,我们可以使用该MANY()函数将行添加到估计中。请注意,该MANY()函数将始终返回 0 行,但它的行估计会随着输入参数的变化而变化。假设您需要将派生表中的行估计值增加 10 倍。实现这一目标的一种方法:
MANY()
SELECT p.Name , th.ProductId , th.Quantity , th.ActualCost FROM Production.Product p INNER JOIN ( SELECT TOP (9223372036854775807) ProductId, Quantity, ActualCost FROM Production.TransactionHistory LEFT OUTER JOIN dbo.Many(10) AS m ON 1=1 ) th ON p.ProductID = th.ProductID;
我们可以看到估计是基表的 10 倍:
添加多余的内容TOP是为了防止优化器移动表。没有它,该MANY()功能可能会应用到计划中的错误位置。
如果您想要精确的高估而不是仅仅将行数乘以一个因子,则可以将这两种技术结合起来。例如,假设您确实需要派生表的估计正好是 1000000 行。实现这一目标的一种方法:
DECLARE @row_goal BIGINT = 9223372036854775807; SELECT p.Name , th.ProductId , th.Quantity , th.ActualCost FROM Production.Product p INNER JOIN ( SELECT TOP (@row_goal) ProductId, Quantity, ActualCost FROM Production.TransactionHistory LEFT OUTER JOIN dbo.Many(10) AS m ON 1=1 ) th ON p.ProductID = th.ProductID OPTION (OPTIMIZE FOR (@row_goal = 1000000));
我们可以看到估计是 1000000 行:
我需要提醒您,这些是查询优化通常不需要的高级技术。如果您想了解更多信息,我建议您观看Adam Machanic 介绍的Clash of the Row Goals 。
-- By Adam Machanic, reproduced with permission IF EXISTS (SELECT * FROM sys.objects WHERE name = 'Many' AND OBJECT_SCHEMA_NAME(object_id) = 'dbo') DROP FUNCTION dbo.Many GO CREATE FUNCTION dbo.Many(@n INT) RETURNS TABLE AS RETURN ( WITH a(x) AS ( SELECT * FROM ( VALUES (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) ) AS x0(x) ) SELECT TOP(@n) 1 AS x FROM a AS a1, a AS a2 WHERE a1.x % 2 = 0 ) GO