Оптимизировать индекс для таблицы строк 2 135 044 521

10

У меня проблема ввода-вывода с большой таблицей.

Общая статистика

Таблица имеет следующие основные характеристики:

  • среда: база данных SQL Azure (уровень P4 Premium (500 DTU))
  • ряды: 2 135 044 521
  • 1275 использованных перегородок
  • кластерный и секционированный индекс

модель

Это реализация таблицы:

CREATE TABLE [data].[DemoUnitData](
    [UnitID] [bigint] NOT NULL,
    [Timestamp] [datetime] NOT NULL,
    [Value1] [decimal](18, 2) NULL,
    [Value2] [decimal](18, 2) NULL,
    [Value3] [decimal](18, 2) NULL,
    CONSTRAINT [PK_DemoUnitData] PRIMARY KEY CLUSTERED 
    (
        [UnitID] ASC,
        [Timestamp] ASC
    )
)
GO

ALTER TABLE [data].[DemoUnitData] WITH NOCHECK ADD CONSTRAINT [FK_DemoUnitData_Unit] FOREIGN KEY([UnitID])
REFERENCES [model].[Unit] ([ID])
GO

ALTER TABLE [data].[DemoUnitData] CHECK CONSTRAINT [FK_DemoUnitData_Unit]
GO

Разделение связано с этим:

CREATE PARTITION SCHEME [DailyPartitionSchema] AS PARTITION [DailyPartitionFunction] ALL TO ([PRIMARY])

CREATE PARTITION FUNCTION [DailyPartitionFunction] (datetime) AS RANGE RIGHT
FOR VALUES (N'2017-07-25T00:00:00.000', N'2017-07-26T00:00:00.000', N'2017-07-27T00:00:00.000', ... )

Качество обслуживания

Я думаю, что индексы и статистика хорошо поддерживаются каждую ночь путем постепенного перестроения / реорганизации / обновления.

Это текущая статистика индекса наиболее часто используемых разделов индекса:

Статистика раздела

Это текущие статистические свойства наиболее часто используемых разделов:

Статистика

проблема

Я запускаю простой запрос с высокой частотой к таблице.

SELECT [UnitID]
    ,[Timestamp]
    ,[Value1]
    ,[Value2]
    ,[Value3]
FROM [data].[DemoUnitData]
WHERE [UnitID] = 8877 AND [Timestamp] >= '2018-03-01' AND [Timestamp] < '2018-03-13'
OPTION (MAXDOP 1)

превышать количество

План выполнения выглядит следующим образом: https://www.brentozar.com/pastetheplan/?id=rJvI_4TtG

Моя проблема заключается в том, что эти запросы производят чрезвычайно большое количество операций ввода-вывода, что приводит к узкому PAGEIOLATCH_SHместу ожидания.

топ ждет

Вопрос

Я читал, что PAGEIOLATCH_SHожидания часто связаны с недостаточно оптимизированными индексами. Есть ли у вас какие-либо рекомендации для меня, как сократить количество операций ввода-вывода? Может быть, добавив лучший индекс?


Ответ 1 - связанный с комментарием от @ S4V1N

План размещенного запроса был из запроса, который я выполнил в SSMS. После вашего комментария я делаю некоторые исследования по истории сервера. Обычный запрос, получаемый из сервиса, выглядит немного иначе (связанный с EntityFramework).

(@p__linq__0 bigint,@p__linq__1 datetime2(7),@p__linq__2 datetime2(7)) 

SELECT 1 AS [C1], [Extent1] 
   .[Timestamp] AS [Timestamp], [Extent1] 
   .[Value1] AS [Value1], [Extent1] 
   .[Value2] AS [Value2], [Extent1] 
   .[Value3] AS [Value3]  
FROM [data].[DemoUnitData] AS [Extent1]  
WHERE ([Extent1].[UnitID] = @p__linq__0)  
AND ([Extent1].[Timestamp] >= @p__linq__1)  
AND ([Extent1].[Timestamp] < @p__linq__2) OPTION (MAXDOP 1) 

Также план выглядит иначе:

