SUM / GROUP производительность и первичный ключ - PullRequest
0 голосов
/ 09 ноября 2009

У меня есть таблица myTable с myGuid (uniqueidentifier), myValues (float), myGroup (integer) и куча других полей, которые сейчас не важны. Я хочу сделать что-то простое, как:

SELECT SUM(myValues)
  FROM myTable
 WHERE myGuid IN (SELECT * FROM ##test)
 GROUP BY myGroup

##test - это просто временная таблица с одним полем (guid_filter) содержащий кучу уникальных идентификаторов.

Теперь вот странная вещь:

  • Когда я создаю myTable с myGuid в качестве первичного ключа (который выглядит как очевидно), запрос медленный ( 8-12 с ).

  • Когда я создаю myTable с myAutoInc, целочисленное автоинкремент поле, как первичный ключ, запрос быстрый (~ 2 с), хотя Предложение WHERE по-прежнему фильтруется myGuid. (у myGuid есть просто "нормальный" некластеризованный индекс в этом сценарии.)

Есть ли логическое объяснение этому? Мое (наивное) предположение было что первый вариант быстрее, так как SQL Server может использовать guid для ищите myValues ​​вместо того, чтобы проходить через guid -> myAutoInc -> myValues. Таким образом, результат был очень удивительным для меня.

Вот вывод SHOWPLAN_TEXT. Медленный сценарий ( план XML-запроса ): (РЕДАКТИРОВАТЬ: Обновлено, спасибо Ремусу за то, что он заметил ненужный дополнительный некластеризованный индекс в myGuid)

|--Compute Scalar(DEFINE:([Expr1007]=CASE WHEN [Expr1015]=(0) THEN NULL ELSE [Expr1016] END))
     |--Stream Aggregate(GROUP BY:([myDB].[dbo].[myTable].[myGroup]) DEFINE:([Expr1015]=COUNT_BIG([myDB].[dbo].[myTable].[myValues]), [Expr1016]=SUM([myDB].[dbo].[myTable].[myValues])))
          |--Sort(DISTINCT ORDER BY:([myDB].[dbo].[myTable].[myGroup] ASC, [myDB].[dbo].[myTable].[myGuid] ASC))
               |--Nested Loops(Inner Join, OUTER REFERENCES:([tempdb].[dbo].[##test].[guid_filter], [Expr1014]) OPTIMIZED WITH UNORDERED PREFETCH)
                    |--Table Scan(OBJECT:([tempdb].[dbo].[##test]))
                    |--Clustered Index Seek(OBJECT:([myDB].[dbo].[myTable].[PK__myTable__2334397B]), SEEK:([myDB].[dbo].[myTable].[myGuid]=[tempdb].[dbo].[##test].[guid_filter]) ORDERED FORWARD)

Table 'myTable'. Scan count 0, logical reads 38046, physical reads 1, read-ahead reads 6914, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table '##test'. Scan count 1, logical reads 23, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

Быстрый сценарий ( План XML-запроса ):

|--Compute Scalar(DEFINE:([Expr1007]=CASE WHEN [globalagg1009]=(0) THEN NULL ELSE [globalagg1011] END))
     |--Stream Aggregate(GROUP BY:([myDB].[dbo].[myTable].[myGroup]) DEFINE:([globalagg1009]=SUM([partialagg1008]), [globalagg1011]=SUM([partialagg1010])))
          |--Parallelism(Gather Streams, ORDER BY:([myDB].[dbo].[myTable].[myGroup] ASC))
               |--Stream Aggregate(GROUP BY:([myDB].[dbo].[myTable].[myGroup]) DEFINE:([partialagg1008]=COUNT_BIG([myDB].[dbo].[myTable].[myValues]), [partialagg1010]=SUM([myDB].[dbo].[myTable].[myValues])))
                    |--Sort(DISTINCT ORDER BY:([myDB].[dbo].[myTable].[myGroup] ASC, [myDB].[dbo].[myTable].[myAutoInc] ASC))
                         |--Parallelism(Repartition Streams, Hash Partitioning, PARTITION COLUMNS:([myDB].[dbo].[myTable].[myGroup], [myDB].[dbo].[myTable].[myAutoInc]))
                              |--Nested Loops(Inner Join, OUTER REFERENCES:([myDB].[dbo].[myTable].[myAutoInc], [Expr1017]) OPTIMIZED WITH UNORDERED PREFETCH)
                                   |--Nested Loops(Inner Join, OUTER REFERENCES:([tempdb].[dbo].[##test].[guid_filter], [Expr1016]) OPTIMIZED WITH UNORDERED PREFETCH)
                                   |    |--Table Scan(OBJECT:([tempdb].[dbo].[##test]))
                                   |    |--Index Seek(OBJECT:([myDB].[dbo].[myTable].[myGuid]), SEEK:([myDB].[dbo].[myTable].[myGuid]=[tempdb].[dbo].[##test].[guid_filter]) ORDERED FORWARD)
                                   |--Clustered Index Seek(OBJECT:([myDB].[dbo].[myTable].[PK__myTable__2334397B]), SEEK:([myDB].[dbo].[myTable].[myAutoInc]=[myDB].[dbo].[myTable].[myAutoInc]) LOOKUP ORDERED FORWARD)

Table 'myTable'. Scan count 0, logical reads 66988, physical reads 48, read-ahead reads 2515, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table '##test'. Scan count 5, logical reads 23, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

Ответы [ 2 ]

3 голосов
/ 09 ноября 2009

Глядя на план в «медленном» случае, он показывает, что запрос выполняет поиск по индексу [myDB].[dbo].[myTable].[myGuid] с последующим поиском кластерного индекса по [myDB].[dbo].[myTable].[PK__myTable__2334397B]. Это имеет смысл только в том случае, если вы создали как некластеризованный индекс для myTable (myGuid), так и объявили myTable (myGuid) в качестве ключа кластеризованного индекса (это выглядит так, судя по типичному объявлению 'PRIMARY KEY', автоматически сгенерированному соглашению об именах имен объекта кластеризованного индекса 'PK _...').

Кроме этого, планы очень похожи, и они оба довольно плохие, так как включают в себя СОРТ. Разница в ширине столбца autoInc и GUID, участвующего в потенциально большей ширине некластеризованного индекса в первом случае , может объяснить разницу, но я сомневаюсь, что это полная история.

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

Также, очевидно, убедитесь, что вы сравниваете одинаковое ## тестовое содержимое, и кеш буферного пула в обоих случаях одинаково прогревается. Запускайте DBCC FREESYSTEMCACHE('All') перед каждым тестом, затем выполняйте запрос не менее 5 раз, игнорируя первый запуск (это будет прогон, который нагревает буферный пул).

Также, как уже заметил Артур, наличие гарантии заказа на ## test (т. Е. Кластеризованный ключ) может ускорить процесс, поскольку вложенные циклы могут быть заменены объединением слиянием, если ## test content достаточно велико. Если у ## temp только несколько строк, то вложенный цикл лучше и порядок не имеет значения.

3 голосов
/ 09 ноября 2009

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

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

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