包含所有列的空结果需要更长的时间

Mik*_*keH 5 performance sql-server entity-framework sql-server-2017 query-performance

我每 1 秒执行一次查询,大部分时间它不返回任何结果(由实体框架生成的 SQL):

SELECT TOP (5) 
   [Extent1].[ID] AS [ID],
   ***30 more columns***

   FROM ParentTable AS [Extent1]
   WHERE ([Extent1].[ImageTaken] = 1) AND ([Extent1].[ImageProjected] <> 1) AND ( EXISTS (SELECT 
      1 AS [C1]
      FROM ChildTable AS [Extent2]
      WHERE [Extent1].[ID] = [Extent2].[Parent_ID]
   ))
Run Code Online (Sandbox Code Playgroud)

上面的查询大约需要 400 毫秒。但是,如果我从结果中排除除 ID 之外的所有列,则大约需要 100 毫秒。

如果没有结果,为什么执行时间如此不同?我查看了执行计划,它们看起来完全相同(在今天之前我从未看过执行计划,因此请谨慎对待)。

我想包括所有列,但显然我只在有结果时才需要它们。

实际执行计划

所有列

仅 ID 列

编辑 更多细节:

  • 我正在使用 SQL Server Express 2017
  • SQL Server 在我的本地机器上
  • 父表有大约 100 万行
  • 子表有 ~40k 行

