简体   繁体   English

功能慢,但查询运行快

[英]Function is slow but query runs fast

I have a simple Table-Valued function that takes around 5 second to execute. 我有一个简单的表值函数,大约需要5秒才能执行。 The function holds a query which returns the data in 1 sec. 该函数保留一个查询,该查询将在1秒内返回数据。 I have read through some blogs where it is said that this might be due to parameter sniffing but couldn't find a resolution yet. 我已经读过一些博客,据说这可能是由于参数嗅探引起的,但尚未找到解决方法。 How can I fix the function if it is due to parameter sniffing? 如果由于参数嗅探而导致该如何解决?

CREATE FUNCTION [dbo].[fn_PurchaseRecord]
(
@ID INT = NULL,
@Name nvarchar(MAX),
@PurchaseDate DATE
)
RETURNS  @result TABLE 
(
[ID] [int]  NULL,
[Name] [varchar](20) NULL,
[BasePrice] [FLOAT] NULL,
[Amount] [FLOAT]
)

AS BEGIN
WITH CTE_Purchase AS 
    (
    SELECT
        ht.ID,
        ProductName                             AS Name,
        BasePrice                               AS BasePrice
    FROM
        data.PurchaseRecord i (NOLOCK)
    WHERE  
        i.ID = @ID
        AND
        Date = @PurchaseDate
        AND
        BuyerName=@Name
        )
INSERT INTO @result
SELECT
    ID,
    Name,
    BasePrice,
    BasePrice*10.25
FROM
    CTE_Purchase
RETURN;

END 结束

Why not a single-statement TVF ? 为什么不使用单语句TVF?

CREATE FUNCTION [dbo].[fn_PurchaseRecordTESTFIRST]
(
@ID INT = NULL,
@Name nvarchar(MAX),
@PurchaseDate DATE
)
RETURNS TABLE 

Return (

    SELECT ID
          ,Name = ProductName
          ,BasePrice
          ,Amount = BasePrice*10.25
    FROM  data.PurchaseRecord i 
    WHERE i.ID = @ID
      AND Date = @PurchaseDate
      AND BuyerName=@Name
)

If parameter sniffing is happening it's the least of your worries - Sean hit nail on the head when saying that Multi-statement Table Valued Functions (mTVFs) should be avoided like the plague. 如果发生参数嗅探,这是您最少的担心-当肖恩(Sean)说要避免像瘟疫那样避免使用多语句表值函数(mTVF)时,他的想法就很少了。 By design, they're going to be much slower than an inline Table Valued Function (iTVF) in that you define a table, populate it, then return it. 通过设计,它们将比内联表值函数(iTVF)慢得多,因为您定义了一个表,然后填充该表,然后将其返回。 iTVF's, on the other hand, can be thought of as views that accept parameters and returns data directly from the underlying tables. 另一方面,iTVF可以看作是接受参数并直接从基础表返回数据的视图。

Another HUGE problem with mTVFs is that they kill parallelism; mTVF的另一个巨大问题是它们杀死了并行性。 this means that if you have 2 CPUS or 2,000 CPUs only only ONE will work on resolving your query. 这意味着如果您有2个CPU或2,000个CPU,则只有一个能解决您的查询。 No exceptions. 没有例外。 Looks have a look at Jeff Moden's delimitedsplit8K: 看看Jeff Moden的delimitedsplit8K:

CREATE FUNCTION [dbo].[DelimitedSplit8K]
--===== Define I/O parameters
        (@pString VARCHAR(8000), @pDelimiter CHAR(1))
--WARNING!!! DO NOT USE MAX DATA-TYPES HERE!  IT WILL KILL PERFORMANCE!
RETURNS TABLE WITH SCHEMABINDING AS
 RETURN
