【问题标题】:Why would a SELECT statement be 45% of the execution plan cost in SQL Server 2008?为什么 SELECT 语句会占 SQL Server 2008 中执行计划成本的 45%?
【发布时间】:2010-12-20 10:03:09
【问题描述】:

我有一个查询,我从 5 个左外连接表中的每一个中选择几列。

我在 SQL Server 2008 中做了一个执行计划,基本上对所有连接的表都进行了表扫描,但它们的成本都是 0% - 我假设是因为这些表中的记录不多.

然后在执行计划的最后2步(所有表的最终合并连接和实际的SELECT语句),它说55%的成本是合并连接,45%的成本是选择.

这对我来说似乎很奇怪...为什么最后 2 个“将所有内容整合在一起”步骤的成本如此之高?我认为所有这些表扫描或排序步骤的成本都会更高。

我正在尝试从所有这些表中获取一个“汇总”记录...也许我在左加入所有内容时采取了错误的方法?

用 SQL 更新

SELECT
/* Names */
NM.EMPLID, NM.NAME_PREFIX, NM.LAST_NAME, NM.FIRST_NAME, NM.MIDDLE_NAME, NM.NAME_SUFFIX,
/* Directory Info */
DIR_PERSON.BIRTH_DT,
/* PERSDATA */
PERS.SEX, PERS.HIGHEST_EDUC_LVL,
/* DIVERS.ETHNIC */
ETHNIC.ETHNIC_GRP_CD,
/* TENURE */
TENURE.EMPLID, TENURE.TENURE_STATUS, TENURE.EG_GRANTED_DT, TENURE.EG_TENURE_HOME, 
TENURE.EG_TRACK_HIRE_DT, TENURE.EG_MAND_REVW_DT, TENURE.CODE,
/* VISA */
VISA.VISA_PERMIT_TYPE

FROM NAMES NM

/* ----- Table Joins ----- */
/* Directory Join */
LEFT OUTER JOIN DIR_PERSON ON DIR_PERSON.ID = NM.EMPLID

/* PERS_DATA Join */
LEFT OUTER JOIN PERS ON PERS.EMPLID = NM.EMPLID
AND PERS.EFFDT =(   SELECT MAX(PERS_CURRENT.EFFDT) FROM PERS_CURRENT 
                    WHERE PERS.EMPLID = PERS_CURRENT.EMPLID 
                    AND PERS_CURRENT.EFFDT <= GETDATE())
/* ETHNIC Join */                       
LEFT OUTER JOIN  ETHNIC ON ETHNIC.EMPLID = NM.EMPLID
AND ETHNIC.PRIMARY_INDICATOR = 'Y'

/* TENURE Join */
LEFT OUTER JOIN TENURE ON TENURE.EMPLID = NM.EMPLID

/* VISA Join */
LEFT OUTER JOIN VISA ON VISA.EMPLID = NM.EMPLID
AND VISA.EFFDT = (  SELECT MAX(VISA_CURRENT.EFFDT) FROM VISA_CURRENT
                    WHERE VISA.EMPLID = VISA_CURRENT.EMPLID 
                    AND VISA_CURRENT.EFFDT <= GETDATE())

/* ----- End Table Joins ----- */       

WHERE   NM.NAME_TYPE = 'PRI' 
    AND NM.EFFDT = (SELECT MAX(NM_CURRENT.EFFDT) FROM NM_CURRENT 
                    WHERE NM.EMPLID = NM_CURRENT.EMPLID 
                    AND NM.NAME_TYPE = NM_CURRENT.NAME_TYPE 
                    AND NM_CURRENT.EFFDT <= GETDATE());

【问题讨论】:

  • 你能发布.sqlplan(即导出的查询执行计划)吗?
  • SET SHOWPLAN_XML 在我指向的服务器上不受支持(我猜它是在 2005 年之前的?),所以我无法得到一个看起来不错的导出计划。

标签: sql sql-server sql-execution-plan


【解决方案1】:

加快思路

我已经重构了您的查询(我没有测试,所以可能存在拼写错误)以摆脱子查询。在这里,您首先获取所有最大项目(1 x empl 数量),然后运行主选择(1 x empl 数量)。这会将您的查询从 O(N^3) 更改为 O(N),因此它应该更快。

我只做了其中两个,从这个例子中应该清楚第三个:

WITH mVisa AS
(
 SELECT MAX(VISA_CURRENT.EFFDT) as max, VISA_CURRENT.EMPID as EMPLID
 FROM VISA_CURRENT
 WHERE VISA_CURRENT.EFFDT <= GETDATE()
 GROUP BY VISA_CURRENT.EMPLID
), mPers AS
(
 SELECT MAX(PERS_CURRENT.EFFDT) as max, PERS_CURRENT.EMPLID
 FROM PERS_CURRENT
 AND PERS_CURRENT.EFFDT <= GETDATE())
 GROUP BY PERS_CURRENT.EMPLID
)
SELECT
/* Names */
NM.EMPLID, NM.NAME_PREFIX, NM.LAST_NAME, NM.FIRST_NAME, NM.MIDDLE_NAME, NM.NAME_SUFFIX,
/* Directory Info */
DIR_PERSON.BIRTH_DT,
/* PERSDATA */
PERS.SEX, PERS.HIGHEST_EDUC_LVL,
/* DIVERS.ETHNIC */
ETHNIC.ETHNIC_GRP_CD,
/* TENURE */
TENURE.EMPLID, TENURE.TENURE_STATUS, TENURE.EG_GRANTED_DT, TENURE.EG_TENURE_HOME, 
TENURE.EG_TRACK_HIRE_DT, TENURE.EG_MAND_REVW_DT, TENURE.CODE,
/* VISA */
VISA.VISA_PERMIT_TYPE

