У меня есть запрос, который в настоящее время занимает в среднем 2500 мсек. Моя таблица очень узкая, но в ней 44 миллиона строк. Какие варианты у меня есть, чтобы улучшить производительность, или это так хорошо, как это получается?
Запрос
SELECT TOP 1000 * FROM [CIA_WIZ].[dbo].[Heartbeats]
WHERE [DateEntered] BETWEEN '2011-08-30' and '2011-08-31';
Таблица
CREATE TABLE [dbo].[Heartbeats](
[ID] [int] IDENTITY(1,1) NOT NULL,
[DeviceID] [int] NOT NULL,
[IsPUp] [bit] NOT NULL,
[IsWebUp] [bit] NOT NULL,
[IsPingUp] [bit] NOT NULL,
[DateEntered] [datetime] NOT NULL,
CONSTRAINT [PK_Heartbeats] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
Индекс
CREATE NONCLUSTERED INDEX [CommonQueryIndex] ON [dbo].[Heartbeats]
(
[DateEntered] ASC,
[DeviceID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
Поможет ли добавление дополнительных индексов? Если так, то как бы они выглядели? Текущая производительность является приемлемой, поскольку запрос выполняется только изредка, но мне интересно как учебное упражнение, могу ли я что-нибудь сделать, чтобы сделать это быстрее?
ОБНОВИТЬ
Когда я изменяю запрос на использование подсказки принудительного индекса, запрос выполняется за 50 мс:
SELECT TOP 1000 * FROM [CIA_WIZ].[dbo].[Heartbeats] WITH(INDEX(CommonQueryIndex))
WHERE [DateEntered] BETWEEN '2011-08-30' and '2011-08-31'
Добавление правильно выбранного предложения DeviceID также попадает в диапазон 50 мс:
SELECT TOP 1000 * FROM [CIA_WIZ].[dbo].[Heartbeats]
WHERE [DateEntered] BETWEEN '2011-08-30' and '2011-08-31' AND DeviceID = 4;
Если я добавлю ORDER BY [DateEntered], [DeviceID]
к исходному запросу, я нахожусь в диапазоне 50 мс:
SELECT TOP 1000 * FROM [CIA_WIZ].[dbo].[Heartbeats]
WHERE [DateEntered] BETWEEN '2011-08-30' and '2011-08-31'
ORDER BY [DateEntered], [DeviceID];
Все они используют индекс, который я ожидал (CommonQueryIndex), так что, я полагаю, мой вопрос сейчас, есть ли способ заставить этот индекс использоваться в таких запросах, как этот? Или это размер моего стола сбросив оптимизатор слишком много , и я просто должен использовать ORDER BY
или намек?
Ответы:
Почему оптимизатор не использует ваш первый индекс:
Это вопрос селективности столбца [DateEntered].
Вы сказали нам, что в вашей таблице 44 миллиона строк. размер строки:
4 байта для идентификатора, 4 байта для идентификатора устройства, 8 байтов для даты и 1 байт для 4-битных столбцов. это 17 байт + 7 байт для (теги, нулевое растровое изображение, смещение переменной col, количество столбцов) составляет 24 байта на строку.
Это было бы грубым переводом на 140 тыс. Страниц. Для хранения этих 44 миллионов строк.
Теперь оптимизатор может делать две вещи:
Теперь в определенный момент становится все дороже выполнять все эти одиночные поиски в кластеризованном индексе для каждой записи индекса, найденной в вашем некластеризованном индексе. Порогом для этого обычно является общее количество поисков, которое должно превышать 25% и 33% от общего числа страниц таблицы.
Так что в этом случае: 140k / 25% = 35000 строк 140k / 33% = 46666 строк.
(@RBarryYoung, 35k - это 0,08% от общего числа строк, а 46666 - это 0,10%, так что я думаю, что именно здесь и произошла путаница)
Таким образом, если ваше предложение where будет содержать где-то между 35000 и 46666 строк. (Это находится под верхним предложением!) Весьма вероятно, что ваше некластеризованное не будет использовано и будет использовано сканирование кластерного индекса.
Единственные два способа изменить это:
Теперь убедитесь, что вы можете создать индекс покрытия, даже если вы используете select *. Однако это просто создает огромные накладные расходы для ваших вставок / обновлений / удалений. Нам нужно больше знать о вашей рабочей нагрузке (читай и писать), чтобы убедиться, что это лучшее решение.
Переход от datetime к smalldatetime уменьшает размер кластеризованного индекса на 16%, а размер некластеризованного индекса - на 24%.
источник
Есть ли конкретная причина, по которой ваш ПК кластеризован? Многие люди делают это, потому что это по умолчанию, или они думают, что PK должны быть кластеризованы. Нет так. Кластерные индексы обычно лучше всего подходят для запросов диапазона (как этот) или для внешнего ключа дочерней таблицы.
Эффект кластеризованного индекса состоит в том, что он объединяет все данные вместе, поскольку данные хранятся в конечных узлах дерева кластера b. Таким образом, предполагая, что вы не запрашиваете «слишком широкий» диапазон, оптимизатор будет точно знать, какая часть дерева b содержит данные, и ему не придется находить идентификатор строки, а затем переходить туда, где эти данные есть (как и при работе с индексом NC). Что такое «слишком широкий» диапазон? Смешной пример - запрос данных за 11 месяцев из таблицы, в которой есть записи за год. Извлечение данных за один день не должно быть проблемой, если предположить, что ваша статистика актуальна. (Тем не менее, у оптимизатора могут возникнуть проблемы, если вы ищете вчерашние данные и не обновляли статистику в течение трех дней.)
Поскольку вы выполняете запрос «SELECT *», движку нужно будет вернуть все столбцы в таблице (даже если кто-то добавит новый столбец, который в данный момент не требуется вашему приложению), так что индекс покрытия или индекс с включенными столбцами мало поможет, если вообще. (Если вы включаете каждый столбец из таблицы в индекс, вы делаете что-то не так.) Оптимизатор, вероятно, будет игнорировать эти индексы NC.
Так что делать?
Мое предложение было бы удалить индекс NC, изменить кластеризованный PK на некластеризованный и создать кластерный индекс на [DateEntered]. Чем проще, тем лучше, пока не доказано обратное.
источник
Пока у вас есть это "*", единственное, что я могу себе представить, что будет иметь большое значение, это изменить определение вашего индекса следующим образом:
Как я отмечал в комментариях, он должен использовать этот индекс, но если это не так, вы можете убедить его либо с помощью ORDER BY, либо с помощью подсказки индекса.
источник
Я бы посмотрел на это немного по-другому.
Я бы сбросил столбец datetime - изменил бы его на int. Имейте таблицу поиска или сделайте преобразование для своей даты.
Дамп кластеризованного индекса - оставьте его в виде кучи и создайте некластеризованный индекс в новом столбце INT, который представляет дату. то есть сегодня будет 20121015. Этот порядок важен. В зависимости от того, как часто вы загружаете таблицу, посмотрите на создание этого индекса в порядке DESC. Стоимость обслуживания будет выше, и вы захотите ввести коэффициент заполнения или разбиение. Разбиение также поможет сократить время выполнения.
И наконец, если вы можете использовать SQL 2012, попробуйте использовать SEQUENCE - он превзойдет identity () для вставок.
источник