Я создал таблицу big_table по вашей схеме
create table big_table
(
updatetime datetime not null,
name char(14) not null,
TheData float,
primary key(Name,updatetime)
)
Затем я заполнил таблицу 50000 строк с этим кодом:
DECLARE @ROWNUM as bigint = 1
WHILE(1=1)
BEGIN
set @rownum = @ROWNUM + 1
insert into big_table values(getdate(),'name' + cast(@rownum as CHAR), cast(@rownum as float))
if @ROWNUM > 50000
BREAK;
END
Используя SSMS, я протестировал оба запроса и понял, что в первом запросе вы ищете MAX для TheData, а во втором - MAX для времени обновления
Таким образом, я изменил первый запрос, чтобы получить также максимум обновления
set statistics time on -- execution time
set statistics io on -- io stats (how many pages read, temp tables)
-- query 1
SELECT MAX([UpdateTime])
FROM big_table
-- query 2
SELECT MAX([UpdateTime]) AS value
from
(
SELECT [UpdateTime]
FROM big_table
group by [UpdateTime]
) as t
set statistics time off
set statistics io off
Используя статистическое время, я получаю количество миллисекунд, необходимое для анализа, компиляции и выполнения каждого оператора.
Используя статистику ввода / вывода, я получаю информацию о дисковой активности
STATISTICS TIME и STATISTICS IO предоставляют полезную информацию. Например, были использованы временные таблицы (указаны рабочим столом). Также, сколько прочитанных логических страниц было прочитано, что указывает на количество страниц базы данных, прочитанных из кэша.
Затем я активирую план выполнения с помощью CTRL + M (активирует показ фактического плана выполнения), а затем выполняю с помощью F5.
Это обеспечит сравнение обоих запросов.
Вот вывод вкладки « Сообщения»
- Запрос 1
Стол "большой_таблицы". Сканирование 1, логическое чтение 543 , физическое чтение 0, чтение с опережением 0, логическое чтение с 0, физическое чтение с 0, чтение с опережением 0.
Время выполнения SQL Server:
время ЦП = 16 мс, прошедшее время = 6 мс .
- Запрос 2
Стол « Рабочий стол ». Сканирование счетчик 0, логическое чтение 0, физическое чтение 0, чтение с опережением 0, логическое чтение с бита 0, физическое чтение с бита 0, чтение с опережением чтения 0.
Стол "большой_таблицы". Сканирование 1, логическое чтение 543 , физическое чтение 0, чтение с опережением 0, логическое чтение с 0, физическое чтение с 0, чтение с опережением 0.
Время выполнения SQL Server:
время ЦП = 0 мс, прошедшее время = 35 мс .
Оба запроса приводят к 543 логическим чтениям, но у второго запроса истекшее время 35 мс, тогда как у первого - только 6 мс. Вы также заметите, что второй запрос приводит к использованию временных таблиц в базе данных tempdb, обозначенных словом worktable . Несмотря на то, что все значения для рабочего стола равны 0, работа все еще выполнялась в базе данных tempdb.
Затем выводится вкладка фактического плана выполнения рядом с вкладкой «Сообщения».
Согласно плану выполнения, предоставленному MSSQL, второй предоставленный вами запрос имеет общую стоимость пакета 64%, тогда как первый запрос стоит всего 36% от общего пакета, поэтому первый запрос требует меньше работы.
Используя SSMS, вы можете тестировать и сравнивать свои запросы и точно выяснять, как MSSQL анализирует ваши запросы и какие объекты: таблицы, индексы и / или статистические данные, если таковые используются, для удовлетворения этих запросов.
Еще одно дополнительное замечание, которое следует учитывать при тестировании, - это очистить кеш перед тестированием, если это возможно. Это помогает гарантировать, что сравнения точны, и это важно, когда вы думаете об активности диска. Я начинаю с DBCC DROPCLEANBUFFERS и DBCC FREEPROCCACHE, чтобы очистить весь кэш. Однако будьте осторожны, чтобы не использовать эти команды на рабочем сервере, который фактически используется, поскольку вы фактически заставите сервер читать все данные с диска в память.
Вот соответствующая документация.
- Очистить кэш плана с помощью DBCC FREEPROCCACHE
- Очистить все из пула буферов с помощью DBCC DROPCLEANBUFFERS
Использование этих команд может быть невозможно в зависимости от того, как используется ваша среда.
Обновлено 28.10 12:46
Внесены исправления в изображение плана выполнения и вывод статистики.
getdate()
из циклаПерезапись могла бы помочь, если в SQL Server реализовано сканирование с пропуском индекса, но это не так.
Пропуск сканирования индекса позволяет ядру базы данных искать следующее другое значение индекса вместо сканирования всех дубликатов (или нерелевантных подключей) между ними. В вашем случае, skip-scan позволит движку найти
MAX(UpdateTime)
первыйName
, перейти коMAX(UpdateTime)
второмуName
... и так далее. Последним шагом будет поискMAX(UpdateTime)
кандидатов по одному имени.Вы можете смоделировать это в некоторой степени с помощью рекурсивного CTE, но это немного грязно и не так эффективно, как встроенное skip-scan:
Этот план выполняет поиск по одному для каждого отдельного
Name
, а затем находит наибольшееUpdateTime
из кандидатов. Его производительность по сравнению с простым полным сканированием таблицы зависит от того, сколько дубликатов существуетName
, и от того, есть ли в памяти страницы, к которым обращается поиск по одиночке, или нет.Альтернативные решения
Если вы можете создать новый индекс для этой таблицы, хорошим выбором для этого запроса будет индекс
UpdateTime
только для одного:Этот индекс позволит механизму выполнения найти самое высокое
UpdateTime
при одиночном поиске до конца b-дерева индекса:Этот план потребляет всего несколько логических операций ввода-вывода (для навигации по уровням B-дерева) и завершается немедленно. Обратите внимание, что индексное сканирование в плане не является полным сканированием нового индекса - оно просто возвращает одну строку из одного «конца» индекса.
Если вы не хотите создавать новый полный индекс для таблицы, вы можете рассмотреть индексированное представление, содержащее только уникальные
UpdateTime
значения:Преимущество этого заключается в создании структуры, содержащей столько строк, сколько имеется уникальных
UpdateTime
значений, хотя каждый запрос, изменяющий данные в базовой таблице, будет иметь дополнительные операторы, добавленные в его план выполнения для поддержки индексированного представления. Запрос для определения максимальногоUpdateTime
значения будет:источник