FROM NAMES NM

/* ----- Table Joins ----- */
/* Directory Join */
LEFT OUTER JOIN DIR_PERSON ON DIR_PERSON.ID = NM.EMPLID

/* PERS_DATA Join */
LEFT JOIN mPers ON NM.EMPLID = mPers.EMPLID 
LEFT OUTER JOIN PERS ON PERS.EMPLID = NM.EMPLID
AND PERS.EFFDT = mPers.max
/* ETHNIC Join */                                       
LEFT OUTER JOIN  ETHNIC ON ETHNIC.EMPLID = NM.EMPLID
AND ETHNIC.PRIMARY_INDICATOR = 'Y'

/* TENURE Join */
LEFT OUTER JOIN TENURE ON TENURE.EMPLID = NM.EMPLID

/* VISA Join */
LEFT JOIN mVisa ON NM.EMPLID = mVisa.EMPLID
LEFT OUTER JOIN VISA ON VISA.EMPLID = NM.EMPLID
AND VISA.EFFDT = mVisa.max

/* ----- End Table Joins ----- */       

WHERE   NM.NAME_TYPE = 'PRI' 
        AND NM.EFFDT = (SELECT MAX(NM_CURRENT.EFFDT) FROM NM_CURRENT 
                                        WHERE NM.EMPLID = NM_CURRENT.EMPLID 
                                        AND NM.NAME_TYPE = NM_CURRENT.NAME_TYPE 
                                        AND NM_CURRENT.EFFDT <= GETDATE());

【讨论】:

  • 感谢 Hogan - 我指向的服务器似乎不支持 SHOWPLAN_XML,所以现在我担心它也不支持 CTE。我将在星期一尽快尝试...否则我将尝试与维护此数据库的人交谈并希望优化一些东西...
  • SQL 2008?然后它会起作用—— showplan_xml 需要 dbo_owner 或其他一些权利。 CTE 现在是语言的一部分,应该没问题。
  • 如果您是 2005 年前的,那么您可以插入 #tempname 并链接到这些表。
  • 啊酷,那我试试看……看起来就像 CTE!你能告诉我还有很多东西要学吗? ;) 谢谢霍根!
  • 服务器是 SQL Server 2000,但临时表似乎工作得很好。 SELECT 仍然显示为 49% 的成本,但是使用这种“首先获取表中的最大值”方法,查询速度要快得多。感谢您的帮助!
【解决方案2】:

正如 Cade 所说,首先检查您的索引。

如果索引到位,请验证您的统计数据是否是最新的。

如果这两个问题都存在,请考虑将您的子查询重构为一个或多个 CTE,然后按照相关标准加入它们。这不是灵丹妙药,但根据我的经验,CTE 通常比子查询表现更好。

【讨论】:

  • 没错!请参阅我的评论 Brook -- O(N) 而不是 O(N^2) 或更糟
  • +1 因为不像我那么懒惰并且实际上重构了查询:)
【解决方案3】:

45% 的小东西仍然是 45%。没有看到更多细节很难说,但我发现在插入聚集(在非 IDENTITY 列上)索引表或具有大量索引的表时,最后阶段非常昂贵。

所有这些表扫描 - 没有索引吗?

【讨论】:

  • 这只是一个直接的选择,将这些表中的数据组合到一个汇总行中作为一个键(在本例中是一个员工)。我没有数据库的管理员权限,创建数据库时我也不在身边,但是当我通过 SQL Server 查看所有这些表时,我在这些表中都看不到任何键或索引……但我不确定 SQL Server 是否向我展示了真相。
  • 我完全去过那里。您可能没有良好的统计数据,因此执行计划可能不准确 - 将您的所有信息汇总并与 DBA 联系。
【解决方案4】:

如果您包含代码会很有帮助,但如果您有 GROUP BYORDER BY,例如,这将大大增加查询。

如果最终选择的是一个大表,而其他的不仅很小而且在主表中没有真正使用过,那么你需要在查询的某些部分达到 100%,即使它是一个简单的部分。

【讨论】:

    【解决方案5】:
    WHERE   NM.NAME_TYPE = 'PRI' 
            AND NM.EFFDT = (SELECT MAX(NM_CURRENT.EFFDT) FROM NM_CURRENT 
                                            WHERE NM.EMPLID = NM_CURRENT.EMPLID 
                                            AND NM.NAME_TYPE = NM_CURRENT.NAME_TYPE 
                                            AND NM_CURRENT.EFFDT <= GETDATE());
    

    你的 45% 在这里。如果您在 MN.NAME_TYPE 和 NM.EFFDT 上创建索引,您将看到 45% 的下降。

    它可能会也可能不会在主选择的成本中包含这个子查询——如果是,那么这就是你的问题所在。请记住,它必须为每一行重新运行该查询。

    查看其他评论以了解对联接的重构。

    【讨论】:

    • 我越看这个,我就越想知道——子查询在做什么……你有 3 个——它们在每一行运行……这必须是是什么减慢了查询速度。
    • 关于联接或 WHERE 的任何子查询都是因为我需要这些表中特定人员的最新记录。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-21
    • 1970-01-01
    • 1970-01-01
    • 2014-08-23
    • 1970-01-01
    • 2013-06-14
    相关资源
    最近更新 更多