SQL Server中用户定义的表类型的性能

时间:2016-02-17 12:48:45

标签: sql-server tsql sql-server-2008-r2 user-defined-types table-valued-parameters

我们一直使用User-Defined Table Types将整数列表传递给存储过程。

然后我们使用它们连接到存储过程查询中的其他表。

例如:

CREATE PROCEDURE [dbo].[sp_Name]
(
    @Ids [dbo].[OurTableType] READONLY  
)
AS
    SET Nocount ON

    SELECT
        *
    FROM
        SOMETABLE
        INNER JOIN @Ids [OurTableType] ON [OurTableType].Id = SOMETABLE.Id

使用更大的数据集时,我们发现这方面的表现很差。

我们用来加快速度的一种方法是将内容转储到临时表中,然后将其加入。

例如:

CREATE PROCEDURE [dbo].[sp_Name]
(
    @Ids [dbo].[OurTableType] READONLY  
)
AS
    SET Nocount ON
    CREATE TABLE #TempTable(Id INT)
    INSERT INTO #TempTable
    SELECT Id from @Ids

    SELECT
        *
    FROM
        SOMETABLE
        INNER JOIN #TempTable ON #TempTable.Id = SOMETABLE.Id

    DROP TABLE #TempTable

这确实显着提高了性能,但我希望对这种方法以及我们未考虑的任何其他后果有所了解。此外,解释为什么这可以提高性能也可能有用。

N.B。有时我们可能需要传递的不仅仅是一个整数,因此我们不使用逗号分隔列表或类似的东西。

1 个答案:

答案 0 :(得分:15)

此主题已在之前讨论过。 JOIN性能不佳的主要原因是表值参数(TVP)是表变量。表变量不保留统计信息,并且查询优化器看起来只有1行。因此他们可以做INSERT INTO Table (column_list) SELECT column_list FROM @TVP;而不是JOIN。

有几件事要试图解决这个问题:

  1. 将所有内容转储到本地临时表(您已经在执行此操作)。这里的一个技术缺点是你正在tempdb复制传入TVP的数据(TVP和临时表都存储了他们的数据)。

  2. 也许尝试将用户定义的表类型定义为具有群集主键。您可以在[Id]字段内联:

    [ID] INT NOT NULL PRIMARY KEY
    

    不确定这对性能有多大帮助,但值得一试。

  3. 您可以尝试将OPTION (RECOMPILE)添加到查询中。这是一种让查询优化器查看表变量中有多少行的方法,以便它可以有适当的估计值。

    SELECT column_list
    FROM   SOMETABLE
    INNER JOIN @Ids [OurTableType]
            ON [OurTableType].Id = SOMETABLE.Id
    OPTION (RECOMPILE);
    

    这里的缺点是你有一个RECOMPILE,每次调用此proc时需要额外的时间。但这可能是整体净收益。

  4. 从SQL Server 2014开始,您可以利用内存中OLTP并为用户定义的表类型指定WITH (MEMORY_OPTIMIZED = ON)。有关详细信息,请参阅Scenario: Table variable can be MEMORY_OPTIMIZED=ON。我听说这绝对有帮助。遗憾的是,在SQL Server 2014和SQL Server 2016 RTM中,此功能仅适用于64位企业版。但是,从SQL Server 2016 SP1开始,此功能可供所有版本使用(可能的例外是SQL Server Express LocalDB)。

  5. SQL Server 2019引入了“Table variable deferred compilation”:

      

    使用表变量延迟编译,引用表变量的语句的编译将延迟到第一次实际执行语句。此延迟编译行为与临时表的行为相同。此更改导致使用实际基数而不是原始的单行猜测。

    有关详细信息,请参阅链接文档。

  6. PS。不要SELECT *。始终指定列列表。除非做IF EXIST(SELECT * FROM)...

    之类的事情