父表定义


    USE [DB]
    GO

    /****** Object:  Table [dbo].[Inspection_CapturedImageQueueItem]    Script Date: 1/8/2018 6:23:45 PM ******/
    SET ANSI_NULLS ON
    GO

    SET QUOTED_IDENTIFIER ON
    GO

    CREATE TABLE [dbo].[Inspection_CapturedImageQueueItem](
        [ID] [INT] IDENTITY(1,1) NOT NULL,
        [X] [FLOAT] NOT NULL,
        [Y] [FLOAT] NOT NULL,
        [Z] [FLOAT] NOT NULL,
        [rX] [FLOAT] NOT NULL,
        [rY] [FLOAT] NOT NULL,
        [rZ] [FLOAT] NOT NULL,
        [Priority] [INT] NOT NULL,
        [TimeTakenUTC] [datetime] NOT NULL,
        [ImageTaken] [bit] NOT NULL,
        [PartProgramInstance_Id] [INT] NULL,
        [PolarizerAngle1] [FLOAT] NOT NULL,
        [PolarizerAngle2] [FLOAT] NOT NULL,
        [ImageProjected] [bit] NOT NULL,
        [LaserTransform_X] [FLOAT] NOT NULL,
        [LaserTransform_Y] [FLOAT] NOT NULL,
        [LaserTransform_Z] [FLOAT] NOT NULL,
        [LaserTransform_rX] [FLOAT] NOT NULL,
        [LaserTransform_rY] [FLOAT] NOT NULL,
        [LaserTransform_rZ] [FLOAT] NOT NULL,
        [LaserPosition_X] [FLOAT] NOT NULL,
        [LaserPosition_Y] [FLOAT] NOT NULL,
        [LaserPosition_Z] [FLOAT] NOT NULL,
        [LaserPosition_rX] [FLOAT] NOT NULL,
        [LaserPosition_rY] [FLOAT] NOT NULL,
        [LaserPosition_rZ] [FLOAT] NOT NULL,
        [ProjectionError] [INT] NOT NULL,
        [LocalTransform_X] [FLOAT] NOT NULL,
        [LocalTransform_Y] [FLOAT] NOT NULL,
        [LocalTransform_Z] [FLOAT] NOT NULL,
        [LocalTransform_rX] [FLOAT] NOT NULL,
        [LocalTransform_rY] [FLOAT] NOT NULL,
        [LocalTransform_rZ] [FLOAT] NOT NULL,
        [IsHighAngleOfIncidence] [bit] NOT NULL,
     CONSTRAINT [PK_dbo.Inspection_CapturedImageQueueItem] PRIMARY KEY CLUSTERED
    (
        [ID] ASC
    )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
    ) ON [PRIMARY]
    GO

    ALTER TABLE [dbo].[Inspection_CapturedImageQueueItem] ADD  DEFAULT ((0)) FOR [PolarizerAngle1]
    GO

    ALTER TABLE [dbo].[Inspection_CapturedImageQueueItem] ADD  DEFAULT ((0)) FOR [PolarizerAngle2]
    GO

    ALTER TABLE [dbo].[Inspection_CapturedImageQueueItem] ADD  DEFAULT ((0)) FOR [ImageProjected]
    GO

    ALTER TABLE [dbo].[Inspection_CapturedImageQueueItem] ADD  DEFAULT ((0)) FOR [LaserTransform_X]
    GO

    ALTER TABLE [dbo].[Inspection_CapturedImageQueueItem] ADD  DEFAULT ((0)) FOR [LaserTransform_Y]
    GO

    ALTER TABLE [dbo].[Inspection_CapturedImageQueueItem] ADD  DEFAULT ((0)) FOR [LaserTransform_Z]
    GO

    ALTER TABLE [dbo].[Inspection_CapturedImageQueueItem] ADD  DEFAULT ((0)) FOR [LaserTransform_rX]
    GO

    ALTER TABLE [dbo].[Inspection_CapturedImageQueueItem] ADD  DEFAULT ((0)) FOR [LaserTransform_rY]
    GO

    ALTER TABLE [dbo].[Inspection_CapturedImageQueueItem] ADD  DEFAULT ((0)) FOR [LaserTransform_rZ]
    GO

    ALTER TABLE [dbo].[Inspection_CapturedImageQueueItem] ADD  DEFAULT ((0)) FOR [LaserPosition_X]
    GO

    ALTER TABLE [dbo].[Inspection_CapturedImageQueueItem] ADD  DEFAULT ((0)) FOR [LaserPosition_Y]
    GO

    ALTER TABLE [dbo].[Inspection_CapturedImageQueueItem] ADD  DEFAULT ((0)) FOR [LaserPosition_Z]
    GO

    ALTER TABLE [dbo].[Inspection_CapturedImageQueueItem] ADD  DEFAULT ((0)) FOR [LaserPosition_rX]
    GO

    ALTER TABLE [dbo].[Inspection_CapturedImageQueueItem] ADD  DEFAULT ((0)) FOR [LaserPosition_rY]
    GO

    ALTER TABLE [dbo].[Inspection_CapturedImageQueueItem] ADD  DEFAULT ((0)) FOR [LaserPosition_rZ]
    GO

    ALTER TABLE [dbo].[Inspection_CapturedImageQueueItem] ADD  DEFAULT ((0)) FOR [ProjectionError]
    GO

    ALTER TABLE [dbo].[Inspection_CapturedImageQueueItem] ADD  DEFAULT ((0)) FOR [LocalTransform_X]
    GO

    ALTER TABLE [dbo].[Inspection_CapturedImageQueueItem] ADD  DEFAULT ((0)) FOR [LocalTransform_Y]
    GO

    ALTER TABLE [dbo].[Inspection_CapturedImageQueueItem] ADD  DEFAULT ((0)) FOR [LocalTransform_Z]
    GO

    ALTER TABLE [dbo].[Inspection_CapturedImageQueueItem] ADD  DEFAULT ((0)) FOR [LocalTransform_rX]
    GO

    ALTER TABLE [dbo].[Inspection_CapturedImageQueueItem] ADD  DEFAULT ((0)) FOR [LocalTransform_rY]
    GO

    ALTER TABLE [dbo].[Inspection_CapturedImageQueueItem] ADD  DEFAULT ((0)) FOR [LocalTransform_rZ]
    GO

    ALTER TABLE [dbo].[Inspection_CapturedImageQueueItem] ADD  DEFAULT ((0)) FOR [IsHighAngleOfIncidence]
    GO

    ALTER TABLE [dbo].[Inspection_CapturedImageQueueItem]  WITH CHECK ADD  CONSTRAINT [FK_dbo.Inspection_CapturedImageQueueItem_dbo.Inspection_PartProgramInstance_PartProgramInstance_Id] FOREIGN KEY([PartProgramInstance_Id])
    REFERENCES [dbo].[Inspection_PartProgramInstance] ([Id])
    GO

    ALTER TABLE [dbo].[Inspection_CapturedImageQueueItem] CHECK CONSTRAINT [FK_dbo.Inspection_CapturedImageQueueItem_dbo.Inspection_PartProgramInstance_PartProgramInstance_Id]
    GO

Run Code Online (Sandbox Code Playgroud)

子表定义


USE [DB]
GO

