Сравнение двух запросов в SQL Server 2012


14

Я сравниваю два запроса в SQL Server 2012. Цель состоит в том, чтобы использовать все соответствующие информацию , доступные из оптимизатора запросов при выборе наилучшего запроса. Оба запроса дают одинаковые результаты; максимальный заказ для всех клиентов.

Очистка пула буферов была выполнена перед выполнением каждого запроса с помощью FREEPROCCACHE и DROPCLEANBUFFERS

Используя информацию, представленную ниже, какой запрос является лучшим выбором?

-- Query 1 - return the maximum order id for a customer
SELECT orderid, custid
FROM Sales.Orders AS O1
WHERE orderid = (SELECT MAX(O2.orderid)
                 FROM Sales.Orders AS O2
                 WHERE O2.custid = O1.custid);


-- Query 2 - return the maximum order id for a customer
SELECT MAX(orderid), custid
FROM Sales.Orders AS O1
group by custid
order by custid

ВРЕМЯ СТАТИСТИКИ

Запрос 1 STATISTICS TIME: время процессора = 0 мс, прошедшее время = 24 мс

Запрос 2 STATISTICS TIME: время процессора = 0 мс, прошедшее время = 23 мс

СТАТИСТИКА IO

Запрос 1 STATISTICS IO: Таблица «Заказы». Сканирование 1, логическое чтение 5, физическое чтение 2, чтение с опережением 0, логическое чтение с бита 0, физическое чтение с бита 0, чтение с опережением чтения 0.

Запрос 2 STATISTICS IO: Таблица «Заказы». Сканирование 1, логическое чтение 4, физическое чтение 1, чтение с опережением 8, логическое чтение с бита 0, физическое чтение с бита 0, чтение с опережением чтения 0.

Планы выполнения

введите описание изображения здесь

ВЫБЕРИТЕ свойства Query 1

введите описание изображения здесь

ВЫБЕРИТЕ свойства Query 2

введите описание изображения здесь

Выводы:

Запрос 1

  1. Пакетная стоимость 48%
  2. Логические чтения 5
  3. Физические чтения 2
  4. Чтение впереди: 0
  5. Время процессора: 0 мс
  6. Прошедшее время 24 мс
  7. Ориентировочная стоимость поддерева: 0,0050276
  8. CompileCPU: 2
  9. CompileMemory: 384
  10. CompileTime: 2

Запрос 2

  1. Пакетная стоимость 52%
  2. Логические чтения 4
  3. Физические Чтения 1
  4. Чтение вперед: 8
  5. Процессорное время 0
  6. Прошедшее время 23мс
  7. Ориентировочная стоимость поддерева: 0,0054782
  8. CompileCPU: 0
  9. CompileMemory: 192
  10. CompileTime: 0

Лично, хотя Query 2 имеет более высокую стоимость пакета в соответствии с графическим планом, я думаю, что он более эффективен, чем Query 1. Это потому, что запрос 2 требует меньше логических чтений, имеет немного меньшее время, затрачиваемое на значения compilecpu, compilememory и compiletime. понизит. чтение с опережением - 8 для запроса 2 и 0 для запроса 1.

Обновление 12:03

Определение кластерного индекса

ALTER TABLE [Sales].[Orders] ADD  CONSTRAINT [PK_Orders] PRIMARY KEY CLUSTERED 
(
    [orderid] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO

Некластерный индекс idx_nc_custid

CREATE NONCLUSTERED INDEX [idx_nc_custid] ON [Sales].[Orders]
(
    [custid] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO

Комментарии не для расширенного обсуждения; этот разговор был перенесен в чат .
Пол Уайт 9

Ответы:


10

Мне нравится ваш подход к тщательному рассмотрению вопросов настройки запросов и рассмотрения вариантов и планов. Я хотел бы, чтобы больше разработчиков сделали это. Одно предостережение: всегда проверяйте множество строк, просматривая логические чтения, это небольшая таблица. Попробуйте сгенерировать пример загрузки и повторите запрос. Одна небольшая проблема - в вашем верхнем запросе вы не запрашиваете заказ, в нижнем запросе вы находитесь. Вы должны сравнить и сопоставить их каждый с порядком.

Я только что быстро создал таблицу SalesOrders с 200 000 заказов на продажу, которая по-прежнему невелика для любого воображения. И запустил запросы с ORDER BY в каждом. Я тоже немного поиграл с индексами.

Без кластеризованного индекса для OrderID, просто некластеризованный индекс для CustID . Второй запрос . Особенно с заказом, включенным в каждый. В первом запросе было вдвое больше чтений, чем во втором запросе, а процент затрат между запросами составил 67% / 33%.

С кластеризованным индексом для OrderID и некластеризованным индексом только для CustID. Они выполнялись с одинаковой скоростью и точно таким же числом операций чтения.

Поэтому я бы посоветовал вам увеличить количество строк и провести еще несколько тестов. Но мой окончательный анализ ваших запросов -

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

Если все, что вы когда-либо захотите вернуть, - это максимальный OrderID для каждого клиента, и вы хотите определить, что по OrderID, являющемуся наибольшим OrderID, то второй запрос из этих двух - лучший способ уйти от моего мышления - это немного Проще и немного дороже, в зависимости от стоимости поддерева, быстрее и проще расшифровать утверждение. Если вы намереваетесь когда-нибудь добавить другие столбцы в свой набор результатов? Тогда первый запрос позволяет вам сделать это.

Обновлено: один из ваших комментариев по вашему вопросу был:

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

Но лучший способ сделать это - проверить с большим количеством данных - всегда убедитесь, что у вас есть данные, согласующиеся с производством и ожидаемым будущим производством. Планы запросов начинают искать данные, когда вы добавляете больше строк в таблицы и пытаетесь сохранить распределение, которое вы ожидаете в рабочей среде. И обратите внимание на такие вещи, как «Заказать по» или «нет», здесь я не думаю, что в конечном итоге это будет иметь какое-то ужасное значение, но все же стоит окунуться в это.

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


Здравствуйте еще раз, спасибо за ваши очки за использование больших объемов данных. Это не первый раз, когда кто-то поднимает это. В последний раз, хотя это было рассмотреть возможную фрагментацию от разбиений страницы. В своем примере из 200 000 строк вы проверяли фрагментацию?
Крейг Эфрейн

Ну, в моем небольшом быстром примере с 200k строк я не фокусировался на фрагментации, нет. Но того, как я это сделал, не было бы. Я создал таблицу, заполнил ее, а затем сделал индексы, поэтому они были только что созданными индексами. И это не изменит подход к рассмотрению планов запросов, который кажется основным вопросом. Объем данных большой - действительно большой - при точном рассмотрении планов запросов. Я часто видел случаи, когда он отлично смотрелся в dev (с 1-10 строками) и был ужасен по сравнению с реальными данными. Но ваш подход хорош, и, надеюсь, эта информация и разговор в комментариях помогут
Майк Уолш

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

Я просто использовал функцию RAND, чтобы создать 100 клиентов и случайным образом назначить одного для каждого orderID. Я делал быструю проверку. :)
Майк Уолш

Спасибо, Майк, за всю твою помощь. Последний вопрос, хотя. На экранах свойств SELECT из плана выполнения в 2012 году, которые я указал в своем вопросе, на какие значения вы обращаете внимание?
Крейг Эфрейн
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.