T-SQL - 错误的查询执行计划行为

时间:2013-08-11 12:37:51

标签: performance query-optimization sql-server-2012

在数据库上生成负载后,我们的一个查询降级了。

我们的查询是3个表之间的连接:

  1. Base表,包含10 M行。
  2. EventPerson表,其中包含5000行。
  3. EventPerson788这是空的。
  4. 似乎优化器扫描EventPerson上的索引而不是seek,这是用于复制问题的脚本:

    --Create Tables 
    CREATE TABLE [dbo].[BASE](
            [ID] [bigint] NOT NULL,
            [IsActive] BIT
           PRIMARY KEY CLUSTERED ([ID] ASC) 
    )ON [PRIMARY]
    GO
    
    CREATE TABLE [dbo].[EventPerson](
        [DUID] [bigint] NOT NULL,
        [PersonInvolvedID] [bigint] NULL,
    
    PRIMARY KEY CLUSTERED ([DUID] ASC)
    ) ON [PRIMARY]
    GO
    
    CREATE NONCLUSTERED INDEX [EventPerson_IDX] ON [dbo].[EventPerson]
    (
        [PersonInvolvedID] ASC
    )
    
    CREATE TABLE [dbo].[EventPerson788](
        [EntryID] [bigint] NOT NULL,
        [LinkedSuspectID] [bigint] NULL,
        [sourceid] [bigint] NULL,
    
        PRIMARY KEY CLUSTERED ([EntryID] ASC)
    ) ON [PRIMARY]
    GO
    
    ALTER TABLE [dbo].[EventPerson788] WITH CHECK 
    ADD CONSTRAINT [FK7A34153D3720F84A] 
    FOREIGN KEY([sourceid]) REFERENCES [dbo].[EventPerson] ([DUID])
    GO
    
    ALTER TABLE [dbo].[EventPerson788] CHECK CONSTRAINT [FK7A34153D3720F84A]
    GO
    
    CREATE NONCLUSTERED INDEX [EventPerson788_IDX] 
    ON [dbo].[EventPerson788] ([LinkedSuspectID] ASC)
    GO
    
    --POPOLATE BASE TABLE 
    DECLARE @I BIGINT=1 
    WHILE (@I<10000000) 
        BEGIN
        begin transaction
        INSERT INTO BASE(ID) VALUES(@I) 
        SET @I+=1
            if (@I%10000=0 )
            begin
        commit;
            end;
    
        END
    go
    
    --POPOLATE EventPerson TABLE 
    DECLARE @I BIGINT=1 
    WHILE (@I<5000) 
        BEGIN
        BEGIN TRANSACTION
        INSERT INTO EventPerson(DUID,PersonInvolvedID) VALUES(@I,(SELECT TOP 1 ID FROM BASE ORDER BY NEWID())) 
            SET @I+=1
            IF(@I%10000=0 )
                COMMIT TRANSACTION ;
        END
    
    GO 
    

    这个查询:

    select 
        count(EventPerson.DUID) 
    from 
        EventPerson  
    inner loop join 
        Base on EventPerson.DUID = base.ID 
    left outer join 
        EventPerson788 on EventPerson.DUID = EventPerson788.sourceid 
    where 
        (EventPerson.PersonInvolvedID = 37909 or 
         EventPerson788.LinkedSuspectID = 37909) 
        AND BASE.IsActive = 1
    

    您是否知道为什么优化器决定使用索引扫描而不是索引搜索?

    已完成的解决方法:

    • 分析表并构建统计信息。
    • 重建指数。
    • 尝试FORCESEEK提示

    以上都没有说服优化器在EventPerson上运行索引查找并在基表上进行搜索。

    感谢您的帮助。

2 个答案:

答案 0 :(得分:2)

由于or条件和针对EventPerson788的外部联接,扫描就在那里。

EventPerson 时,当EventPerson.PersonInvolvedID = 37909中存在EventPerson788的行时,它会从EventPerson788.LinkedSuspectID = 37909返回行。最后一部分意味着必须针对连接检查EventPerson中的每一行。

查询优化器无法使用EventPerson788为空的事实,因为保存查询计划以便以后EventPerson788中可能存在匹配的行时重复使用。

更新

您可以使用union all重写查询,而不是使用EventPerson进行搜索。

select count(EventPerson.DUID) 
from 
  (
    select EventPerson.DUID
    from EventPerson
    where EventPerson.PersonInvolvedID = 1556 and
          not exists (select * 
                      from EventPerson788 
                      where EventPerson788.LinkedSuspectID = 1556) 
    union all
    select EventPerson788.sourceid
    from EventPerson788
    where EventPerson788.LinkedSuspectID = 1556
  ) as EventPerson  
  inner join BASE  
    on EventPerson.DUID=base.ID 
where
  BASE.IsActive=1

enter image description here

答案 1 :(得分:1)

嗯,您要求SQL Server 计算EventPerson表的行数 - 那么为什么您希望搜索优于此处的扫描?

对于COUNT,SQL Server优化器几乎总是使用扫描 - 它毕竟需要对行进行计数 - 所有这些...它将执行群集索引扫描,如果没有其他非可空列被索引。

如果你有一个小的,不可为空的列的索引(例如在ID INT或类似的东西上),它可能会对该索引进行扫描(更少的数据要读取以计算所有行) )。

但总的来说:搜索非常适合选择一行或几行 - 但如果您正在处理所有行,这很糟糕(比如计数)

如果您正在使用AdventureWorks示例数据库,则可以轻松地观察到此行为。

COUNT(*)表上执行Sales.SalesOrderDetail时有超过120000行,如下所示:

SELECT COUNT(*) FROM Sales.SalesOrderDetail

然后你会在IX_SalesOrderDetail_ProductID上获得一个索引扫描 - 它只是不会为超过120000个条目进行搜索而获得回报!

但是,如果对较小的数据集执行相同的操作,请执行以下操作:

SELECT COUNT(*) FROM Sales.SalesOrderDetail
WHERE ProductID = 897

然后你会从所有这些行中找回2行 - 而SQL Server现在会在同一个索引上使用索引搜索