与单独的SELECT相比,在OR条件下索引查找要慢得多


8

根据这些问题和给出的答案:

SQL 2008 Server-性能损失可能与非常大的表有关

具有历史数据的大表分配了过多的SQL Server 2008 Std。内存-其他数据库的性能损失

我在数据库SupervisionP中有一个表,定义如下:

CREATE TABLE [dbo].[PenData](
    [IDUkazatel] [smallint] NOT NULL,
    [Cas] [datetime2](0) NOT NULL,
    [Hodnota] [real] NULL,
    [HodnotaMax] [real] NULL,
    [HodnotaMin] [real] NULL,
 CONSTRAINT [PK_Data] PRIMARY KEY CLUSTERED 
(
    [IDUkazatel] ASC,
    [Cas] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

ALTER TABLE [dbo].[PenData]  WITH NOCHECK ADD  CONSTRAINT [FK_Data_Ukazatel] FOREIGN KEY([IDUkazatel])
REFERENCES [dbo].[Ukazatel] ([IDUkazatel])

ALTER TABLE [dbo].[PenData] CHECK CONSTRAINT [FK_Data_Ukazatel]

它包含cca 211百万行。

我运行以下语句:

DECLARE @t1 DATETIME;
DECLARE @t2 DATETIME;

SET @t1 = GETDATE();
SELECT min(cas) from PenData p WHERE IDUkazatel=24
SELECT min(cas) from PenData p WHERE IDUkazatel=25
SET @t2 = GETDATE();
SELECT DATEDIFF(millisecond,@t1,@t2) AS elapsed_ms;


SET @t1 = GETDATE();
SELECT min(cas) from PenData p WHERE IDUkazatel=24 OR IDUkazatel=25 
SET @t2 = GETDATE();
SELECT DATEDIFF(millisecond,@t1,@t2) AS elapsed_ms;

结果显示在这里:

执行计划

第三个SELECT还将更多数据加载到SQL Server内存缓存中。

为什么第三个SELECT比前两个SELECT(16毫秒)慢得多(8.5 s)?如何使用OR提高第三选择的性能?我想运行以下SQL命令,但在我看来,在这种情况下,创建游标和运行单独的查询比单次选择要快得多。

 SELECT MIN(cas) from PenData p WHERE IDUkazatel IN (SELECT IDUkazatel FROM  ...)

编辑

正如大卫建议的那样,我将鼠标悬停在粗箭头上:

胖箭

Answers:


11

对于前两个查询,所有要做的就是在聚集索引中扫描到该值的第一个条目IDUkazatel-由于索引的顺序,该行将是cas在该值下的最低值IDUkazatel

在第二个查询中,此优化不是值,它可能正在寻找第一行,IDUkazatel=24然后向下搜索索引直到最后一行,IDUkazatel=25以查找cas所有这些行中的最小值。

如果将鼠标悬停在那个粗箭头上,您会看到它正在读取许多行(肯定是所有的24行,可能也是25行的所有行),而计划输出中的其他两个细箭头显示了top导致其仅行的行为。考虑一排。

您可以尝试运行每个查询,然后获取找到的最小值的最小值:

SELECT MIN(cas)
FROM   (
        SELECT cas=MIN(cas) FROM PenData p WHERE p.IDUkazatel = 24
        UNION ALL
        SELECT cas=MIN(cas) FROM PenData p WHERE p.IDUkazatel = 25
    ) AS minimums

就是说,似乎您有一个包含IDUkazatel值而不是显式OR子句的表。下面的代码将适用于这种安排,只需将表名替换为@T包含IDUkazatel值的表名即可:

SELECT 
    MinCas = MIN(CA.PartialMinimum)
FROM @T AS T
CROSS APPLY 
(
    SELECT 
        PartialMinimum = MIN(PD.Cas)
    FROM dbo.PenData AS PD
    WHERE 
        PD.IDUkazatel = T.IDUkazatel
) AS CA;

在理想情况下,SQL Server查询优化器将为您执行此重写,但今天并不总是考虑使用此选项。


你可以重写了最后一个没有派生表SELECT TOP (1) min_cas=MIN(CAS) ... ORDER BY min_cas;(但我想这个计划将是一样的你。)
ypercubeᵀᴹ
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.