https://www.brentozar.com/pastetheplan/?id=H1fhALpKG

или

https://www.brentozar.com/pastetheplan/?id=S1DFQvpKz

И, как вы можете видеть здесь, этот запрос практически не влияет на производительность нашей БД.

Top SQL

Ответ 2 - связанный с ответом от @Joe Obbish

Для тестирования решения я заменил Entity Framework на простой SqlCommand. Результатом стало потрясающее повышение производительности!

План запросов теперь такой же, как в SSMS, и логические операции чтения и записи уменьшаются до ~ 8 за выполнение.

Общая нагрузка ввода-вывода упала почти до 0! Падение ввода / вывода

Это также объясняет, почему я получаю значительное падение производительности после изменения диапазона разделов с ежемесячного на ежедневный. Отсутствие удаления разделов привело к сканированию большего количества разделов.

Штеффен Мангольд
источник
2
Глядя на план выполнения, этот запрос не кажется настолько проблематичным. Он просканировал только необходимые разделы с небольшим количеством операций чтения и не сообщил, что pageiolatch_sh ожидает (sos_sched ..). И это понятно, потому что у вас все равно не было физических чтений. Это кумулятивные ожидания или они заняли определенное количество времени? Может быть, проблема в другом вопросе.
S4V1N
Я написал подробный ответ вам @ S4V1N выше
Штеффен Мангольд

Ответы:

7

Вы можете сократить время PAGEIOLATCH_SHожидания для этого запроса, если сможете изменить типы данных, генерируемые ORM. TimestampСтолбец в таблице имеет тип данных , DATETIMEно параметры @p__linq__1и @p__linq__2имеет типов данных DATETIME2(7). Это различие заключается в том, что план запроса для запросов ORM намного сложнее, чем первый опубликованный план запроса с жестко закодированными поисковыми фильтрами. Вы можете получить подсказку об этом и в XML:

<ScalarOperator ScalarString="GetRangeWithMismatchedTypes([@p__linq__1],NULL,(22))">

Как и в случае с запросом ORM, вы не можете получить никакого исключения раздела. Вы получите по крайней мере несколько логических чтений для каждого раздела, определенного в функции раздела, даже если вы просто ищете день данных. В каждом разделе вы получаете поиск по индексу, поэтому SQL Server не займет много времени, чтобы перейти к следующему разделу, но, возможно, все эти операции ввода-вывода складываются.

Я сделал простое воспроизведение, чтобы быть уверенным. В функции разделения определены 11 разделов. Для этого запроса:

DECLARE @p__linq__0 bigint = 2000;
DECLARE @p__linq__1 datetime2(7) = '20180103';
DECLARE @p__linq__2 datetime2(7) = '20180104';

SELECT 1 AS [C1]
, [Extent1].[Timestamp] AS [Timestamp]
, [Extent1].[Value1] AS [Value1]
FROM [DemoUnitData] AS [Extent1]  
WHERE ([Extent1].[UnitID] = @p__linq__0)  
AND ([Extent1].[Timestamp] >= @p__linq__1)  
AND ([Extent1].[Timestamp] < @p__linq__2)
OPTION (MAXDOP 1) ;

Вот как выглядит IO:

Таблица 'DemoUnitData'. Сканирование 11, логическое чтение 40

Когда я исправляю типы данных:

DECLARE @p__linq__0 bigint = 2000;
DECLARE @p__linq__1 datetime = '20180103';
DECLARE @p__linq__2 datetime = '20180104';

SELECT 1 AS [C1]
, [Extent1].[Timestamp] AS [Timestamp]
, [Extent1].[Value1] AS [Value1]
FROM [DemoUnitData] AS [Extent1]  
WHERE ([Extent1].[UnitID] = @p__linq__0)  
AND ([Extent1].[Timestamp] >= @p__linq__1)  
AND ([Extent1].[Timestamp] < @p__linq__2)
OPTION (MAXDOP 1) ;

IO уменьшается в результате исключения раздела:

Таблица 'DemoUnitData'. Сканирование 2, логическое чтение 8

Джо Оббиш
источник