SQL-запрос выполняется медленно (для некоторых значений параметров) - PullRequest
2 голосов
/ 05 июля 2010

У меня есть база данных SQL Server 2005 с несколькими таблицами.Одна из таблиц используется для хранения меток времени и счетчиков сообщений для нескольких устройств и имеет следующие столбцы:

CREATE TABLE [dbo].[Timestamps] (
[Id] [uniqueidentifier] NOT NULL,
[MessageCounter] [bigint] NULL,
[TimeReceived] [bigint] NULL,
[DeviceTime] [bigint] NULL,
[DeviceId] [int] NULL
)

Id - это уникальный первичный ключ (Guid.Comb), и у меня есть индексы настолбцы DeviceId и MessageCounter.

Я хочу найти последнюю вставленную строку (строку с наибольшим MessageCounter) для определенного устройства.

ВещиСтранно, что запрос для устройства отсутствует.4 (и все другие устройства, кроме № 1) возвращает почти мгновенно:

select top 1 * 
   from "Timestamps"
   where DeviceId = 4
   order by MessageCounter desc

, но тот же запрос для устройства №.1 занимает целую вечность:

select top 1 * 
   from "Timestamps"
   where DeviceId = 1 /* this is the only line changed */
   order by MessageCounter desc

Самое странное, что устройство 1 имеет намного меньше строк , чем устройство 4:

select count(*) from "Timestamps" where DeviceId = 4
(returns 1,839,210)

select count(*) from "Timestamps" where DeviceId = 1
(returns 323,276).

Кто-нибудьПонять, что я могу делать неправильно?

[Редактировать]

Из планов выполнения для обоих запросов ясно видно, что устройство 1 (нижняя диаграмма)создает намного большее количество строк при сканировании индекса:

Планы выполнения для устройства 4 (верхнее) и устройства 1 (нижнее) http://img295.imageshack.us/img295/5784/execplans.png

Разница заключается в том, что при наведении курсора на узлы индексана диаграммах плана выполнения:

Device 4 Actual Number of Rows: 1

Device 1 Actual Number of Rows: approx. 6,500,000

6 500 000 строк - очень странное число, поскольку мой запрос select count(*) возвращает около 300 000 строк для устройства 1!

Ответы [ 6 ]

2 голосов
/ 05 июля 2010

Попробуйте создать индекс для (DeviceId, MessageCounter DESC).

Также попробуйте этот запрос:

select * 
   from "Timestamps"
   where DeviceId = 1
   and MessageCounter = (SELECT MAX(MessageCounter) FROM "Timestamps" WHERE DeviceID = 1)

Просто догадываюсь: разница в производительности может быть из-за того, что DeviceId = 1 распространяется на большее количество страниц, чем DeviceId = 4. При сортировке я подозреваю, что вы углубляете все подходящие страницы, даже если в итоге вы выбрали только верхнюю строку.

2 голосов
/ 05 июля 2010

Вы уверены, что статистика актуальна?Используйте ОБНОВЛЕНИЕ СТАТИСТИКИ :

UPDATE STATISTICS dbo.Timestamps

Как вы выполняете запрос?Если с помощью хранимой процедуры, возможно, у вас есть проблема с сниффинг параметра ?

1 голос
/ 06 июля 2010

Диаграммы планов выполнения не очень полезны, потому что они не показывают, какой индекс используется.

Наиболее полезная информация получается из следующего запроса

select DeviceId, max(MessageCounter) from "Timestamps" group by DeviceId

Я предполагаю, что MessageCounter дляУстройства с 2 по 4 являются относительными большими числами.MessageCounter - это относительно небольшое число.

Как SQL-сервер выполняет запрос в этом случае:

Сервер считывает индекс MessageCounter с больших на низкие числа.Для каждой строки сервер выполняет вложенный поиск по кастрированному индексу для сравнения идентификатора устройства.

Для устройств 2-4 это заканчивается очень скоро, поскольку сервер находит строку в индексе MessageCounter для устройства 2-4.Для устройства 1 серверу требуется более 6 миллионов операций поиска, прежде чем сервер найдет первую строку для устройства 1.

Было бы быстрее прочитать индекс deviceid и выполнить поиск в кастрированном индексе.Это должно прекратиться после того, как 323k ищет.Даже плохо.

У вас должен быть индекс, содержащий как идентификаторы устройств, так и MessageCounter (как указал Марсело Кантос).

1 голос
/ 06 июля 2010

Я предполагаю, что это должно происходить, потому что если вы упорядочите записи по MessageCounter по убыванию, то 6 500 000, которые он должен пропустить, прежде чем найдет первый с DeviceId=4, тогда как для других DeviceIdгораздо лучше спред

Я предполагаю, что предикат DeviceId=4 не вступит в игру, пока оператор Filter в плане выполнения.

Составной индекс в DeviceId, MessageCounter разрешит это,Но является ли Устройство с DeviceId=4 устаревшим устройством, для которого новые данные больше не записываются?Если это так, вы можете избежать извлечения записей DeviceId = 4 в свою собственную таблицу и использовать секционированное представление, чтобы запросы на этом устройстве не сканировали загрузку несвязанных записей.

Ниже исправлено

Также В чем причина выбора Guid.Comb в качестве кластерного индекса?Я предполагаю, что кластерный индекс на DeviceId, MessageCounter будет иметь аналогичные характеристики с точки зрения фрагментации и избежания горячих точек, но будет более полезным.

0 голосов
/ 05 июля 2010

Эти запросы отправляются на SQL Server точно , как вы их отправили

select top 1 * 
   from "Timestamps"
   where DeviceId = 4
   order by MessageCounter desc

или NHibernate использовал параметризованные запросы? (where deviceid = @deviceid или что-то в этом роде) ??

Это могло бы объяснить это - SQL Server получает параметризованный запрос для DeviceId = 4, разрабатывает план выполнения, который работает для этого значения параметра, но затем при следующем выполнении, для DeviceId = 1, он останавливается и каким-то образом выполняется план из первого запроса больше не является оптимальным для этого второго случая.

Можете ли вы попытаться выполнить эти два запроса в обратном порядке? Сначала с DeviceId = 1, а затем с DeviceId = 4 - это дает вам те же результаты ??

0 голосов
/ 05 июля 2010

Моей первой мыслью было, что это может быть связано с перехватом параметров - по сути, SQL Server разрабатывает план при первом запуске запроса, но этот запрос не представляет типичную рабочую нагрузку.См. http://www.sqlshare.com/solve-parameter-sniffing-by-using-local-variables_531.aspx

Советы по статистике - это хорошо, но я подозреваю, что вам нужно взглянуть на планы запросов для обоих этих запросов.Вы можете сделать это в Query Analyzer - это примерно три кнопки справа от кнопки Выполнить.Попробуй посмотреть, чем отличаются планы на оба запроса ...

...