在数据库上生成负载后,我们的一个查询降级了。
我们的查询是3个表之间的连接:
Base
表,包含10 M行。 EventPerson
表,其中包含5000行。 EventPerson788
这是空的。 似乎优化器扫描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
您是否知道为什么优化器决定使用索引扫描而不是索引搜索?
已完成的解决方法:
以上都没有说服优化器在EventPerson
上运行索引查找并在基表上进行搜索。
感谢您的帮助。
答案 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
答案 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现在会在同一个索引上使用索引搜索。