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

Защо оптимизаторът не използва знания за буферен пул

SQL Server има базиран на разходите оптимизатор, който използва знания за различните таблици, включени в заявка, за да произведе това, което реши, че е най-оптималният план за времето, с което разполага по време на компилацията. Това знание включва всички съществуващи индекси и техните размери и каквато и статистика на колоните да съществува. Част от това, което влиза в намирането на оптимален план за заявка, се опитва да сведе до минимум броя на физическите четения, необходими по време на изпълнението на плана.

Едно нещо, което ме питаха няколко пъти, е защо оптимизаторът не взема предвид какво има в буферния пул на SQL Server, когато компилира план за заявка, тъй като със сигурност това може да направи заявката да се изпълнява по-бързо. В тази публикация ще обясня защо.

Определяне на съдържанието на буферния пул

Първата причина, поради която оптимизаторът игнорира буферния пул е, че е нетривиален проблем да се разбере какво има в буферния пул поради начина, по който е организиран буферният пул. Страниците с файлове с данни се контролират в буферния пул от малки структури от данни, наречени буфери, които проследяват неща като (неизчерпателен списък):

  • Идентификационният номер на страницата (номер на файла:номер на страница във файл)
  • Последният път, когато страницата е била препращана (използвана от мързеливия писател, за да помогне за внедряването на най-малко наскоро използвания алгоритъм, който създава свободно пространство, когато е необходимо)
  • Местоположението на паметта на 8KB страницата в буферния пул
  • Независимо дали страницата е мръсна или не (мръсна страница има промени на нея, които все още не са записани обратно в трайно хранилище)
  • Разпределителната единица, към която принадлежи страницата (обяснено тук) и идентификаторът на разпределителната единица може да се използва, за да се разбере каква таблица и индекс е част от страницата

За всяка база данни, която има страници в буферния пул, има хеш списък със страници, в ред на идентификатора на страницата, който може бързо да се търси, за да се определи дали дадена страница вече е в паметта или трябва да се извърши физическо четене. Нищо обаче не позволява лесно на SQL Server да определи какъв процент от нивото на листа за всеки индекс на таблица вече е в паметта. Кодът ще трябва да сканира целия списък с буфери за базата данни, търсейки буфери, които картографират страниците за въпросната единица за разпределение. И колкото повече страници са в паметта на база данни, толкова по-дълго ще отнеме сканирането. Би било твърде скъпо да се направи като част от компилацията на заявка.

Ако се интересувате, написах публикация преди известно време с някакъв T-SQL код, който сканира буферния пул и дава някои показатели, използвайки DMV sys.dm_os_buffer_descriptors .

Защо използването на съдържанието на буферния пул би било опасно

Нека се преструваме, че *има* високоефективен механизъм за определяне на съдържанието на буферния пул, който оптимизаторът може да използва, за да му помогне да избере кой индекс да използва в план за заявка. Хипотезата, която ще проуча е, че ако оптимизаторът знае достатъчно, че по-малко ефективен (по-голям) индекс вече е в паметта, в сравнение с най-ефективния (по-малък) индекс за използване, той трябва да избере индекса в паметта, защото ще намалете броя на необходимите физически четения и заявката ще работи по-бързо.

Сценарият, който ще използвам, е следният:таблица BigTable има два неклъстерирани индекса, Index_A и Index_B, като и двата напълно покриват конкретна заявка. Заявката изисква пълно сканиране на нивото на листа на индекса, за да се извлекат резултатите от заявката. Таблицата има 1 милион реда. Index_A има 200 000 страници на ниво лист, а Index_B има 1 милион страници на ниво лист, така че пълното сканиране на Index_B изисква обработка пет пъти повече страници.

Създадох този измислен пример на лаптоп, работещ със SQL Server 2019 с 8 процесорни ядра, 32 GB памет и твърдотелни дискове. Кодът е както следва:

