EXEC sp_executesql 与 INSERT INTO 一起使用时真的很慢 :(

EXEC sp_executesql is really really slow when used with INSERT INTO :(

当我尝试将 sp_executesql 的一些结果插入变量 table 时,我的性能非常差。

首先,查询只是简单的Select。

EXEC sp_executesql N'SELECT      [a].[ListingId]
FROM        [dbo].[Listings] [a] LEFT OUTER JOIN 
            [dbo].[AgencyCompany] [b] ON [a].[AgencyCompanyId] = [b].[AgencyCompanyId]
ORDER BY    UpdatedOn DESC'

这 运行 会在几秒钟内完成,因为有几百万个结果要通过网络从云中的数据库传递到我的本地主机。所以这完全是kewl。

查询计划:

现在,让我们将查询更改为 INSERT INTO 结果...

DECLARE @ListingIds TABLE (ListingId INTEGER PRIMARY KEY)
INSERT INTO @ListingIds
EXEC sp_executesql N'SELECT      [a].[ListingId]
FROM        [dbo].[Listings] [a] LEFT OUTER JOIN 
            [dbo].[AgencyCompany] [b] ON [a].[AgencyCompanyId] = [b].[AgencyCompanyId]
ORDER BY    UpdatedOn DESC'

将结果 return 发送到我的本地主机大约需要 45 秒。 相同的查询(嗯,相同的 SELECT 查询)。

让我们看看查询计划...

现在让我们尝试使用原始 sql...

DECLARE @ListingIds TABLE (ListingId INTEGER PRIMARY KEY)
INSERT INTO @ListingIds
SELECT      [a].[ListingId]
FROM        [dbo].[Listings] [a] LEFT OUTER JOIN 
            [dbo].[AgencyCompany] [b] ON [a].[AgencyCompanyId] = [b].[AgencyCompanyId]
ORDER BY    UpdatedOn DESC

4 秒 运行 和计划..

科技..
- Sql 服务器 2012

评论有点长

您没有显示整个执行计划,但 "index scan" 表明正在发生的事情。索引用于按顺序检索数据。这意味着没有额外的排序步骤,查询可以立即开始 returning 数据。您看到的结果是 return.

证据?如果有数百万行,在获得 returned 结果时应该 一些 滞后。如果数据必须排序,则必须读取行然后排序。

另一方面,insert into 必须等到 all 数据已经生成,因为它必须将所有结果插入到一个 table。它不会 return 直到最后一行被读取。

编辑:

我注意到另一个问题。主索引希望数据按主键排序。您正在按另一个值对其进行排序。毫无疑问,额外种类的数据需要更多的时间。但是,10的系数似乎有点极端。

遗憾的是我现在没有机器可以详细说明,但我相信区别在于用主键声明临时 table。在第二个查询执行计划中,您可以看到聚簇索引插入占用了大部分时间。

使用不带主键的临时 table 应该可以达到相同的性能。

两种说法的不同之处在于,在raw版本中:

DECLARE @ListingIds TABLE (ListingId INTEGER PRIMARY KEY)
INSERT INTO @ListingIds
SELECT      [a].[ListingId]
FROM        [dbo].[Listings] [a] LEFT OUTER JOIN 
            [dbo].[AgencyCompany] [b] ON [a].[AgencyCompanyId] = [b].[AgencyCompanyId]
ORDER BY    UpdatedOn DESC

... select 的结果直接流入 insert 语句。

但在动态版本中:

DECLARE @ListingIds TABLE (ListingId INTEGER PRIMARY KEY)
INSERT INTO @ListingIds
EXEC sp_executesql N'SELECT      [a].[ListingId]
FROM        [dbo].[Listings] [a] LEFT OUTER JOIN 
            [dbo].[AgencyCompany] [b] ON [a].[AgencyCompanyId] = [b].[AgencyCompanyId]
ORDER BY    UpdatedOn DESC'

... EXEC sp_executesql 的结果累积到一种称为 的临时 table 参数 Table(你可以在你的执行计划中看到额外的步骤)。只有 这个临时 table 填充了数百万行之后 insert 语句才真正开始读取数据。这要慢得多。

如果您能以某种方式重构代码以将 语句推入 调用 EXEC sp_executesql 中,您也许能够避免这种性能下降。通过这样做,SELECT 语句的结果可以再次直接流式传输到 INSERT 语句。

参考文献:这是一篇有趣的文章,讨论了您面临的问题:The Hidden Costs of INSERT EXEC.

顺便说一句:如果您所做的只是在之后立即插入数据,则不需要 ORDER BY 子句。