Sqlserver
 sql >> база данни >  >> RDS >> Sqlserver

подзаявка или leftjoin с група с кое е по-бързо?

Страхотен ресурс за изчисляване на текущи суми в 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 и един с изключена тази настройка (Причината за това е, че моите първоначални резултати бяха в DB с включена настройка, което доведе до озадачаващ аспект на резултатите).

Клъстерираните индекси за всички таблици имаха само 1 основна страница. Броят листни страници за всеки е показан по-долу.

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

Тествах следните случаи (Връзките показват планове за изпълнение)

  1. Наляво присъединяване и групиране по
  2. Свързана подзаявка план с 2000 реда ,план с 10 000 реда
  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, за 10k реда 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 index търси в индекса на 2 нива, за да извлече всички редове плюс допълнителния в края, който не връща нищо и прекратява рекурсията. Това все пак означаваше 20 002 четения за обработка на таблица от 22 страници!

Четенията на логическата работна таблица за рекурсивната CTE версия са много високи. Изглежда, че работи при 6 четения на работна маса на ред на източника. Те идват от макарата на индекса, която съхранява изхода от предишния ред, след което се чете отново при следващата итерация (добро обяснение за това от Umachandar Jayachandran тук ). Въпреки високия брой, това все още е най-доброто представяне.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. MS SQL Server 2005 - Съхранената процедура се поврежда спонтанно

  2. Как да запазвате документи като PDF,Docx,xls в sql server 2008

  3. Най-бързият начин да проверите дали знакът е цифра?

  4. Разлика между неявна и явна транзакция

  5. Временни таблици на SQL Server и групиране на връзки