Този въпрос беше публикуван в #sqlhelp от Джейк Манске и беше представен на вниманието ми от Ерик Дарлинг.
Не си спомням някога да съм имал проблем с производителността с sys.partitions
. Първоначалната ми мисъл (озвучена от Джоуи Д'Антони) беше, че филтър върху data_compression
колона трябва избягвайте излишното сканиране и намалете времето за изпълнение на заявката с около половината. Този предикат обаче не се избутва надолу и причината за това изисква малко разопаковане.
Защо sys.partitions е бавен?
Ако погледнете определението за sys.partitions
, това е основно това, което Джейк описа – UNION ALL
от всички дялове на columnstore и rowstore, с ТРИ изрични препратки към sys.sysrowsets
(съкратен източник тук):
СЪЗДАЙТЕ ПРЕГЛЕД sys.partitions КАТО С partitions_columnstore(...cols...) AS ( SELECT ...cols..., cmprlevel AS data_compression ... FROM sys.sysrowsets rs ВЪНШНО ПРИЛАГАНЕ OpenRowset(TABLE ALUCOUNT, rs .rowsetid, 0, 0, 0) ct-------- *** ^^^^^^^^^^^^^^ *** LEFT JOIN sys.syspalvalues cl ... КЪДЕ .. sysconv(bit, rs.status &0x00010000) =1 -- Обмислете само основните индекси на columnstore), partitions_rowstore(...cols...) AS ( SELECT ...cols..., cmprlevel AS data_compression ... FROM sys.sysrowsets rs -------- *** ^^^^^^^^^^^^^ *** LEFT JOIN sys.syspalvalues cl ... WHERE ... sysconv(bit, rs .status &0x00010000) =0 -- Игнорирайте базовите индекси на columnstore и осиротелите редове. ) ИЗБЕРЕТЕ ...cols... от partitions_rowstore p ВЪНШНО ПРИЛОЖЕНИЕ OpenRowset(TABLE ALUCOUNT, p.partition_id, 0, 0, p.object_id всички SELECT ...cols... FROM partitions_columnstore като P1 LEFT JOIN (ИЗБЕРЕТЕ ...cols... FROM sys.sysrowsets rs ВЪНШНО ПРИЛОЖЕНИЕ LY OpenRowset(TABLE ALUCOUNT, rs.rowsetid, 0, 0, 0) ct------- *** ^^^^^^^^^^^^^^ *** ) ...предварително>Тази гледна точка изглежда съчетана, вероятно поради опасения за обратна съвместимост. Със сигурност може да бъде пренаписан, за да бъде по-ефективен, по-специално да се позовава само на
sys.sysrowsets
иTABLE ALUCOUNT
обекти веднъж. Но аз или вие не можем да направим много по въпроса в момента.Колоната
cmprlevel
идва отsys.sysrowsets
(префикс псевдоним в препратката към колоната би бил полезен). Бихте се надявали, че предикат срещу колона там логично ще се случи преди всякоOUTER APPLY
и може да предотврати едно от сканирането, но това не се случва. Изпълняване на следната проста заявка:SELECT * FROM sys.partitions AS p INNER JOIN sys.objects AS o ON p.object_id =o.object_id WHERE o.is_ms_shipped =0;Получава следния план, когато има индекси на columnstore в базите данни (щракнете, за да увеличите):
Планирайте за sys.partitions, с налични индекси на columnstore
И следния план, когато няма (щракнете за уголемяване):
Планирайте за sys.partitions, без налични индекси на columnstore
Това са същия приблизителен план, но SentryOne Plan Explorer може да подчертае, когато дадена операция е пропусната по време на изпълнение. Това се случва за третото сканиране в последния случай, но не знам дали има начин да се намали допълнително броя на сканирането по време на изпълнение; второто сканиране се случва дори когато заявката връща нула реда.
В случая на Джейк той има много на обекти, така че извършването на това сканиране дори два пъти е забележимо, болезнено и един път твърде много. И съвсем честно казано не знам дали
TABLE ALUCOUNT
, вътрешно и недокументирано обратно извикване, също трябва да сканира някои от тези по-големи обекти няколко пъти.Поглеждайки назад към източника, се чудех дали има някакъв друг предикат, който може да се предаде на изгледа, който би могъл да принуди формата на плана, но наистина не мисля, че има нещо, което би могло да окаже влияние.
Ще работи ли друг изглед?
Бихме могли обаче да опитаме съвсем различен поглед. Потърсих други изгледи, които съдържат препратки към двата
sys.sysrowsets
иALUCOUNT
, и има няколко, които се показват в списъка, но само два са обещаващи:sys.internal_partitions
иsys.system_internals_partitions
.sys.internal_partitions
Опитах
sys.internal_partitions
първо:SELECT * FROM sys.internal_partitions AS p INNER JOIN sys.objects AS o ON p.object_id =o.object_id WHERE o.is_ms_shipped =0;Но планът не беше много по-добър (щракнете за уголемяване):
План за sys.internal_partitions
Има само две сканирания срещу
sys.sysrowsets
този път, но сканирането така или иначе е без значение, защото заявката не се доближава до създаването на редовете, които ни интересуват. Виждаме редове само за обекти, свързани с columnstore (както се посочва в документацията).sys.system_internals_partitions
Нека опитаме
sys.system_internals_partitions
. Малко съм внимателен относно това, защото не се поддържа (вижте предупреждението тук), но изчакайте малко:SELECT * FROM sys.system_internals_partitions AS p INNER JOIN sys.objects AS o ON p.object_id =o.object_id WHERE o.is_ms_shipped =0;В базата данни с индекси на columnstore има сканиране срещу
sys.sysschobjs
, но сега самоединен сканирайте срещуsys.sysrowsets
(щракнете за увеличаване):Планирайте за sys.system_internals_partitions, с налични индекси на columnstore
Ако изпълним същата заявка в базата данни без индекси на columnstore, планът е още по-опростен, с търсене срещу
sys.sysschobjs
(щракнете за увеличаване):Планирайте за sys.system_internals_partitions, без налични индекси на columnstore
Това обаче не е съвсем това, което търсим, или поне не съвсем това, което търсеше Джейк, защото включва и артефакти от индекси на columnstore. Ако добавим тези филтри, действителният изход вече съответства на нашата по-ранна, много по-скъпа заявка:
SELECT * FROM sys.system_internals_partitions AS p INNER JOIN sys.objects AS o ON p.object_id =o.object_id КЪДЕ o.is_ms_shipped =0 И p.is_columnstore =0 И p.is_orphaned =0;Като бонус, сканирането срещу
sys.sysschobjs
се превърна в търсене дори в базата данни с обекти columnstore. Повечето от нас няма да забележат тази разлика, но ако сте в сценарий като този на Джейк, можете просто (щракнете, за да увеличите):По-опростен план за sys.system_internals_partitions, с допълнителни филтри
sys.system_internals_partitions
разкрива различен набор от колони отsys.partitions
(някои са напълно различни, други имат нови имена), така че, ако консумирате изхода надолу по веригата, ще трябва да се коригирате за тях. Вие също така ще искате да потвърдите, че връща цялата информация, която искате в индексите на rowstore, оптимизирани за памет и columnstore, и не забравяйте за тези досадни купища. И накрая, бъдете готови да пропуснетеs
въвinternals
много, много пъти.Заключение
Както споменах по-горе, този системен изглед не се поддържа официално, така че неговата функционалност може да се промени по всяко време; може също да бъде преместен под специалната администраторска връзка (DAC) или напълно премахнат от продукта. Чувствайте се свободни да използвате този подход, ако
sys.partitions
не работи добре за вас, но моля, уверете се, че имате резервен план. И се уверете, че е документирано като нещо, което регресионно тествате, когато започнете да тествате бъдещи версии на SQL Server или след надграждане, за всеки случай.