подзапрос или левое соединение с группой, по которой быстрее? - PullRequest
7 голосов
/ 09 сентября 2011

Query Execution Plan Мне нужно показать итоговую сумму вместе со столбцом итогов в моем приложении ... поэтому я использовал следующие запросы для поиска промежуточной суммы ... и я обнаружил, что оба работают в соответствии с моими потребностями. в одном я использовал левое соединение с группой, а в другом я использовал подзапрос.

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

declare @tmp table(ind int identity(1,1),col1 int)
insert into @tmp
select 2
union
select 4
union
select 7
union 

select 5
union
select 8
union 
select 10



SELECT t1.col1,sum( t2.col1)
FROM @tmp AS t1 LEFT JOIN @tmp t2 ON t1.ind>=t2.ind
group by t1.ind,t1.col1


select t1.col1,(select sum(col1) from  @tmp as t2 where t2.ind<=t1.ind)
from @tmp as t1

Ответы [ 3 ]

5 голосов
/ 17 сентября 2011

Отличным ресурсом для расчета промежуточных итогов в SQL Server является этот документ Ицик Бен Ган, который был представлен команде SQL Server в рамках его кампании, чтобы предложение OVER было расширено от егоначальная реализация SQL Server 2005.В нем он показывает, как, попав в десятки тысяч строк, курсоры выполняют решения на основе множеств.SQL Server 2012 действительно расширил предложение OVER, сделав этот тип запроса намного проще.

SELECT col1,
       SUM(col1) OVER (ORDER BY ind ROWS UNBOUNDED PRECEDING)
FROM   @tmp 

Поскольку вы работаете на SQL Server 2005, однако он вам недоступен.

АдамМачаник показывает здесь , как можно использовать CLR для улучшения производительности стандартных курсоров TSQL.

Для этого определения таблицы

CREATE TABLE RunningTotals
(
ind int identity(1,1) primary key,
col1 int
)

Я создаю таблицы с обоими 2000и 10 000 строк в базе данных с ALLOW_SNAPSHOT_ISOLATION ON и одной с отключенной настройкой (причина этого в том, что мои первоначальные результаты были в БД с настройкой, которая привела к удивительному аспекту результатов).

Кластерные индексы для всех таблиц имели только одну корневую страницу.Количество листовых страниц для каждого показано ниже.

+-------------------------------+-----------+------------+
|                               | 2,000 row | 10,000 row |
+-------------------------------+-----------+------------+
| ALLOW_SNAPSHOT_ISOLATION OFF  |         5 |         22 |
| ALLOW_SNAPSHOT_ISOLATION ON   |         8 |         39 |
+-------------------------------+-----------+------------+

Я проверил следующие случаи (ссылки показывают планы выполнения)

  1. Левое объединение и группировка по
  2. коррелированный подзапрос план строки 2000 , 10000 план строки
  3. CTE из ответа Микаэля (обновлено)
  4. CTE ниже

Причина включения дополнительной опции CTE была в том, чтобы предоставить решение CTE, которое все еще работало бы, если столбец ind не был гарантированпоследовательный.

SET STATISTICS IO ON;
SET STATISTICS TIME ON;
DECLARE @col1 int, @sumcol1 bigint;

WITH    RecursiveCTE
AS      (
        SELECT TOP 1 ind, col1, CAST(col1 AS BIGINT) AS Total
        FROM RunningTotals
        ORDER BY ind
        UNION   ALL
        SELECT  R.ind, R.col1, R.Total
        FROM    (
                SELECT  T.*,
                        T.col1 + Total AS Total,
                        rn = ROW_NUMBER() OVER (ORDER BY T.ind)
                FROM    RunningTotals T
                JOIN    RecursiveCTE R
                        ON  R.ind < T.ind
                ) R
        WHERE   R.rn = 1
        )
SELECT  @col1 =col1, @sumcol1=Total
FROM    RecursiveCTE
OPTION  (MAXRECURSION 0);

Во все запросы добавлено CAST(col1 AS BIGINT), чтобы избежать ошибок переполнения во время выполнения.Кроме того, для всех них я назначил результаты переменным, как указано выше, чтобы исключить время, затрачиваемое на отправку результатов из рассмотрения.

Результаты

