SQL-запрос - необходимо повысить производительность - PullRequest
6 голосов
/ 20 июля 2011

У меня есть сценарий загрузки данных, где я создаю динамический SQL-запрос для извлечения данных и кеша в нашем сервисе. Существует 1 таблица, которая содержит все данные о продукте: ProductHistory (47 столбцов, 200 000 записей + и будет продолжать расти)

Что мне нужно: Получить последние продукты, используя максимальный идентификатор, максимальную версию и максимальное изменение.

Первая попытка:

SELECT distinct Product.* FROM ProductHistory product 
WHERE  product.version = 
(SELECT max(version) from ProductHistory p2 where product.Id = p2.Id 
  and product.changeId = 
(SELECT max(changeid) from ProductHistory p3 where p2.changeId = p3.changeId))

Это заняло более 2,51 минуты.

Другая неудачная попытка:

select distinct product.* from ProductHistory product 
where CAST(CAST(id as nvarchar)+'0'+CAST(Version as nvarchar)+'0'+CAST(changeid as nvarchar) as decimal) = 
(select MAX(CAST(CAST(id as nvarchar)+'0'+CAST(Version as nvarchar)+'0'+CAST(changeid as nvarchar) as decimal)) from ProductHistory p2 
where product.Id = p2.Id)

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

For example 11 Jun 2007 = 20070711
And in our case: Id = 4 , version = 127, changeid = 32   => 40127032
The zeros are there not to mix up the 3 different ids

Но это занимает 3,10 минуты !!! (

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

  1. Я запустил sp_helpindex ProductHistory и обнаружил индексы, как показано ниже:

    PK_ProductHistoryNew - кластерный, уникальный первичный ключ, расположенный в PRIMARY-Id, версия

  2. Я завернул первый запрос в SP, но все еще без изменений.

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

Спасибо, Mani p.s: я просто запускаю эти запросы в SQL Management Stuido, чтобы увидеть время.

Ответы [ 8 ]

6 голосов
/ 20 июля 2011

Запустите запрос из Sql Server Management Studio и посмотрите на план запроса, чтобы увидеть, где находится горлышко бутылки. В любом месте, где вы видите «сканирование таблицы» или «сканирование индекса», оно должно пройти через все данные, чтобы найти то, что ищет. Если вы создаете соответствующие индексы, которые можно использовать для этих операций, это должно повысить производительность.

4 голосов
/ 20 июля 2011

Некоторые вещи, которые я вижу:

  • Нужно ли DISTINCT?Если вы сделаете DISTINCT *, это вряд ли принесет какую-либо выгоду, но у него будут накладные расходы на проверку дубликатов во всех полях.
  • Вместо двух подвыборов в предложении WHERE, JOIN для производногоТаблица.Это должно обрабатываться только один раз.Я подозреваю, что ваше предложение WHERE обрабатывается несколько раз.

<- ->

SELECT Product.* 
FROM ProductHistory product 
INNER JOIN ( SELECT P.Id, 
                    MAX(p.version) as [MaxVer], 
                    MAX(p.Changeid) as [MaxChange]
             FROM Product p
             GROUP BY p.ID) SubQ
    ON SubQ.ID = product.ID
    AND SubQ.MaxChange = Product.ChangeID
    AND SubQ.MaxVer = Product.Version

Для этого также должен быть индекс Id, Version, ChangeID.

1 голос
/ 20 июля 2011

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

Кроме того, не используйте DISTINCT, поскольку он часто скрывает проблемы в запросе (обычно, если дубликаты извлекаются, это означает, что вы можете оптимизировать лучше).

Теперь лучшая часть: как решить вашу проблему? Я полагаю, вам следует использовать принцип группировки , который дает что-то вроде этого:

SELECT max(id), max(version), max(changeid) 
  FROM ProductHistory p
  WHERE <filter if necessary for old products or anything else>
  GROUP BY version, changeid
  HAVING version = max(version)
     AND changeid = max(changeid)
     AND id = max(id)

Но, если я посмотрю на ваш ПК, я удивлюсь, changeid не имеет значения, так как вы должны иметь дело только с id и версией ...

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

0 голосов
/ 04 сентября 2012

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

with mysuperfastcte as (
 select product.*, 
 row_number() over (partition by id order by version desc) as versionorder,
 row_number() over (partition by id order by changeid desc) as changeorder 
 from ProductHistory as product
)
select distinct product.*
from mysuperfastcte
where versionorder = 1
and changeorder = 1;

NB. Я думаю, что в этот момент в вашем коде может быть ошибка, поэтому, пожалуйста, подтвердите и дважды проверьте результаты, которые вы ожидаете, с моим кодом:

  and product.changeId =  (SELECT max(changeid) from ProductHistory p3 where p2.changeId = p3.changeId))
  • вы пытаетесь получить максимум (changeid), используя коррелированный подзапрос, но вы также присоединяетесь к changeid - это то же самое, что просто получать каждую строку. Предположительно, ты не собирался этого делать?

Также - очевидно, уменьшите количество возвращаемых столбцов до тех, которые вам нужны, а затем выполните следующую команду перед выполнением запроса и проверьте вывод сообщений:

НАСТРОЙКА СТАТИСТИКИ IO ON

Ищите таблицы с высоким логическим чтением и выясните, где индекс поможет вам.

Подсказка: если мой код работает для вас, то в зависимости от нужных вам столбцов вы можете сделать:

создать индекс ix1 (id, версия версии), включить (changeid, ....) в ProductHistory.

Надеюсь, это поможет!

0 голосов
/ 20 июля 2011

У меня такое чувство, что этот запрос займет больше времени, так как количество строк увеличивается, но оно того стоит:

SELECT * FROM 
(
SELECT Col1, Col2, Col3,
ROW_NUMBER() OVER (PARTITION BY ProductHistory.Id ORDER BY Version DESC, ChangeID DESC) AS RowNumber 
FROM ProductHistory
)
WHERE RowNumber = 1
0 голосов
/ 20 июля 2011

Это немного странно, но мне интересно, будет ли работать разбиение:

  SELECT Id
  FROM (
      SELECT Id,
      MAX(version) OVER (PARTITION BY changeId) max_version
      FROM ProductHistory
  ) s
  where version = s.max_version
0 голосов
/ 20 июля 2011

Я думаю, вам нужен индекс на (Id, changeId, version) для этого запроса. Пожалуйста, предоставьте определение таблицы, индексы в таблице сейчас и план запроса для вашего запроса.

0 голосов
/ 20 июля 2011

Говоря в общем, select max () необходимо отсортировать по всей таблице. И вы делаете это дважды

SELECT TOP 1 намного быстрее, но вы должны убедиться, что ваш индекс правильный и у вас правильный ORDER BY. Посмотрим, сможешь ли ты поиграть с этим.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...