Производительность SQL MAX()

Только что получил небольшой вопрос. При попытке получить одно максимальное значение таблицы. Какой из них лучше?

SELECT MAX(id) FROM myTable WHERE (whatever)

или

SELECT TOP 1 id FROM myTable WHERE (whatever) ORDER BY id DESC

Я использую Microsoft SQL Server 2012

Ответ 1

Не будет никакой разницы, поскольку вы можете проверить себя, проверив планы выполнения. Если id - это кластеризованный индекс, вы должны увидеть упорядоченное сканирование индексированных кластеров; если он не проиндексирован, вы все равно увидите либо сканирование таблицы, либо кластерное сканирование индекса, но оно не будет упорядочено в любом случае.

Подход TOP 1 может быть полезен, если вы хотите вытащить другие значения из строки, что проще, чем потянуть max в подзапрос и затем присоединиться. Если вам нужны другие значения из строки, вам нужно диктовать, как обращаться со связями в обоих случаях.

Сказав это, есть некоторые сценарии, в которых план может быть другим, поэтому важно проверить в зависимости от того, индексирован ли столбец и монотонно ли он увеличивается. Я создал простую таблицу и вставил 50000 строк:

CREATE TABLE dbo.x
(
  a INT, b INT, c INT, d INT, 
  e DATETIME, f DATETIME, g DATETIME, h DATETIME
);
CREATE UNIQUE CLUSTERED INDEX a ON dbo.x(a);
CREATE INDEX b ON dbo.x(b)
CREATE INDEX e ON dbo.x(e);
CREATE INDEX f ON dbo.x(f);

INSERT dbo.x(a, b, c, d, e, f, g, h)
SELECT 
  n.rn, -- ints monotonically increasing
  n.a,  -- ints in random order
  n.rn, 
  n.a, 
  DATEADD(DAY, n.rn/100, '20100101'), -- dates monotonically increasing
  DATEADD(DAY, -n.a % 1000, '20120101'),     -- dates in random order
  DATEADD(DAY, n.rn/100, '20100101'),
  DATEADD(DAY, -n.a % 1000, '20120101')
FROM
(
  SELECT TOP (50000) 
     (ABS(s1.[object_id]) % 10000) + 1, 
     rn = ROW_NUMBER() OVER (ORDER BY s2.[object_id])
  FROM sys.all_objects AS s1 
  CROSS JOIN sys.all_objects AS s2
) AS n(a,rn);
GO

В моей системе это создало значения в a/c от 1 до 50000, b/d между 3 и 9994, e/g с 2010-01-01 по 2011-05-16 и f/h с 2009-04 -28 до 2012-01-01.

Сначала сравним индексированные монотонно возрастающие целые столбцы a и c. a имеет кластерный индекс, c не:

SELECT MAX(a) FROM dbo.x;
SELECT TOP (1) a FROM dbo.x ORDER BY a DESC;

SELECT MAX(c) FROM dbo.x;
SELECT TOP (1) c FROM dbo.x ORDER BY c DESC;

Результаты:

enter image description here

Большая проблема с 4-м запросом заключается в том, что, в отличие от MAX, для него требуется сортировка. Здесь 3 по сравнению с 4:

enter image description here

enter image description here

Это будет распространенной проблемой во всех этих вариантах запроса: a MAX против необработанного столбца сможет копировать сканирование кластерного индекса и выполнять агрегацию потока, тогда как TOP 1 необходимо выполнить сортировка, которая будет дороже.

Я проверил и увидел те же самые результаты при тестировании b + d, e + g и f + h.

Итак, мне кажется, что помимо создания большего количества кода соответствия стандартам существует потенциальная эффективность использования MAX в пользу TOP 1 в зависимости от базовой таблицы и индексов (которая может измениться после вас 'выложите свой код в производство). Поэтому я бы сказал, что без дополнительной информации MAX предпочтительнее.

(И, как я уже говорил, TOP 1 действительно может быть поведением, которое вам нужно, если вы тянете дополнительные столбцы. Вам нужно будет протестировать методы MAX + JOIN, если это вы после.)

Ответ 2

Первый, безусловно, яснее в намерении.

Не должно быть существенных различий в производительности для этого конкретного запроса (они должны быть практически идентичны, хотя результат отличается, если в myTable нет строк). Если у вас нет веских причин для настройки запроса (например, доказанной проблемы с производительностью), всегда выбирайте тот, который показывает намерение кода.

Ответ 3

Все оптимизаторы запросов, заслуживающие своей соли, должны создавать планы запросов с одинаковой производительностью для обоих запросов: если индекс оптимизирован для столбца, оба запроса должны его использовать; если индекс отсутствует, оба будут производить полное сканирование таблицы.

Ответ 4

Хотя я подозреваю, что оператор сортировки TOP 1 превышен в плане. Я пробовал с ТОП-1, ТОП-100, > и ТОП-101, и все дали мне такую ​​же расчетную стоимость поддерева, несмотря на то, что последнему нужно было отсортировать все строки. - Мартин Смит 2 июля в 6:53

Если вам нужен 1 ряд или 100 строк, оптимизатор должен выполнить такой же объем работы в этом примере, то есть прочитать все строки из таблицы (сканирование с кластерным индексом). Затем отсортировать все эти строки (сортировать оперант), поскольку нет индекс в столбце C..Наконец просто покажите, какой из них необходим.

SELECT TOP (1) b FROM dbo.x ORDER BY b DESC
option(recompile); 
SELECT TOP (100) b FROM dbo.x ORDER BY b DESC
option(recompile); 

Попробуйте код выше, и здесь top 1 и top 100 показывают стоимость diff, потому что есть индекс в столбце b. Таким образом, в этом случае вам не нужно читать все строки и сортировать их, но работа заключается в том, чтобы перейти к указателю последней страницы. Для одной строки прочитайте последнюю строку на последней странице листа индекса. TFor 100 row найдите последнюю строку на последней странице, а затем запустите обратное сканирование, пока не получите 100 строк.