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 秒 运行 和计划..
- 所以我运行正常查询的时候,还可以,很快
- 当我
INSERT INTO
需要 45 奇数秒。
- 无论是否有参数都一样
- 无论是否使用
OPTION (RECOMPILE)
都一样
- 为什么要使用
sp_executesql
而不是原始的 sql 语句?因为我们有一个 堆 的动态 WHERE / AND
语句开始使事情 hard/not-that-perf-nice.
科技..
- 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
子句。
当我尝试将 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 秒 运行 和计划..
- 所以我运行正常查询的时候,还可以,很快
- 当我
INSERT INTO
需要 45 奇数秒。 - 无论是否有参数都一样
- 无论是否使用
OPTION (RECOMPILE)
都一样 - 为什么要使用
sp_executesql
而不是原始的 sql 语句?因为我们有一个 堆 的动态WHERE / AND
语句开始使事情 hard/not-that-perf-nice.
科技..
- 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
子句。