为什么子查询和连接速度如此之慢

时间:2014-09-30 15:25:00

标签: sql sql-server join

我需要从BUNDLES表中选择具有多个SAP_STATE_ID值之一的行。这些值取决于是否应该导出相应的SAP状态。

此查询运行速度非常快(SAP_STATE_ID字段上有索引) -

SELECT b.* FROM BUNDLES b WHERE b.SAP_STATE_ID IN (2,3,5,6)

但是......我想动态获取ID列表,如下所示:

SELECT b.* FROM BUNDLES b 
WHERE b.SAP_STATE_ID IN 
(SELECT s.SAP_STATE_ID FROM SAP_STATES s WHERE s.EXPORT_TO_SAP = 1)

而且,这个查询突然花费了太多时间。我希望SQL Server首先运行子查询(它不依赖于主查询中的任何内容)然后像我的第一个例子一样运行整个事件。我试图重写它以使用连接而不是子查询:

SELECT b.* FROM BUNDLES b 
JOIN SAP_STATES s ON (s.SAP_STATE_ID = b.SAP_STATE_ID) 
WHERE s.EXPORT_TO_SAP = 1

但它的性能相同。它似乎正在为BUNDLES表的每一行运行子查询或类似的东西。我在阅读执行计划方面不是很熟练,但我试过了。它说81%的成本用于扫描BUNDLES的主键索引(我不知道为什么它应该这样做,有BUNDLE_ID字段定义为PRIMARY KEY,但它根本不会出现在查询中。 ..)

有没有人解释为什么SQL服务器如此"愚蠢"?有没有办法以良好的性能实现我想要的但不需要提供SAP_STATE_ID的静态列表?

两个表和相关索引的脚本 - http://mab.to/xbYiI0wKj

子查询版本的执行计划 - http://mab.to/8Qh6gpdYZ

具有联接的版本的查询计划 - http://mab.to/YCqeGCUbr

(出于某种原因,这两个计划看起来一样,都建议创建BUNDLES.SAP_STATE_ID索引,已经存在)

3 个答案:

答案 0 :(得分:3)

我很确定您的统计数据不在桌面上。如果你想让它匆忙工作,我会把查询写成:

SELECT b.*
  FROM SAP_STATES s 
 INNER LOOP JOIN BUNDLES b 
    ON s.SAP_STATE_ID = b.SAP_STATE_ID
 WHERE s.EXPORT_TO_SAP = 1

这会强制嵌套循环加入SAP_STATES BUNDLES

上的过滤器

答案 1 :(得分:2)

当您使用表(临时或物理)时,SQL引擎会针对它构建统计信息,因此对其中的行数有一个非常清晰的想法,这是它的最佳执行方法。另一方面,计算表(子查询)没有针对它的统计信息。

因此,虽然人类可能看似简单地推断其中的行数,但“愚蠢”的SQL引擎并不知道所有这些。现在,来到查询,WHERE s.EXPORT_TO_SAP = 1子句在这里创造了一个与众不同的世界。聚集索引在SAP_STATE_ID上进行排序和构建,但是为了另外检查WHERE子句,除了扫描整个表(在最终数据集中)之外别无选择!我敢打赌,如果不是聚集索引,如果SAP_STATE_ID列上有一个覆盖EXPORT_TO_SAP字段的非聚集覆盖索引,它可能已经完成了这个技巧。由于聚簇索引扫描通常不利于性能,我建议您采用以下方法:

SELECT s.SAP_STATE_ID 
into #Sap_State
FROM SAP_STATES s WHERE s.EXPORT_TO_SAP = 1

SELECT b.* FROM BUNDLES b 
join #Sap_State a on a.sap_state_id = b.sap_state_id

答案 2 :(得分:0)

由于某些原因导致mab.to出现问题,

我建议确保以下

table        index
sap_states   (export_to_sap, sap_state_id )
bundles      (sap_state_id)

select
      b.*
   from 
      sap_states ss
         join bundles b
            on ss.sap_state_id = b.sap_state_id
   where 
      ss.export_to_sap = 1