【问题标题】:Sql Server unexpected Cartesian ProductSql Server 意外的笛卡尔积
【发布时间】:2012-06-24 02:11:22
【问题描述】:

这是 SQL Server 2008。我有这两个表和一个连接:

DECLARE @EmployeeCrossDay TABLE
(
    EmployeeId UNIQUEIDENTIFIER, 
    WorkDate DATE, OtherStuff...
)

DECLARE @ET TABLE
(
    EmployeeId      UNIQUEIDENTIFIER,
    WorkDate        DATE, DifferentOtherStuff...
)

SELECT *
FROM @EmployeeCrossDay ecd
LEFT JOIN @ET et ON et.EmployeeId = ecd.EmployeeId
    AND et.WorkDate = ecd.WorkDate 

第一个表有 5,680 行(每个员工一个范围内的每个日期),第二个有 397 行(员工实际工作的每一天一个或多个)。 (因此,EmployeeId/WorkDate 是第一个表中的唯一组合,但不是第二个表中的唯一组合。)我的查询结果是正确的(每个员工的列表,其中包含他工作天数的一行或多行,每天一行他没有工作),但这大约需要 3 秒,我的个人资料显示了一个笛卡尔积(2,254,960 行)。有没有办法重组这个查询来防止完全交叉连接?

* 已编辑 * 按照建议添加主键后,Set Showplan_Text On 给了我这个:

  |--Compute Scalar(DEFINE:([Expr1007]=isnull(@ET.[StartTime] as [et].[StartTime],[Expr1010]), [Expr1008]=isnull(@ET.[EndTime] as [et].[EndTime],[Expr1010])))
       |--Nested Loops(Left Outer Join, OUTER REFERENCES:([et].[ServiceCallId]))
            |--Compute Scalar(DEFINE:([Expr1006]=isnull(@ET.[TypeId] as [et].[TypeId],(8)), [Expr1009]=isnull(@ET.[Interrupt] as [et].[Interrupt],($0.0000))))
            |    |--Nested Loops(Left Outer Join, WHERE:(@ET.[EmployeeId] as [et].[EmployeeId]=@EmployeeCrossDay.[EmployeeId] as [ecd].[EmployeeId] AND @ET.[WorkDate] as [et].[WorkDate]=@EmployeeCrossDay.[WorkDate] as [ecd].[WorkDate]))
            |         |--Compute Scalar(DEFINE:([Expr1010]=CONVERT_IMPLICIT(datetime,@EmployeeCrossDay.[WorkDate] as [ecd].[WorkDate],0)))
            |         |    |--Sort(ORDER BY:([ecd].[Number] ASC, [ecd].[WorkDate] ASC))
            |         |         |--Clustered Index Scan(OBJECT:(@EmployeeCrossDay AS [ecd]))
            |         |--Clustered Index Scan(OBJECT:(@ET AS [et]))
            |--Clustered Index Seek(OBJECT:([Snapper].[dbo].[ServiceCalls].[PK_Jobs] AS [sc]), SEEK:([sc].[ServiceCallId]=@ET.[ServiceCallId] as [et].[ServiceCallId]) ORDERED FORWARD)

我所说的“沿途显示笛卡尔积”的意思来自于设置统计配置文件。它显示的内容太多,无法在此处粘贴,但对于计划中的倒数第二项(聚集索引扫描),它在 Rows 下显示 2,254,960(我的逗号),在 Executes 下显示 5680。我是不是误读了,说我有笛卡尔积?

【问题讨论】:

  • 为每个表添加主键?
  • 我在第一个表中添加了PRIMARY KEY( EmployeeId, WorkDate ),在第二个表中添加了EmployeeTimeId UNIQUEIDENTIFIER PRIMARY KEY。我的执行时间降到了亚秒级,这很棒(谢谢!),但中间的笛卡尔积仍然存在!有点让人担心,嗯?
  • 您是否尝试过使用查询提示?在查询末尾添加“Option (HASH JOIN, MERGE JOIN)”。
  • 不确定您所说的“我的个人资料显示沿途的笛卡尔积”是什么意思。你能发布实际的执行计划吗?
  • @KellyCline - @ET 上没有可用于查找 EmployeeId 的索引,因此它正在执行 5680 次全表扫描。它可能会选择嵌套循环计划,因为它假设对表变量的操作只会发出一行。您可以添加OPTION (RECOMPILE) 提示以使其考虑实际基数。您也可以使用前导列EmployeeId 向表变量添加一个约束,以提供有用的索引。

标签: sql-server-2008


【解决方案1】:

我得到了三个很好的答案,但它们都是以 cmets 的形式出现的,所以我在这里发布了“答案”。

Aaron 建议我为每个表变量添加一个主键。我加了

EmployeeTimeId  UNIQUEIDENTIFIER PRIMARY KEY

致@ET 和

PRIMARY KEY (EmployeeId, WorkDate)

@EmployeeCrossDay 因为在这两种情况下,EmployeeId 都不是唯一的。

即使 EmpmloyeeTimeId 没有参与连接,仅此一项就可以将我的查询从 3 秒以上减少到 1 秒以下。但是,我的统计资料显示,执行计划中的一个步骤运行了 5680 次并达到了超过 220 万次行(5,680 * 397)。尽管响应时间可以接受,但我对此感到好奇。

Martin 然后建议我的@ET 密钥需要以 EmployeeId 开头。所以我把钥匙换成了

PRIMARY KEY( EmployeeId, EmployeeTimeId )

此时,执行计划显示之前的交叉连接减少到只有 397 行(而不是超过 200 万行),尽管它仍在为每个 @ET 行 (5680) 执行该过程,但它现在正在执行聚集索引查找而不是全表扫描。

一路上,Gordon 建议添加

OPTION( HASH JOIN, MERGE JOIN )

我删除了所有以前应用的索引,并且结果计划中的任何步骤都执行了一次以上。

所有三个建议都在一秒钟内返回了相同的数据,所以我给每一个都打了分(现在,谢谢)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多