GSe*_*erg 1 performance sql-server execution-plan sql-server-2012 table-valued-parameters query-performance
我有一个标量函数,它返回一个大的 XML,它是通过一堆发票创建的。
可以使用几种不同的方法计算要提供给函数的确切发票列表,但函数每次都是相同的。出于这个原因,我声明了一个用户定义的表类型来包含表中的主键eInvoice.Header
并将其传递给函数。这样我就可以有几个不同的函数来决定要处理哪些发票,并且只有一个函数可以实际生成 XML:
create function eInvoice.GetRelevantLinesInOneWay()
returns table ...
create function eInvoice.GetRelevantLinesInAnotherWay()
returns table ...
create function eInvoice.GetXML(@lines eInvoice.InvoicePrimaryKeys readonly)
returns xml
as
begin
declare @x xml;
with xmlnamespaces(N'important namespace' as pro)
select @x = (
select
...
from
eInvoice.Header h
inner join @lines l on h.ST_PRIMARY = l.invoice_row_id
for xml path(N'pro:Import'), type
);
return @x;
end;
Run Code Online (Sandbox Code Playgroud)
不幸的是,这种设置已被证明是非常脆弱的。
通常@lines
包含大约 150 行(大约1m
in eInvoce.Header
)。正确的执行计划是在 上使用索引查找ST_PRIMARY
,当我将 的主体eInvoice.GetXML
作为临时查询执行时,总是会发生这种情况。
然而,当我将它存储为一个函数时,它会按预期工作一段时间,然后发生了一些事情(太多行@lines
,比如大约300
?),它决定将执行计划更改为完整扫描eInvoice.Header
并保持它方式。
用seek plan功能立即执行,用scan plan不知道要花多少时间,等了30分钟就取消了。
我尝试了各种方法来强制执行该函数的搜索计划。
with (forceseek)
aftereInvoce.Header
似乎工作,但当我实际执行它时,提示被忽略并执行扫描。option (recompile)
函数内部还是外部,在调用代码,似乎没有产生效果。option (use plan N'<plan>')
中不起作用,因为@lines
在其唯一的列上包含一个主键,并且该索引的名称对于 的每个实例都不同@lines
,但执行计划必须通过固定名称引用索引.有没有办法强制索引查找?
该问题不提供执行计划或完整的复制脚本,但根据所提供的信息,您应该使用FAST 1
提示。如果可以,您还应该考虑将标量函数转换为内联表值类型。
在此处下载 Microsoft 示例数据库。
CREATE TYPE dbo.PrimaryKeys AS TABLE
(
PK integer PRIMARY KEY
);
Run Code Online (Sandbox Code Playgroud)
您可以尝试各种组合INNER LOOP JOIN
(将写入的连接顺序颠倒,并FORCE ORDER
添加以抑制警告),FORCESEEK(index_name(columns))
(没有模式绑定)甚至QUERYTRACEON(8690)
防止性能假脱机;但根据我的经验,这FAST 1
是获得正确计划的最可靠方法,而且也更简单。
CREATE FUNCTION dbo.GetXML
(
@Lines dbo.PrimaryKeys READONLY
)
RETURNS xml
WITH SCHEMABINDING -- If possible
AS
BEGIN
DECLARE @x xml;
WITH XMLNAMESPACES (N'important namespace' AS pro)
SELECT @x =
(
SELECT
SOH.SalesOrderID,
SOH.OrderDate,
SOH.DueDate,
SOH.ShipDate,
SOH.[Status],
SOH.PurchaseOrderNumber,
SOH.AccountNumber,
SOH.CustomerID,
SOH.TotalDue
FROM @Lines AS L
JOIN Sales.SalesOrderHeader AS SOH
ON SOH.SalesOrderID = L.PK
ORDER BY
SOH.SalesOrderID
FOR XML PATH(N'pro:Import'), TYPE
)
OPTION (FAST 1);
RETURN @x;
END;
Run Code Online (Sandbox Code Playgroud)
除了避免标量函数的所有常见原因外,它们确实使计划分析变得更加困难。首先,您需要使用 Profiler 或扩展事件捕获实际计划 - 它们不会出现在 SSMS 等查询工具中。其次,并非所有提示都如您所愿,例如RECOMPILE
由于范围界定和单独的计划。
DECLARE @Lines dbo.PrimaryKeys;
INSERT @Lines (PK)
SELECT TOP (300) SOH.SalesOrderID
FROM Sales.SalesOrderHeader AS SOH;
SELECT dbo.GetXML(@Lines);
Run Code Online (Sandbox Code Playgroud)
无论表变量中的行数如何,上面的代码都能可靠地为我生成所需的计划。
没有FAST 1
函数中的提示,优化器选择合并连接并扫描两个表。该计划被缓存并重用于稍后的函数执行,其中TOP (300)
更改为TOP (1)
,重现问题的核心。
除非有绝对令人信服的理由坚持使用标量函数,否则您应该将其重写为内联表值形式,如下所示:
CREATE FUNCTION dbo.GetXMLTable
(
@Lines dbo.PrimaryKeys READONLY
)
RETURNS TABLE
AS
RETURN
WITH XMLNAMESPACES (N'important namespace' AS pro)
SELECT
X.xml_result
FROM
(
SELECT
SOH.SalesOrderID,
SOH.OrderDate,
SOH.DueDate,
SOH.ShipDate,
SOH.[Status],
SOH.PurchaseOrderNumber,
SOH.AccountNumber,
SOH.CustomerID,
SOH.TotalDue
FROM @Lines AS L
CROSS APPLY
(
SELECT TOP (1) SOH.*
FROM Sales.SalesOrderHeader AS SOH
WITH (FORCESEEK(PK_SalesOrderHeader_SalesOrderID(SalesOrderID)))
WHERE
SOH.SalesOrderID = L.PK
ORDER BY
SOH.SalesOrderID
) AS SOH
ORDER BY
SOH.SalesOrderID
FOR XML PATH(N'pro:Import'), TYPE
) AS X (xml_result);
Run Code Online (Sandbox Code Playgroud)
将TOP (1)
在交叉运用阻止了优化选择的任何其他比嵌套循环连接。该FORCESEEK
提示是不是必需的; 我已经包含它以说明在必要时如何使用它。
DECLARE @Lines dbo.PrimaryKeys;
INSERT @Lines (PK)
SELECT TOP (300) SOH.SalesOrderID
FROM Sales.SalesOrderHeader AS SOH;
SELECT GXT.xml_result
FROM dbo.GetXMLTable(@Lines) AS GXT;
Run Code Online (Sandbox Code Playgroud)
实际的执行计划(不使用 Profiler/XE)是:
归档时间: |
|
查看次数: |
822 次 |
最近记录: |