【问题标题】:Why is this join taking so long?为什么这个加入需要这么长时间?
【发布时间】:2011-12-07 16:01:20
【问题描述】:

我在我的数据库服务器上运行以下查询,但运行大约需要 30 秒,我不知道这是为什么。

SELECT *
FROM [dbo].[PackageInstance] AS packInst
  INNER JOIN [dbo].[PackageDefinition] AS packageDef 
    ON packInst.[PackageDefinitionID] = packageDef.[PackageDefinitionID]
  LEFT OUTER JOIN [dbo].[PackageInstanceContextDef] AS contextDef 
   ON packInst.[PackageInstanceID] = contextDef.[PackageInstanceID]

这产生了以下执行计划,对我来说看起来不错....所以我不明白为什么在结果数据只有 100,000 条记录的情况下执行需要这么长时间(这应该是在为 SQL Server 停放)。

有什么想法会导致这么长的执行时间吗?

我查看了 Profiler 中的查询以查看其上的统计信息,它们如下:

CPU - 4711
Reads - 744453
Writes - 9
Duration - 26329

以下是表定义:

CREATE TABLE [dbo].[PackageDefinition](
    [PackageDefinitionID] [int] IDENTITY(1,1) NOT NULL,
    [ts] [timestamp] NOT NULL,
    [ProgramID] [int] NULL,
    [VendorID] [int] NULL,
    [PackageExecutionTypeID] [int] NULL,
    [PackageDefinitionStatusID] [int] NOT NULL,
    [IsInternal] [bit] NOT NULL,
    [Name] [dbo].[D_Name] NOT NULL,
    [Description] [dbo].[D_Description] NOT NULL,
    [CreatedDate] [datetime] NOT NULL,
    [PublishedDate] [datetime] NULL,
    [OwnerUserGuid] [uniqueidentifier] NOT NULL,
    [ProcessDefinitionMainID] [int] NULL,
    [KeyInfoHtml] [nvarchar](max) NULL,
    [DescriptionHtml] [nvarchar](max) NULL,
    [WhatToExpectHtml] [nvarchar](max) NULL,
    [BestPracticesHtml] [nvarchar](max) NULL,
    [RecommendedJourneysHtml] [nvarchar](max) NULL,
    [RequiresSLAAgreement] [bit] NOT NULL,
    [SLAFileAssetID] [int] NULL,
    [ImageDataID] [int] NULL,
    [VideoHtml] [nvarchar](max) NULL,
    [VideoAssetID] [int] NULL,
    [UseMapCosts] [bit] NOT NULL,
    [CostMin] [money] NOT NULL,
    [CostMax] [money] NOT NULL,
    [LandingPageVisitCount] [int] NOT NULL,
    [IsDeleted] [dbo].[D_IsDeleted] NOT NULL,
    [CreatedByUserGuid] [uniqueidentifier] NOT NULL,
    [OrderHtml] [nvarchar](max) NULL,
 CONSTRAINT [PK_PackageDefinition] PRIMARY KEY CLUSTERED 
(
    [PackageDefinitionID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

CREATE TABLE [dbo].[PackageInstance](
    [PackageInstanceID] [int] IDENTITY(1,1) NOT NULL,
    [ts] [timestamp] NOT NULL,
    [PackageDefinitionID] [int] NOT NULL,
    [PackageStatusID] [int] NOT NULL,
    [Name] [dbo].[D_Description] NOT NULL,
    [CampaignID] [int] NULL,
    [MarketingPlanID] [int] NULL,
    [CountryID] [int] NULL,
    [DateEntered] [datetime] NULL,
    [DateExecuted] [datetime] NULL,
    [ProcessID] [int] NULL,
    [OrderedByUserGuid] [uniqueidentifier] NULL,
    [RequestedByUserGuid] [uniqueidentifier] NULL,
    [SLAEndDate] [datetime] NULL,
 CONSTRAINT [PK_PackageInstance] PRIMARY KEY CLUSTERED 
(
    [PackageInstanceID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

CREATE TABLE [dbo].[PackageInstanceContextDef](
    [PackageInstanceContextDefID] [int] IDENTITY(1,1) NOT NULL,
    [ts] [timestamp] NOT NULL,
    [PackageInstanceID] [int] NOT NULL,
    [ContextObjectDefID] [int] NOT NULL,
    [EnteredFieldValue] [varchar](max) NULL,
    [SelectedListValueID] [int] NULL,
    [AssetIdsString] [nvarchar](max) NULL,
    [SelectedListValueIdsString] [nvarchar](max) NULL,
    [ContextObjectFieldName] [nvarchar](30) NOT NULL,
 CONSTRAINT [PK_PackageInstanceContextDef] PRIMARY KEY CLUSTERED 
(
    [PackageInstanceContextDefID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

【问题讨论】:

  • 其他进程是否存在 CPU/磁盘争用?
  • PackageInstanceContextDef 上索引扫描的执行计数是多少? (它在工具提示中)
  • Profiler 中有任何排序警告吗?如果您尝试read uncommitted 隔离级别会怎样?可能会遇到阻塞。 @Andomar - 这将是 1. Merge Join 只处理每个输入一次。
  • 744,453 次读取是 > 5GB 的数据。这些行有多宽?也可能有严重的碎片?
  • @Penfold - 是所有那些max 列都在扼杀性能。 There might be something in this article of relevance

标签: sql-server sql-server-2008 join indexing sql-execution-plan


【解决方案1】:

删除SELECT *中的*

它将始终扫描,因为您要求所有列。你有聚集索引吗?

【讨论】:

  • 不幸的是,我要做的是找出为什么我的 Linq-to-Entities 查询需要这么长时间,所以我无法控制我可以选择哪些字段,所以我需要它们!! :( 在生产版本中,我会删除星号并按名称添加列。每个表的主键上都有聚集索引。
  • @Penfold:您要求我们帮助进行测试查询然后有 SELECT *...?
  • 是的,如果您愿意,我可以列出每一列,但其中有一些,我认为这对查看没有帮助,因为我不想全部列出。
  • 根据定义,您不需要所有列,因为您有多个列中相同数据的连接。 SELECT * 是一种 SQL 反模式,不应在生产代码中使用。如果您的 ORM 无法处理,那么它是一个糟糕的工具,不应该使用。
  • @HLGEM 我完全同意您在生产代码中使用 SELECT * 的观点,并且 ORM 应该能够限制返回的数据。但是,我在这里说的不是我想使用 SELECT * 或者实体框架无法解决它需要的内容,而是我使用 SELECT * 使语句更易于查看,因为会有关于框架返回的 50 列。
【解决方案2】:

答案原来是@MartinSmith 建议的。因为 PackageDefinition 表包含大约 8 个 NVARCHAR(MAX) 列,所以当创建结果连接并且超过 100k 行时,这会导致 varchar(max) 值被一遍又一遍地重新读取,并且它们存在于行外页。因此需要大量的逻辑读取。

感谢大家的支持,只需要想办法让实体框架产生我想要的查询。

【讨论】:

  • 你试过我的答案了吗?很想知道在排序操作之后检索大列是否会以访问PackageDefinition 两次为代价来改善情况?
【解决方案3】:

如果添加以下索引会发生什么...

CREATE NONCLUSTERED INDEX ix ON  PackageDefinition(PackageDefinitionID)

...并尝试以下方法来减少进入排序的数据的宽度?

SELECT packInst.*,
       packageDef2.*,
       contextDef.*
FROM   [dbo].[PackageInstance] AS packInst
       INNER MERGE JOIN [dbo].[PackageDefinition] AS packageDef
         ON packInst.[PackageDefinitionID] = packageDef.[PackageDefinitionID]
       LEFT OUTER MERGE JOIN [dbo].[PackageInstanceContextDef] AS contextDef
        ON packInst.[PackageInstanceID] = contextDef.[PackageInstanceID]
       INNER MERGE JOIN [dbo].[PackageDefinition] AS packageDef2
        ON packageDef.[PackageDefinitionID] = packageDef2.[PackageDefinitionID]  

当然,* 不应该被使用,因为即使你需要所有列,你也绝对不需要与JOIN 结果相同的列的两倍,但这只是为了保持原始查询的语义。

【讨论】:

    猜你喜欢
    • 2011-08-27
    • 2012-08-09
    • 2021-07-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多