+------------------+----------+--------+------------+---------------+------------+---------------+-------+---------+
|                  |          |        |          Base Table        |         Work Table         |     Time        |
+------------------+----------+--------+------------+---------------+------------+---------------+-------+---------+
|                  | Snapshot | Rows   | Scan count | logical reads | Scan count | logical reads | cpu   | elapsed |
| Group By         | On       | 2,000  | 2001       | 12709         |            |               | 1469  | 1250    |
|                  | On       | 10,000 | 10001      | 216678        |            |               | 30906 | 30963   |
|                  | Off      | 2,000  | 2001       | 9251          |            |               | 1140  | 1160    |
|                  | Off      | 10,000 | 10001      | 130089        |            |               | 29906 | 28306   |
+------------------+----------+--------+------------+---------------+------------+---------------+-------+---------+
| Sub Query        | On       | 2,000  | 2001       | 12709         |            |               | 844   | 823     |
|                  | On       | 10,000 | 2          | 82            | 10000      | 165025        | 24672 | 24535   |
|                  | Off      | 2,000  | 2001       | 9251          |            |               | 766   | 999     |
|                  | Off      | 10,000 | 2          | 48            | 10000      | 165025        | 25188 | 23880   |
+------------------+----------+--------+------------+---------------+------------+---------------+-------+---------+
| CTE No Gaps      | On       | 2,000  | 0          | 4002          | 2          | 12001         | 78    | 101     |
|                  | On       | 10,000 | 0          | 20002         | 2          | 60001         | 344   | 342     |
|                  | Off      | 2,000  | 0          | 4002          | 2          | 12001         | 62    | 253     |
|                  | Off      | 10,000 | 0          | 20002         | 2          | 60001         | 281   | 326     |
+------------------+----------+--------+------------+---------------+------------+---------------+-------+---------+
| CTE Alllows Gaps | On       | 2,000  | 2001       | 4009          | 2          | 12001         | 47    | 75      |
|                  | On       | 10,000 | 10001      | 20040         | 2          | 60001         | 312   | 413     |
|                  | Off      | 2,000  | 2001       | 4006          | 2          | 12001         | 94    | 90      |
|                  | Off      | 10,000 | 10001      | 20023         | 2          | 60001         | 313   | 349     |
+------------------+----------+--------+------------+---------------+------------+---------------+-------+---------+

И коррелированный подзапрос, и GROUP BYверсия использует «треугольные» объединения вложенных циклов, управляемые сканированием кластеризованного индекса в таблице RunningTotals (T1), и для каждой строки, возвращаемой этим сканированием, поиск обратно в таблицу (T2), самосоединение в T2.ind<=T1.ind.

Это означает, что одни и те же строки обрабатываются неоднократно.Когда строка T1.ind=1000 обработана, самообъединение извлекает и суммирует все строки с ind <= 1000, затем для следующей строки, где T1.ind=1001 те же 1000 строк извлекаются снова и суммируются вместе с одной дополнительнойстрока и т. д.

Общее количество таких операций для таблицы 2000 строк составляет 2 001 000, для строк 10 000 - 50 005 000 или более (n² + n) / 2, что явно растет в геометрической прогрессии.

В случае с 2000 строками основное различие между GROUP BY и версиями подзапроса состоит в том, что первый имеет агрегат потока после объединения и поэтому в него входят три столбца (T1.ind, T2.col1, T2.col1) и свойство GROUP BY, равное T1.ind, тогда как последнее рассчитывается как скалярный агрегат с агрегатом потока перед объединением, в него подается только T2.col1 и вообще не имеет свойства GROUP BY.Можно заметить, что это более простое расположение дает ощутимое преимущество с точки зрения сокращения процессорного времени.

Для случая с 10 000 строк есть дополнительное различие в плане подзапроса.Он добавляет нетерпеливая шпуля , которая копирует все значения ind,cast(col1 as bigint) в tempdb.В случае, когда используется изоляция моментальных снимков, это работает более компактно, чем структура кластерного индекса, и чистый эффект заключается в сокращении числа операций чтения примерно на 25% (поскольку базовая таблица сохраняет довольно много пустого пространства для информации о версиях),когда эта опция выключена, она работает менее компактно (предположительно из-за разницы bigint против int) и дает больше результатов чтения.Это уменьшает разрыв между подзапросом и группой по версиям, но подзапрос все еще выигрывает.

Однако явным победителем стал Рекурсивный CTE.Для версии «без пробелов» логические чтения из базовой таблицы теперь 2 x (n + 1), отражающие индекс n поиска в двухуровневом индексе для извлечения всех строк плюс дополнительной в конце, которая ничего не возвращает и завершает рекурсию,Это по-прежнему означало 20 002 чтения для обработки таблицы из 22 страниц!

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

4 голосов
/ 13 сентября 2011

Я думаю, вы найдете рекурсивный CTE немного быстрее.

;with C as
(
  select t.ind,
         t.col1,
         t.col1 as Total
  from @tmp as t
  where t.ind = 1
  union all
  select t.ind,
         t.col1,
         C.Total + t.col1 as Total
  from @tmp as t
    inner join C
      on C.ind + 1 = t.ind
)
select C.col1,
       C.Total
from C

любой другой метод, который быстрее

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

0 голосов
/ 12 сентября 2011

Ваш вопрос не был очень точным, поэтому вот несколько общих правил, на которые следует ответить.

  • Добавить индекс. На вашем упрощенном примере это будетбыть на col1.
  • Используйте EXPLAIN для сравнения запросов. Это даст вам подсказки о том, что произойдет с большими данными.
  • Тест включен (реальные данные и оптимизировать ваш сервер .Время запроса будет зависеть от многих параметров.Например, ваши данные помещаются в памяти сервера?Или ваши буферы настроены достаточно большими?
  • Использует кэши для перенаправления запросов с сервера БД. Memcached - наиболее часто используемый кэш на уровне приложений, но другие кэши существуют на каждом уровне.
...