与单独的 SELECT 相比,使用 OR 条件查找索引的速度要慢得多

Voj*_*nal 8 performance sql-server-2008 sql-server execution-plan query-performance

基于这些问题和给出的答案:

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]
Run Code Online (Sandbox Code Playgroud)

它包含 cca 2.11 亿行。

我运行以下语句:

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;
Run Code Online (Sandbox Code Playgroud)

结果如下所示:

执行计划

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

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

 SELECT MIN(cas) from PenData p WHERE IDUkazatel IN (SELECT IDUkazatel FROM  ...)
Run Code Online (Sandbox Code Playgroud)

编辑

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

胖箭

Dav*_*ett 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
Run Code Online (Sandbox Code Playgroud)

也就是说,您似乎有一个带有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;
Run Code Online (Sandbox Code Playgroud)

在理想情况下,SQL Server 查询优化器会为您执行这种重写,但今天它并不总是考虑这个选项。