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

Производителност на sys.partitions

sys.partitions изглежда е СЪЕДИНЕНИЕ ВСИЧКИ от два набора резултати (съхранение на редове и хранилище на колони) и повечето от моите заявки водят до две сканирания на sysrowsets. Има ли някакъв филтър, който мога да поставя при заявка за sys.partitions, ако знам, че редът, който търся, е rowstore?

Този въпрос беше публикуван в #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 или след надграждане, за всеки случай.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Как да получите деня от дата в T-SQL

  2. Как да използвате функцията SQL SUM

  3. Лоши навици :Фокусиране само върху дисковото пространство при избора на ключове

  4. Среща на върха за MVP за 2013 г.:Бърз преглед и поглед напред

  5. Защитена ли е вашата база данни? Помисли отново