Мы используем SQL Server 2008 R2, и у нас есть очень большая (100M + строки) таблица с индексом первичного идентификатора и datetime
столбец с некластеризованным индексом. Мы наблюдаем довольно необычное поведение клиент / сервер, основанное на использовании order by
предложения специально для индексированного столбца даты и времени .
Я прочитал следующий пост: /programming/1716798/sql-server-2008-ordering-by-datetime-is-too-slow, но с клиентом / сервером происходит больше, чем то, что есть начать описано здесь.
Если мы запустим следующий запрос (отредактированный для защиты некоторого содержимого):
select *
from [big table]
where serial_number = [some number]
order by test_date desc
Время ожидания запроса каждый раз. В SQL Server Profiler выполненный запрос выглядит на сервере следующим образом:
exec sp_cursorprepexec @p1 output,@p2 output,NULL,N'select * .....
Теперь, если вы измените запрос на, скажите это:
declare @temp int;
select * from [big table]
where serial_number = [some number]
order by test_date desc
Профилировщик SQL Server показывает, что выполненный запрос выглядит на сервере следующим образом, и он работает мгновенно:
exec sp_prepexec @p1 output, NULL, N'declare @temp int;select * from .....
На самом деле, вы можете даже поставить пустой комментарий ('-;') вместо неиспользованного оператора объявления и получить тот же результат. Итак, изначально мы указывали на препроцессор sp в качестве основной причины этой проблемы, но если вы сделаете это:
select *
from [big table]
where serial_number = [some number]
order by Cast(test_date as smalldatetime) desc
Он также работает мгновенно (вы можете привести его как любой другой datetime
тип), возвращая результат в миллисекундах. А профилировщик показывает запрос к серверу как:
exec sp_cursorprepexec @p1 output, @p2 output, NULL, N'select * from .....
Так что это несколько исключает sp_cursorprepexec
процедуру из полной причины проблемы. Добавьте к этому тот факт, что также sp_cursorprepexec
вызывается, когда не используется 'order by', и результат также мгновенно возвращается.
Мы довольно много гуглили по этой проблеме, и я вижу похожие проблемы, опубликованные другими, но ни одна из них не приводит к этому уровню.
Так другие были свидетелями этого поведения? У кого-нибудь есть решение лучше, чем помещать бессмысленный SQL перед оператором select, чтобы изменить поведение? Поскольку SQL Server должен вызывать порядок после того, как данные собраны, кажется, что это ошибка на сервере, которая сохраняется в течение длительного времени. Мы обнаружили, что это поведение согласованно во многих наших больших таблицах и воспроизводимо.
Редактирование:
Я должен также добавить вставку forceseek
в также делает проблему исчезнуть.
Я должен добавить, чтобы помочь поисковикам, выдана ошибка тайм-аута ODBC: [Microsoft] [Драйвер ODBC SQL Server] Операция отменена
Добавлено 12.10.2012: Все еще выискивая основную причину (наряду с созданием образца для передачи в Microsoft, я буду отправлять здесь любые результаты после отправки). Я копался в файле трассировки ODBC между рабочим запросом (с добавленным оператором комментария / объявления) и нерабочим запросом. Фундаментальная разница трасс приведена ниже. Это происходит при вызове вызова SQLExtendedFetch после завершения всех обсуждений SQLBindCol. Вызов завершается неудачно с кодом возврата -1, и родительский поток затем вводит SQLCancel. Так как мы можем производить это как с Native Client, так и с устаревшими драйверами ODBC, я все еще указываю на некоторые проблемы совместимости на стороне сервера.
(clip)
MSSQLODBCTester 1664-1718 EXIT SQLBindCol with return code 0 (SQL_SUCCESS)
HSTMT 0x001EEA10
UWORD 16
SWORD 1 <SQL_C_CHAR>
PTR 0x03259030
SQLLEN 51
SQLLEN * 0x0326B820 (0)
MSSQLODBCTester 1664-1718 ENTER SQLExtendedFetch
HSTMT 0x001EEA10
UWORD 1 <SQL_FETCH_NEXT>
SQLLEN 1
SQLULEN * 0x032677C4
UWORD * 0x032679B0
MSSQLODBCTester 1664-1fd0 ENTER SQLCancel
HSTMT 0x001EEA10
MSSQLODBCTester 1664-1718 EXIT SQLExtendedFetch with return code -1 (SQL_ERROR)
HSTMT 0x001EEA10
UWORD 1 <SQL_FETCH_NEXT>
SQLLEN 1
SQLULEN * 0x032677C4
UWORD * 0x032679B0
DIAG [S1008] [Microsoft][ODBC SQL Server Driver]Operation canceled (0)
MSSQLODBCTester 1664-1fd0 EXIT SQLCancel with return code 0 (SQL_SUCCESS)
HSTMT 0x001EEA10
MSSQLODBCTester 1664-1718 ENTER SQLErrorW
HENV 0x001E7238
HDBC 0x001E7B30
HSTMT 0x001EEA10
WCHAR * 0x08BFFC5C
SDWORD * 0x08BFFF08
WCHAR * 0x08BFF85C
SWORD 511
SWORD * 0x08BFFEE6
MSSQLODBCTester 1664-1718 EXIT SQLErrorW with return code 0 (SQL_SUCCESS)
HENV 0x001E7238
HDBC 0x001E7B30
HSTMT 0x001EEA10
WCHAR * 0x08BFFC5C [ 5] "S1008"
SDWORD * 0x08BFFF08 (0)
WCHAR * 0x08BFF85C [ 53] "[Microsoft][ODBC SQL Server Driver]Operation canceled"
SWORD 511
SWORD * 0x08BFFEE6 (53)
MSSQLODBCTester 1664-1718 ENTER SQLErrorW
HENV 0x001E7238
HDBC 0x001E7B30
HSTMT 0x001EEA10
WCHAR * 0x08BFFC5C
SDWORD * 0x08BFFF08
WCHAR * 0x08BFF85C
SWORD 511
SWORD * 0x08BFFEE6
MSSQLODBCTester 1664-1718 EXIT SQLErrorW with return code 100 (SQL_NO_DATA_FOUND)
HENV 0x001E7238
HDBC 0x001E7B30
HSTMT 0x001EEA10
WCHAR * 0x08BFFC5C
SDWORD * 0x08BFFF08
WCHAR * 0x08BFF85C
SWORD 511
SWORD * 0x08BFFEE6
(clip)
Добавлен кейс Microsoft Connect 10/12/2012:
Следует также отметить, что мы просмотрели планы запросов как для действующих, так и для неработающих запросов. Они оба повторно используются надлежащим образом в зависимости от количества выполненных операций. Сброс кэшированных планов и повторный запуск не влияют на успешность запроса.
источник
select id, test_date from [big table] where serial_number = ..... order by test_date
- мне просто интересно, еслиSELECT *
это негативно влияет на вашу производительность. Если у вас есть некластеризованный индексtest_date
и кластеризованный индексid
(при условии, что он так и называется), этот запрос должен быть покрыт этим некластеризованным индексом и, следовательно, должен возвращаться довольно быстроsp_executesql
и посмотрите, что произойдет.Ответы:
В этом нет ничего загадочного, вы получаете хороший (ошибочный) или (действительно) плохой план, в основном случайный, потому что не существует четкого выбора для использования индекса. Хотя для предложения ORDER BY и, таким образом, избегается сортировка, не кластеризованный индекс для столбца datetime является очень плохим выбором для этого запроса. То, что сделало бы намного лучший индекс для этого запроса, было бы одним на
(serial_number, test_date)
. Более того, это было бы очень хорошим кандидатом для ключа кластеризованного индекса.Как правило, временные ряды должны быть кластеризованы по временному столбцу, поскольку подавляющее большинство запросов заинтересованы в определенных временных диапазонах. Если данные также по своей сути разделены на столбец с низкой избирательностью, как, например, в случае с вашим serial_number, то этот столбец следует добавить как самый левый в определении кластерного ключа.
источник
the order
пункте? Не должен ли план ограничивать себяwhere
условиями, так как упорядочение должно происходить только после извлечения строк? Зачем серверу пытаться отсортировать записи до полного набора результатов?Документируйте детали того, как воспроизвести ошибку, и отправьте ее на connect.microsoft.com. Я проверил и не мог видеть ничего там, что будет связано с этим.
источник
Моя гипотеза состоит в том, что вы работаете с кэшем плана запросов. (Ремус может говорить то же самое, что и я, но по-другому.)
Вот масса деталей о том, как SQL планирует кэширование .
Затенение деталей: кто-то выполнил этот запрос ранее, для определенного [некоторого числа]. SQL посмотрел на предоставленное значение, индексы и статистику для соответствующей таблицы / столбцов и т. Д. И создал план, который хорошо работал для этого конкретного [некоторого числа]. Затем он кэшировал план, запускал его и возвращал результаты вызывающей стороне.
Позже кто-то другой выполняет тот же запрос, для другого значения [некоторое число]. Это конкретное значение приводит к сильно различному количеству строк результатов, и механизм должен создать другой план для этого экземпляра запроса. Но это не работает таким образом. Вместо этого SQL принимает запрос и (более или менее) выполняет поиск в кеше запроса с учетом регистра в поисках уже существующей версии запроса. Когда он находит тот из ранее, он просто использует этот план.
Идея состоит в том, что это экономит время, необходимое для определения плана и его построения. Дыра в идее заключается в том, что один и тот же запрос выполняется со значениями, которые дают совершенно разные результаты. У них должны быть разные планы, но они этого не делают. Кто бы ни запускал запрос первым, он устанавливает поведение для всех, кто запускает его потом.
Быстрый пример: выберите * из [people], где lastname = 'SMITH' - очень популярная фамилия в США. GO выберите * из [people], где lastname = 'BONAPARTE' - НЕ популярная фамилия в США.
При выполнении запроса для BONAPARTE план, созданный для SMITH, будет использован повторно. Если SMITH вызвал сканирование таблицы (что может быть хорошо , если строки в таблице 99% SMITH), то BONAPARTE также получит сканирование таблицы. Если BONAPARTE был запущен до SMITH, план, использующий индекс, мог бы быть создан и использован, а затем снова использован для SMITH (что может быть лучше при сканировании таблицы). Люди могут не заметить, что производительность для SMITH плохая, поскольку они ожидают плохой производительности, поскольку вся таблица должна быть прочитана, а чтение индекса и переход к таблице не замечены напрямую.
Что касается ваших изменений, которые должны быть изменены, я подозреваю, что SQL просто рассматривает это как совершенно другой запрос и строит новый план, соответствующий вашему значению [некоторое число].
Чтобы проверить это, внесите бессмысленное изменение в запрос, например, добавив несколько пробелов между FOR и именем таблицы, или добавьте комментарий в конце. Это быстро? Если это так, то это потому, что этот запрос немного отличается от того, что находится в кэше, поэтому SQL сделал то, что он делает для «новых» запросов.
Для решения я бы посмотрел на три вещи. Во-первых, убедитесь, что ваша статистика актуальна. Это действительно должно быть первым, что вы делаете, когда запрос кажется странным или случайным. Ваш DBA должен делать это, но вещи случаются. Обычный способ обеспечить актуальную статистику - это переиндексировать ваши таблицы, что не обязательно облегчает задачу, но есть также варианты просто обновить статистику.
Второе, о чем стоит подумать, - это добавить индексы в соответствии с предложениями Ремуса. При более высоком / другом индексе одно значение по сравнению с другим может быть более стабильным и не так сильно изменяться.
Если это не помогает, третье, что нужно попробовать, это принудительно вводить новый план каждый раз, когда вы запускаете инструкцию, используя ключевое слово RECOMPILE:
выберите * из [большой таблицы], где serial_number = [некоторый номер], порядок по test_date desc OPTION (RECOMPILE)
Здесь есть статья, описывающая подобную ситуацию . Честно говоря, раньше я видел только применение RECOMPILE к хранимым процедурам, но, похоже, он работает с «обычными» операторами SELECT. Кимберли Трипп никогда не водила меня неправильно.
Вы также можете посмотреть на функцию, называемую « руководство по планированию », но она более сложна и может быть излишней.
источник
order by
использованием именно индекса datetime. 3. Просто опробовал вашу идею с опцией RECOMPILE, но она все равно не удалась, что меня немного удивило, я надеялся, что она сработает, хотя я не знаю, является ли это решением для производства.