/****** Object:  Table [dbo].[Inspection_CapturedImageItem]    Script Date: 1/11/2018 9:01:34 AM ******/
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE TABLE [dbo].[Inspection_CapturedImageItem](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [PolarizerAngle] [float] NOT NULL,
    [ImageRequest_ID] [int] NULL,
    [TimeTakenUTC] [datetime] NOT NULL,
    [ProjectorNumber] [int] NOT NULL,
    [AngleOfIncidence] [float] NOT NULL,
    [MirrorRx] [float] NOT NULL,
    [MirrorRy] [float] NOT NULL,
 CONSTRAINT [PK_dbo.Inspection_CapturedImageItem] PRIMARY KEY CLUSTERED 
(
    [ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

ALTER TABLE [dbo].[Inspection_CapturedImageItem] ADD  DEFAULT ('1900-01-01T00:00:00.000') FOR [TimeTakenUTC]
GO

ALTER TABLE [dbo].[Inspection_CapturedImageItem] ADD  DEFAULT ((0)) FOR [ProjectorNumber]
GO

ALTER TABLE [dbo].[Inspection_CapturedImageItem] ADD  DEFAULT ((0)) FOR [AngleOfIncidence]
GO

ALTER TABLE [dbo].[Inspection_CapturedImageItem] ADD  DEFAULT ((0)) FOR [MirrorRx]
GO

ALTER TABLE [dbo].[Inspection_CapturedImageItem] ADD  DEFAULT ((0)) FOR [MirrorRy]
GO

ALTER TABLE [dbo].[Inspection_CapturedImageItem]  WITH CHECK ADD  CONSTRAINT [FK_dbo.Inspection_CapturedImageItem_dbo.Inspection_CapturedImageQueueItem_ImageRequest_ID] FOREIGN KEY([ImageRequest_ID])
REFERENCES [dbo].[Inspection_CapturedImageQueueItem] ([ID])
GO

ALTER TABLE [dbo].[Inspection_CapturedImageItem] CHECK CONSTRAINT [FK_dbo.Inspection_CapturedImageItem_dbo.Inspection_CapturedImageQueueItem_ImageRequest_ID]
GO
Run Code Online (Sandbox Code Playgroud)

指数

在此处输入图片说明

Joe*_*ish 3

该扫描所遇到的性能问题是一个神秘的问题。一般来说,我希望看到不返回任何行的扫描的 CPU 时间非常相似。没有任何计算列,并且逻辑读取计数是相同的。毫不奇怪,我无法在我的机器上重现该问题。也许它与 Express 版本有某种关系,但这是一个疯狂的猜测。

\n\n

我可以为您提供解决性能问题的方法。查看实际计划,我们可以看到该Inspection_CapturedImageItem表只有一个不同的值ImageRequest_ID。从该表到该表的连接Inspection_CapturedImageQueueItem是针对该表的主键和聚集键,因此使用Inspection_CapturedImageItem外部表的嵌套循环连接计划可能会非常有效。这肯定比扫描整个Inspection_CapturedImageItem表更有效率。

\n\n

那么为什么查询优化器选择合并连接呢?聚集索引扫描成本因查询部分引入的行目标而降低。TOP (5)事实上,通过一些数学计算,我可以为您提供过滤器的近似基数估计[Extent1].[ImageTaken] = 1) AND ([Extent1].[ImageProjected] <> 1)

\n\n
\n

优化器单位的扫描成本 = 0.0031895 + LEAST(1, ROW_GOAL /\n CARDINALITY_ESTIMATE) * (FULL_SCAN_COST \xe2\x80\x93 0.0031895)

\n\n

0.0031895 + ( 5 / 基数估计) * (23.4928 + 1.11635 \xe2\x80\x93 0.0031895) = 0.0453535

\n\n

基数估计 = (5 * (23.4928 + 1.11635 \xe2\x80\x93 0.0031895)) /\n (0.0453535 - 0.0031895)

\n\n

基数估计 = 2918

\n
\n\n

SQL Server 认为大约有 2918 行与您的过滤器匹配Inspection_CapturedImageQueueItem。您的查询只需要前五个结果。优化器假设它不必为了这五行而扫描整个表。毕竟表里有2918个。不幸的是,您在这里过滤表中没有匹配行的谓词。对于行目标来说,这是最坏的情况。您支付扫描的全部费用,但查询优化器会创建一个计划,假设您只需要扫描表的 0.3%。

\n\n

有两种方法可以解决这个问题:您可以为优化器提供更好的信息,或者可以强制优化器使用您认为更好的计划。如果可能的话,通常首选第一个选项。这里没有足够的详细信息来深入研究,但您可能会从ImageTakenImageProjected列的统计信息更新中受益。更新theImageRequest_ID列的统计数据Inspection_CapturedImageItem也可能是有益的。查询优化器估计有 42 个不同的行,但实际上只有 1 个。

\n\n

如果您确信自己理解为什么会得到一个糟糕的计划,并且数据不会随着时间的推移而改变,您可以考虑查询提示。禁用行目标的使用提示HINT(\'DISABLE_OPTIMIZER_ROWGOAL\')可能是一个不错的选择。通过该提示,您将指示查询优化器不要优化查询以尽快返回前五行。相反,将创建一个计划来有效地返回结果集中的所有行。即使基数估计很差,Inspection_CapturedImageItem您也可能最终得到一个嵌套循环连接,该连接应该比执行扫描的查询更快。

\n