Поиск индекса намного медленнее с условием ИЛИ по сравнению с отдельными SELECT

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]

Он содержит около 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 намного медленнее (8,5 с), чем первые два SELECT (16 мс)? Как я могу улучшить производительность третьего выбора с помощью ИЛИ? Я хочу выполнить следующую команду SQL, но мне кажется, что создание курсора и выполнение отдельных запросов в этом случае выполняется намного быстрее, чем один выбор.

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

РЕДАКТИРОВАТЬ

Как предположил Дэвид, я завис над жирной стрелой:

FatArrow

Войтех Донхал
источник

Ответы:

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ᵀᴹ