--===== "Inline" CTE Driven "Tally Table" produces values from 1 up to 10,000...
     -- enough to cover VARCHAR(8000)
  WITH E1(N) AS (
                 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
                 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
                 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
                ),                          --10E+1 or 10 rows
       E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
       E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
 cteTally(N) AS (--==== This provides the "base" CTE and limits the number of rows right up front
                     -- for both a performance gain and prevention of accidental "overruns"
                 SELECT TOP (ISNULL(DATALENGTH(@pString),0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
                ),
cteStart(N1) AS (--==== This returns N+1 (starting position of each "element" just once for each delimiter)
                 SELECT 1 UNION ALL
                 SELECT t.N+1 FROM cteTally t WHERE SUBSTRING(@pString,t.N,1) = @pDelimiter
                ),
cteLen(N1,L1) AS(--==== Return start and length (for use in substring)
                 SELECT s.N1,
                        ISNULL(NULLIF(CHARINDEX(@pDelimiter,@pString,s.N1),0)-s.N1,8000)
                   FROM cteStart s
                )
--===== Do the actual split. The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found.
 SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY l.N1),
        Item       = SUBSTRING(@pString, l.N1, l.L1)
   FROM cteLen l;
GO

Now let's build an mTVF version like so and do a performance test... 现在让我们构建一个像这样的mTVF版本并进行性能测试...

CREATE FUNCTION [dbo].[DelimitedSplit8K_MTVF]
        (@pString VARCHAR(8000), @pDelimiter CHAR(1))
RETURNS @table TABLE (ItemNumber int, Item varchar(100)) 
AS
BEGIN
--===== "Inline" CTE Driven "Tally Table" produces values from 1 up to 10,000...
     -- enough to cover VARCHAR(8000)
  WITH E1(N) AS (
                 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
                 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
                 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
                ),                          --10E+1 or 10 rows
       E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
       E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
 cteTally(N) AS (--==== This provides the "base" CTE and limits the number of rows right up front
                     -- for both a performance gain and prevention of accidental "overruns"
                 SELECT TOP (ISNULL(DATALENGTH(@pString),0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
                ),
cteStart(N1) AS (--==== This returns N+1 (starting position of each "element" just once for each delimiter)
                 SELECT 1 UNION ALL
                 SELECT t.N+1 FROM cteTally t WHERE SUBSTRING(@pString,t.N,1) = @pDelimiter
                ),
cteLen(N1,L1) AS(--==== Return start and length (for use in substring)
                 SELECT s.N1,
                        ISNULL(NULLIF(CHARINDEX(@pDelimiter,@pString,s.N1),0)-s.N1,8000)
                   FROM cteStart s
                )
--===== Do the actual split. The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found.
 INSERT @table
 SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY l.N1),
        Item       = SUBSTRING(@pString, l.N1, l.L1)
   FROM cteLen l;

 RETURN;
END
GO

Before continuing I want to address @John Cappelletti 's statement: 在继续之前,我想谈谈@John Cappelletti的声明:

I've seen claims like this before [about MAX data types], but I've yet to see any compelling stats 在[关于MAX数据类型]之前,我已经看到过这样的声明,但是我还没有看到任何令人信服的统计数据

For some compelling stats let's make a minor tweek to the iTVF version of delimitedSplit8K and change the input string to varchar(max): 对于一些引人注目的统计数据,让我们对iTVF版本的delimitedSplit8K进行一个小调,将输入字符串更改为varchar(max):

CREATE FUNCTION [dbo].[DelimitedSplit8K_VCMAXINPUT]
        (@pString VARCHAR(max), @pDelimiter CHAR(1))
RETURNS TABLE WITH SCHEMABINDING AS
 RETURN
--===== "Inline" CTE Driven "Tally Table" produces values from 1 up to 10,000...
     -- enough to cover VARCHAR(8000)
  WITH E1(N) AS (
                 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
                 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
                 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
                ),                          --10E+1 or 10 rows
       E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
       E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
 cteTally(N) AS (--==== This provides the "base" CTE and limits the number of rows right up front
                     -- for both a performance gain and prevention of accidental "overruns"
                 SELECT TOP (ISNULL(DATALENGTH(@pString),0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
                ),
cteStart(N1) AS (--==== This returns N+1 (starting position of each "element" just once for each delimiter)
                 SELECT 1 UNION ALL
                 SELECT t.N+1 FROM cteTally t WHERE SUBSTRING(@pString,t.N,1) = @pDelimiter
                ),
cteLen(N1,L1) AS(--==== Return start and length (for use in substring)
                 SELECT s.N1,
                        ISNULL(NULLIF(CHARINDEX(@pDelimiter,@pString,s.N1),0)-s.N1,8000)
                   FROM cteStart s
                )
--===== Do the actual split. The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found.
 SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY l.N1),
        Item       = SUBSTRING(@pString, l.N1, l.L1)
   FROM cteLen l;
GO

Now we have three versions of the function: the original iTVF, one that accepts varchar(max) and an mTVF version. 现在,我们有了该函数的三个版本:原始iTVF,一个接受varchar(max)的版本和一个mTVF版本。 Now a performance test. 现在进行性能测试。

-- sample data
IF OBJECT_ID('tempdb..#string') IS NOT NULL DROP TABLE #string;
SELECT TOP (10000) 
  id  = IDENTITY(int, 1,1), 
  txt = REPLICATE(newid(), ABS(checksum(newid())%5)+1)
INTO #string
FROM sys.all_columns a, sys.all_columns b;

SET NOCOUNT ON;

-- Performance tests:
PRINT 'ITVF 8K'+char(13)+char(10)+replicate('-',90);
GO
DECLARE @st datetime2 = getdate(), @x varchar(20);
SELECT  @x = ds.Item
FROM #string s
CROSS APPLY dbo.DelimitedSplit8K(s.txt, '-') ds;
PRINT datediff(ms, @st, getdate());
GO 5

PRINT 'MTVF 8K'+char(13)+char(10)+replicate('-',90);
GO
DECLARE @st datetime2 = getdate(), @x varchar(20);
SELECT  @x = ds.Item
FROM #string s
CROSS APPLY dbo.DelimitedSplit8K_MTVF(s.txt, '-') ds;
PRINT datediff(ms, @st, getdate());
GO 5

PRINT 'ITVF VCMAX'+char(13)+char(10)+replicate('-',90);
GO
DECLARE @st datetime2 = getdate(), @x varchar(20);
SELECT  @x = ds.Item
FROM #string s
CROSS APPLY dbo.DelimitedSplit8K_VCMAXINPUT(s.txt, '-') ds;
PRINT datediff(ms, @st, getdate());
GO 5

and the results: 结果:

ITVF 8K
------------------------------------------------------------------------------------------
Beginning execution loop
280
267
284
300
280
Batch execution completed 5 times.

MTVF 8K
------------------------------------------------------------------------------------------
Beginning execution loop
1190
1190
1157
1173
1187
Batch execution completed 5 times.

ITVF VCMAX
------------------------------------------------------------------------------------------
Beginning execution loop
1204
1220
1190
1190
1203
Batch execution completed 5 times.

Both the mTVF and iTVF version that takes varchar(max) are 4-5 times slower. 使用varchar(max)的mTVF和iTVF版本都慢4-5倍。 Again: Avoid mTVFs like the plague and avoid max data types whenever possible. 再次: 避免使用鼠疫之类的mTVF,并尽可能避免使用最大数据类型。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 在 Oracle 中运行速度极慢的 Postgres 查询 - Extremely slow Postgres query that runs fast in Oracle 使用日期表达式查询运行缓慢,但使用字符串文字快速查询 - Query runs slow with date expression, but fast with string literal Oracle中的慢查询(在SQL Server中快速运行)(相关子查询) - A slow query in Oracle (runs fast in SQL Server) (a correlated subquery) DELETE QUERY第一次运行缓慢,但第二次(相同条件)运行速度很快 - 如何在第一次运行时快速查询? - DELETE QUERY Runs Slow First Time, but second time onwards (for same condition) runs fast - How to make the query fast at first time running? SQL在SSRS中运行缓慢,但在SSMS中运行速度很快 - SQL runs slow in SSRS, but fast in SSMS 使用格式日期功能时,SQL查询运行速度非常慢 - SQL query runs very slow when using format date function 快速查询慢速创建表 - Fast to query slow to create table 用文字快速查询,但用变量慢查询 - Query fast with literal but slow with variable 什么索引可以确保此查询快速运行? - What index would insure this query runs fast? Oracle 报告耗时很久,但查询运行速度很快 - Oracle Report taking forever, but the query runs fast
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM