Един от наличните алгоритми за свързване на две таблици заедно в SQL Server е вложените цикли. Съединяването с вложени цикли използва един вход за свързване като външна входна таблица и един като вътрешна входна таблица. Външният цикъл итерира външната входна таблица ред по ред. Вътрешният цикъл, изпълняван за всеки външен ред, търси съвпадащи редове във вътрешната входна таблица.
Това се нарича наивно присъединяване на вложени цикли.
Ако имате индекс за условията на присъединяване във вътрешната входна таблица, тогава не е необходимо да изпълнявате вътрешен цикъл за всеки ред от външната таблица. Вместо това можете да предадете стойността от външната таблица като аргумент за търсене и да свържете всички върнати редове на вътрешната таблица с редовете от външната таблица.
Търсенето по вътрешната таблица е произволен достъп. SQL Server, започвайки от версия 2005, има оптимизация на пакетно сортиране (не бъркайте с оператора Sort в пакетен режим за индекси на Columnstore). Целта на оптимизацията е да поръча ключове за търсене от външната таблица, преди да получи данни от вътрешната. По този начин произволният достъп ще бъде последователен.
Планът за изпълнение не показва операцията за групово сортиране като отделен оператор. Вместо това можете да видите свойството Optimized=true в оператора Nested Loops. Ако беше възможно да се види пакетно сортиране като отделен оператор в плана, това би изглеждало така:
В този псевдоплан ние четем данни от неклъстерирания индекс на ix_CustomerID в реда на този ключ за индекс на CustomerID. След това трябва да извършим Key Lookup в клъстерирания индекс, тъй като ix_CustomerID не е покриващ индекс. Key Lookup е клъстерирана операция за търсене на ключ в индекс – произволен достъп. За да го направи последователен, SQL Server може да изпълни групово сортиране по клъстерирания индексен ключ.
За да научите повече за груповото сортиране, моля, вижте моята статия Пакетно сортиране и вложени цикли.
Тази оптимизация осигурява голям тласък с достатъчен брой редове. Можете да прочетете повече за резултатите от теста в блога OPTIMIZED Nested Loops Joins, създаден от Крейг Фридман, разработчик на оптимизатор.
Ако обаче действителният брой редове е по-малък от очаквания, тогава допълнителните разходи за процесора за изграждане на този сорт могат да скрият предимствата му, да увеличат потреблението на процесора и да намалят неговата производителност.
Помислете за този конкретен пример:
use tempdb; go -- create a test table (SalesOrderID - clustered PK) create table dbo.SalesOrder(SalesOrderID int identity primary key, CustomerID int not null, SomeData char(200) not null); go -- add test data with n as (select top(1000000) rn = row_number() over(order by (select null)) from sys.all_columns c1,sys.all_columns c2) insert dbo.SalesOrder(CustomerID, SomeData) select rn%500000, str(rn,100) from n; -- create a clustered index create index ix_c on dbo.Salesorder(CustomerID); go -- the batch sort optimization is enabled by default (Nested Loops: Optimized = true) select * from dbo.SalesOrder with(index(ix_c)) where CustomerID < 1000; -- disable it with the DISABLE_OPTIMIZED_NESTED_LOOP hint (Nested Loops: Optimized = false) select * from dbo.SalesOrder with(index(ix_c)) where CustomerID < 1000 option(use hint('DISABLE_OPTIMIZED_NESTED_LOOP')); go
Върнат резултат:
Бих искал да насоча вниманието ви към различния ред на редовете в изхода. Сървърът връща редове в реда, в който ги обработва, тъй като не сме посочили изрично ORDER BY. В първия случай четем постепенно от индекса ix_c. Въпреки това, за да оптимизираме произволните показания от клъстерирания индекс, ние филтрираме редовете по клъстерирания индексен ключ SalesOrderID. Във втория случай няма сортиране, както и четенията се извършват в реда на ключовете CustomerID на неклъстерирания индекс ix_c.
Разлика от флага за проследяване 2340
Въпреки факта, че документацията посочва флага за проследяване 2340 като еквивалент на намека DISABLE_OPTIMIZED_NESTED_LOOP, това всъщност не е вярно.
Помислете за следния пример, където ще използвам недокументираната команда UPDATE STATISTICS ... WITH PAGECOUNT, за да заблудя оптимизатора, като кажа, че таблицата отнема повече страници, отколкото всъщност има. След това разгледайте тези запитвания:
- Без никакви намеци (добавих MAXDOP, за да поддържам прост план);
- С намека DISABLE_OPTIMIZED_NESTED_LOOP;
- С флага за проследяване 2340.
-- create a huge table update statistics dbo.SalesOrder with pagecount = 100000; go set showplan_xml on; go -- 1. without hints select * from dbo.SalesOrder with(index(ix_c)) where CustomerID < 1000000 option(maxdop 1); -- 2. hint select * from dbo.SalesOrder with(index(ix_c)) where CustomerID < 1000000 option(use hint('DISABLE_OPTIMIZED_NESTED_LOOP'), maxdop 1); -- 3. trace flag select * from dbo.SalesOrder with(index(ix_c)) where CustomerID < 1000000 option(querytraceon 2340, maxdop 1); go set showplan_xml off; go
В резултат на това имаме следните планове:
Вложените цикли имат свойството Optimized =false и в трите плана. Факт е, че увеличавайки ширината на таблицата, ние също така увеличаваме разходите за достъп до данни. Когато цената е достатъчно висока, SQL Server може да използва изричния оператор за сортиране, а не имплицитния оператор за групово сортиране. Можем да видим това в първия план за заявка.
Във втората заявка използвахме подсказката DISABLE_OPTIMIZED_NESTED_LOOP, която изключва неявното групово сортиране. Въпреки това премахва изрично сортиране от отделен оператор.
В третия план можем да видим, че въпреки добавянето на флаг за проследяване 2340, операторът за сортиране съществува.
По този начин разликата на подсказката от флага е следната:намек забранява оптимизацията, като трансформира произволен достъп в сериен в зависимост от това дали сървърът го прилага с имплицитно пакетно сортиране или с отделния оператор Сортиране.
P.S. Плановете може да зависят от оборудването. По този начин, ако не успеете да изпълните тези заявки, опитайте да увеличите или намалите размера на колоната SomeData char(200) в описанието на таблицата dbo.SalesOrder.
Статията е преведена от екипа на Codingsight с разрешението на автора.