СЪЗДАДЕТЕ ТАБЛИЦА BigTable ( c1 BIGINT IDENTITY, c2 AS (c1 * 2), c3 CHAR (1500) DEFAULT 'a', c4 CHAR (5000) DEFAULT 'b');GO INSERT INTO BigTable DEFAULT STONS;GO 1000000 CREATE НЕКЛУСТРИРАН ИНДЕКС Индекс_A НА BigTable (c2) ВКЛЮЧВА (c3);-- 5 записа на страница =200 000 странициGO СЪЗДАЙТЕ НЕКЛУСТРИРАН ИНДЕКС Индекс_B НА BigTable (c2) ВКЛЮЧВАТЕ (c4);-- 1 запис на страница =1 милион CHECKPO INT; /предварително> 

И тогава засегнах измислените заявки:

DBCC DROPCLEANBUFFERS;GO -- Индекс_A не е в паметтаSELECT SUM (c2) FROM BigTable WITH (INDEX (Index_A));GO-- CPU време =796 ms, изминало време =764 ms -- Index_A в памет SELECT SUM (c2) ОТ BigTable WITH (ИНДЕКС (Индекс_A));GO-- Време на процесора =312 ms, изминало време =52 ms DBCC DROPCLEANBUFFERS;GO -- Индекс_B не е в паметта SELECT SUM (c2) ОТ BigTable WITH (ИНДЕКС (Индекс_B));GO- - Време на процесора =2952 ms, изминало време =2761 ms -- Индекс_B в паметИЗБИРАНЕ СУМ (c2) ОТ BigTable WITH (ИНДЕКС (Индекс_B));GO-- Време на процесора =1219 ms, изминало време =149 ms

Можете да видите, когато нито един от индексите не е в паметта, Index_A е лесно най-ефективният индекс за използване, с изминало време на заявка от 764ms срещу 2761ms при използване на Index_B, и същото е вярно, когато и двата индекса са в паметта. Ако обаче Index_B е в паметта, а Index_A не е, ако заявката използва Index_B (149ms), тя ще работи по-бързо, отколкото ако използва Index_A (764ms).

Сега нека позволим на оптимизатора да базира избора на план върху това, което е в буферния пул...

Ако Index_A не е предимно в паметта, а Index_B е предимно в паметта, би било по-ефективно да се компилира планът на заявката, за да се използва Index_B, за заявка, изпълнявана в този момент. Въпреки че Index_B е по-голям и ще има нужда от повече цикли на процесора за сканиране, физическите четения са много по-бавни от допълнителните цикли на процесора, така че по-ефективният план за заявка минимизира броя на физическите четения.

Този аргумент е валиден само и планът за заявка „използване на Index_B“ е само по-ефективен от план за заявка „използване на Index_A“, ако Index_B остава предимно в паметта, а Index_A остава предимно не в паметта. Веднага щом по-голямата част от Index_A е в паметта, планът за заявка „use Index_A“ ще бъде по-ефективен, а планът за заявка „use Index_B“ е грешен избор.

Ситуациите, в които компилираният план „използване на индекс_B“ е по-малко ефективен от базирания на разходите план „използване на индекс_A“ са (обобщаващи):

  • Index_A и Index_B са в паметта:компилираният план ще отнеме почти три пъти повече време
  • Нито един индекс не е резидентен в паметта:компилираният план отнема 3,5 пъти повече време
  • Index_A е резидентен в паметта, а Index_B не е:всички физически четения, извършени от плана, са външни и това ще отнеме огромни 53 пъти повече време

Резюме

Въпреки че в нашето мисловно упражнение оптимизаторът може да използва знанията за буферния пул, за да компилира най-ефективната заявка в един момент, това би било опасен начин за стимулиране на компилацията на план поради потенциалната нестабилност на съдържанието на буферния пул, което прави бъдещата ефективност на кешираният план е много ненадежден.

Не забравяйте, че работата на оптимизатора е бързо да намери добър план, а не непременно най-добрия план за 100% от всички ситуации. Според мен оптимизаторът на SQL Server прави правилното нещо, като игнорира действителното съдържание на буферния пул на 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. Съхранена процедура за получаване на състоянието на индексите във всички бази данни

  2. Как да създадете таблица от SQL заявка

  3. Модел на данни на платформата за равностойно кредитиране

  4. SQL АКТУАЛИЗАЦИЯ за начинаещи

  5. SQL ПОРЪЧАЙ ПО:5-те правила, които не трябва да се правят за сортиране на данни като професионалист