Sql server 插入到表值函数中会减慢速度

Sql server 插入到表值函数中会减慢速度,sql-server,performance,Sql Server,Performance,在SQLServer2008端,我有一个表值函数,它接收合并到单个VARBINARYMAX中的45k整数ID,将它们拆分并作为表返回。SplitID最多需要5秒。正如我在估计执行计划中看到的,100%是“表插入”。是否有可能以某种方式加速此功能 ALTER FUNCTION [dbo].[SplitIds](@data VARBINARY(MAX)) RETURNS @result TABLE(Id INT NOT NULL) AS BEGIN IF @data IS NULL

在SQLServer2008端,我有一个表值函数,它接收合并到单个VARBINARYMAX中的45k整数ID,将它们拆分并作为表返回。SplitID最多需要5秒。正如我在估计执行计划中看到的,100%是“表插入”。是否有可能以某种方式加速此功能

ALTER FUNCTION [dbo].[SplitIds](@data VARBINARY(MAX))
RETURNS @result TABLE(Id INT NOT NULL)
AS
BEGIN
    IF @data IS NULL
        RETURN
    DECLARE @ptr INT = 0, @size INT = 4
    WHILE @ptr * @size < LEN(@data)
    BEGIN
        INSERT INTO @result(Id)
        VALUES(SUBSTRING(@data, @ptr * @size + 1, @size))
        SET @ptr += 1
    END
    RETURN
END

您可以尝试更基于集合的方法

我保留了多语句TVF方法,因为生成数字表的内联方法在隔离状态下工作得很好,但是当合并到更大的查询中时,执行计划可能非常糟糕——这确保了拆分只发生一次

我还向返回表添加了一个主键,以便它包含一个有用的索引

CREATE FUNCTION [dbo].[SplitIds](@data VARBINARY(MAX))
RETURNS @result TABLE(Id INT NOT NULL PRIMARY KEY WITH (IGNORE_DUP_KEY=ON))
AS
  BEGIN
      IF @data IS NULL
        RETURN

      DECLARE @size INT = 4;

      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), -- 1*10^1 or 10 rows
           E2(N)
           AS (SELECT 1 FROM   E1 a, E1 b), -- 1*10^2 or 100 rows
           E4(N)
           AS (SELECT 1 FROM   E2 a, E2 b), -- 1*10^4 or 10,000 rows
           E8(N)
           AS (SELECT 1 FROM   E4 a, E4 b), -- 1*10^8 or 100,000,000 rows
           Nums(N)
           AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) - 1
               FROM   E8)
      INSERT INTO @result
                  (Id)
      SELECT TOP (DATALENGTH(@data)/@size) SUBSTRING(@data, N * @size + 1, @size)
      FROM   Nums

      RETURN
  END 
我将在大约160毫秒内完成以下操作

DECLARE @data VARBINARY(MAX) = 0x

WHILE DATALENGTH(@data) < 184000
  SET @data = @data + CRYPT_GEN_RANDOM(8000)

SELECT COUNT(*)
FROM   [dbo].[SplitIds](@data) 

这是我的基于集合的方法

create FUNCTION [dbo].[SplitIds1](@data VARBINARY(MAX))
returns table with SCHEMABINDING
as
return
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
), -- 10
e2(n) AS (SELECT 1 FROM e1 CROSS JOIN e1 AS b), -- 10*10
e3(n) AS (SELECT 1 FROM e1 CROSS JOIN e2), -- 10*100
e4(n) AS (SELECT 1 FROM e3 A CROSS JOIN e3 B), -- 1000*1000
Numbers(ptr,Size) AS (SELECT ROW_NUMBER() OVER (ORDER BY n)-1,4 FROM e4)
SELECT SUBSTRING(@data, ptr * Size + 1, Size) as Id
FROM Numbers
WHERE ptr * Size < LEN(@data)
关于我的方法的几点注释

向函数中添加SCHEMABINDING将避免不必要的表 执行计划中的假脱机操作员 还删除了@size变量,因为它是在函数内部硬编码的 将多语句表值函数更改为内联表值函数,它允许您在函数中查看select语句的执行计划,就像任何视图或select查询一样
当然,表插入是100%,因为这是函数唯一要做的事情……目前,它既不编译“Numbers”,也不编译比列列表中指定的列更多的列。或者返回正确的结果,当运行我答案底部的脚本时,只返回1000行,而不是46000行。@MartinSmith-对不起,现在我没有SSM来验证query@MartinSmith-更新了tally CTE以增加行数使用SQL fiddle对其进行验证。@MartinSmith-fiddle有问题,它在这里不起作用,否则我将验证sureWorks,就像一个符咒一样。现在需要0秒。谢谢。我不太明白为什么,但它不适用于单个id 0x00006120。它适用于0x000061F或0x000006121,适用于0x000061F0000612000006121。有什么想法吗?@Denis-Replace LEN with DATALENGTH-0x20被视为尾随空格,不计算在内。
DECLARE @data VARBINARY(MAX) = 0x

WHILE DATALENGTH(@data) < 184000
  SET @data = @data + CRYPT_GEN_RANDOM(8000)

SELECT COUNT(*)
FROM   [dbo].[SplitIds](@data) 
create FUNCTION [dbo].[SplitIds1](@data VARBINARY(MAX))
returns table with SCHEMABINDING
as
return
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
), -- 10
e2(n) AS (SELECT 1 FROM e1 CROSS JOIN e1 AS b), -- 10*10
e3(n) AS (SELECT 1 FROM e1 CROSS JOIN e2), -- 10*100
e4(n) AS (SELECT 1 FROM e3 A CROSS JOIN e3 B), -- 1000*1000
Numbers(ptr,Size) AS (SELECT ROW_NUMBER() OVER (ORDER BY n)-1,4 FROM e4)
SELECT SUBSTRING(@data, ptr * Size + 1, Size) as Id
FROM Numbers
WHERE ptr * Size < LEN(@data)