Тази серия от пет части се потапя дълбоко в начина, по който се стартират паралелните планове в режим на ред на SQL Server. Тази първа част обхваща ролята на родителската задача (координатор) при подготовката на плана за паралелно изпълнение. Включва инициализиране на всеки оператор и добавяне на скрити профили за събиране на данни за ефективността по време на изпълнение, като действителен брой редове и изминало време.
Настройка
За да предоставим конкретна основа за анализа, ще проследим как конкретна паралелна заявка започва да се изпълнява. Използвах публичния Stack Overflow 2013 база данни (подробности за изтегляне). Желаната форма на план може да бъде получена и срещу по-малкия набор от данни на Stack Overflow 2010, ако това е по-удобно. Може да се изтегли на същия линк. Добавих един неклъстериран индекс:
CREATE NONCLUSTERED INDEX PP ON dbo.Posts ( PostTypeId ASC, CreationDate ASC );
Моята тестова среда е SQL Server 2019 CU9 на лаптоп с 8 ядра и 16GB памет, разпределена за инстанцията. Ниво на съвместимост150 се използва изключително. Споменавам тези подробности, за да ви помогна да възпроизведете целевия план, ако желаете. Основите на паралелното изпълнение на редов режим не са се променили от SQL Server 2005, така че следната дискусия е широко приложима.
Тестовата заявка връща общия брой въпроси и отговори, групирани по месец и година:
WITH MonthlyPosts AS ( SELECT P.PostTypeId, CA.TheYear, CA.TheMonth, Latest = MAX(P.CreationDate) FROM dbo.Posts AS P CROSS APPLY ( VALUES ( YEAR(P.CreationDate), MONTH(P.CreationDate) ) ) AS CA (TheYear, TheMonth) GROUP BY P.PostTypeId, CA.TheYear, CA.TheMonth ) SELECT rn = ROW_NUMBER() OVER ( ORDER BY Q.TheYear, Q.TheMonth), Q.TheYear, Q.TheMonth, LatestQuestion = Q.Latest, LatestAnswer = A.Latest FROM MonthlyPosts AS Q JOIN MonthlyPosts AS A ON A.TheYear = Q.TheYear AND A.TheMonth = Q.TheMonth WHERE Q.PostTypeId = 1 AND A.PostTypeId = 2 ORDER BY Q.TheYear, Q.TheMonth OPTION ( USE HINT ('DISALLOW_BATCH_MODE'), USE HINT ('FORCE_DEFAULT_CARDINALITY_ESTIMATION'), ORDER GROUP, MAXDOP 2 );
Използвах подсказки, за да получа определен план за режим на ред с форма. Изпълнението е ограничено до DOP 2, за да направи някои от детайлите, показани по-късно, по-сбити.
Прогнозният план за изпълнение е (щракнете за уголемяване):
Фон
Оптимизаторът на заявки произвежда единичен компилиран план за партида. Всяко изявление в пакета е маркирано за серийно или паралелно изпълнение, в зависимост от допустимостта и прогнозните разходи.
Паралелен план съдържа размяна (оператори на паралелизъм). Обмените може да се появяват в разпространяващи потоци , преразпределяне на потоци , или събиране на потоци форма. Всеки от тези видове обмен използва едни и същи основни компоненти, просто свързани по различен начин, с различен брой входове и изходи. За повече информация относно паралелното изпълнение на редов режим вижте Планове за паралелно изпълнение – клонове и нишки.
Понижаване на DOP
Степента на паралелизъм (DOP) за паралелен план може да бъде понижена по време на изпълнение, ако е необходимо. Паралелна заявка може да започне с искане на DOP 8, но постепенно да бъде понижена до DOP 4, DOP 2 и накрая DOP 1 поради липса на системни ресурси в този момент. Ако искате да видите това в действие, вижте това кратко видео от Erik Darling.
Изпълнението на паралелен план в една нишка може също да се случи, когато кеширан паралелен план се използва повторно от сесия, която е ограничена до DOP 1 от настройка на средата (например маска на афинитет или управител на ресурс). Вижте Мит:SQL Server кешира сериен план с всеки паралелен план за подробности.
Каквато и да е причината, понижаването на DOP на кеширан паралелен план не води до съставяне на нов сериен план. SQL Server използва повторно съществуващия паралелен план чрез деактивиране обмените. Резултатът е „паралелен“ план, който се изпълнява в една нишка. Обмените все още се появяват в плана, но се заобикалят по време на изпълнение.
SQL Server не може да насърчава сериен план за паралелно изпълнение чрез добавяне на обмен по време на изпълнение. Това ще изисква нова компилация.
Инициализация на паралелен план
Паралелен план включва всички обмени, необходими за използване на допълнителни работни нишки, но има допълнителна работа по настройка необходими по време на изпълнение, преди да започне паралелното изпълнение. Един очевиден пример е, че допълнителни работни нишки трябва да бъдат разпределени за конкретни задачи в рамките на плана, но има много повече от това.
Ще започна от точката, където е извлечен паралелен план от кеша на плана. В този момент съществува само оригиналната нишка, която обработва текущата заявка. Тази нишка понякога се нарича „координаторна нишка“ в паралелни планове, но аз предпочитам термините „родителска задача ” или „работник-родител”. Иначе няма нищо особено в тази тема; това е същата нишка, която връзката използва за обработка на клиентски заявки и изпълнение на серийни планове до завършване.
За да се подчертае, че съществува само една нишка точно сега искам да визуализирате плана в този момент по следния начин:
Ще използвам екранни снимки от Sentry One Plan Explorer почти изключително в тази публикация, но само за този първи преглед ще покажа и SSMS версията:
При всяко представяне ключовата разлика е липсата на икони за паралелизъм на всеки оператор, въпреки че обмените все още присъстват. В момента съществува само родителската задача, изпълняваща се в оригиналната нишка за връзка. Без допълнителни работни нишки все още са запазени, създадени или възложени задачи. Запазете горното представяне на плана пред ума си, докато вървим напред.
Създаване на изпълнимия план
Планът в този момент по същество е само шаблон който може да се използва като основа за всяко бъдещо изпълнение. За да го подготви за конкретно изпълнение, SQL Server трябва да попълни стойности по време на изпълнение като текущия потребител, контекст на транзакцията, стойности на параметри, идентификатори за всякакви обекти, създадени по време на изпълнение (например временни таблици и променливи) и т.н.
За паралелен план SQL Server трябва да извърши доста допълнителна подготвителна работа, за да доведе вътрешната машина до точката, в която изпълнението може да започне. Работната нишка на родителската задача е отговорна за извършването на почти цялата тази работа (и със сигурност цялата работа, която ще разгледаме в част 1).
Процесът на трансформиране на шаблона на план за конкретно изпълнение е известен като създаване на изпълним план . Понякога е трудно да се поддържа терминологията ясна, защото термините често са претоварени и неправилно прилагани (дори от Microsoft), но ще направя всичко възможно да бъда възможно най-последователен.
Контексти на изпълнение
Можете да мислите за контекст на изпълнение като шаблон на план, попълнен с цялата специфична информация по време на изпълнение, необходима на конкретна нишка. Изпълнимият план за сириен Инструкцията се състои от един контекст на изпълнение, където една нишка изпълнява целия план.
Аналел изпълним план съдържа колекция от изпълнителни контексти :Един за родителската задача и един за нишка във всеки паралелен клон. Всяка допълнителна паралелна работна нишка изпълнява своята част от цялостния план в рамките на собствен контекст на изпълнение. Например, паралелен план с три клона, работещи в DOP 8, има (1 + (3 * 8)) =25 контекста за изпълнение. Серийните контексти на изпълнение се кешират за повторна употреба, но допълнителните контексти на паралелно изпълнение не са.
Родителската задача винаги съществува преди всякакви допълнителни паралелни задачи, така че й се присвоява нулев контекст на изпълнение . Изпълнителните контексти, използвани от паралелни работници, ще бъдат създадени по-късно, след като родителският контекст бъде напълно инициализиран. Допълнителните контексти са клонирани от родителския контекст, след което персонализирани за тяхната конкретна задача (това е разгледано в част 2).
Има редица дейности, включени в стартирането на нулев контекст на изпълнение. Непрактично е да се опитваме да ги изброим всички, но ще бъде полезно да покрием някои от основните, приложими към нашата тестова заявка. Все още ще има твърде много за един списък, така че ще ги разделя на (донякъде произволни) секции:
1. Инициализация на родителския контекст
Когато изпратим изявлението за изпълнение, контекстът на родителската задача (нулев контекст на изпълнение) се инициализира с:
- Препратка към основната транзакция (изрично, имплицитно или автоматично извършване). Паралелните работници ще изпълняват подтранзакции, но всички те са в обхват в рамките на основната транзакция.
- Списък с параметри на израза и техните текущи стойности.
- Основен обект на памет (PMO), използван за управление на предоставените и разпределения на памет.
- Свързана карта на операторите (възли на заявка) в изпълнимия план.
- Фабрика за всеки задължителен голям обект дръжки (blob).
- Класове за заключване за проследяване на множество заключвания, задържани за определен период по време на изпълнение. Не всички планове изискват класове за заключване тъй като операторите с пълно поточно предаване обикновено заключват и отключват отделни редове последователно.
- Прогнозното разпределение на паметта за заявката.
- Предоставяне на памет в режим на ред обратна връзка структури за всеки оператор (SQL Server 2019).
Много от тези неща ще бъдат използвани или препращани от паралелни задачи по-късно, така че първо трябва да съществуват в родителския обхват.
2. Метаданни на родителския контекст
Следващите основни изпълнявани задачи са:
- Проверка на прогнозната цена на заявката е в рамките на ограничението, зададено от опциите за конфигурация на ограничението на разходите за управител на заявка.
- Актуализиране на използването на индекса записи – изложени чрез
sys.dm_db_index_usage_stats
. - Създаване на кеширани стойности на изрази (константи по време на изпълнение).
- Създаване на списък с профилиращи оператори използва се за събиране на показатели по време на изпълнение, като брой редове и времена, ако това е било поискано за текущото изпълнение. Самите профили още не са създадени, а само списъкът.
- Правете моментна снимка на изчаквания за функцията за изчакване на сесия, изложена чрез
sys.dm_exec_session_wait_stats
.
3. DOP и предоставяне на памет
Контекстът на родителската задача сега:
- Изчислява степента на паралелизъм по време на изпълнение (DOP ). Това се влияе от броя на безплатните работници (вижте „Понижаване на DOP“ по-рано), където те могат да бъдат поставени между възли, и редица флагове за проследяване.
- Резервира необходимия брой нишки. Тази стъпка е чисто счетоводство – самите нишки може да не съществуват в този момент. SQL Server следи максималния брой нишки, които е разрешено да има. Резервиране на нишки изважда от това число. Когато нишките приключат, максималният брой се увеличава отново.
- Задава време за изчакване на предоставяне на памет .
- Изчислява предоставената памет, включително паметта, необходима за обменните буфери.
- Придобива предоставената памет чрез подходящия семафор за ресурси .
- Създава мениджърски обект за обработка на паралелни подпроцеси . Задачата-родител е процесът от най-високо ниво; допълнителните задачи са известни също като подпроцеси .
Докато нишките са „резервирани“ в този момент, SQL Server все още може да срещне THREADPOOL
изчаква по-късно, когато се опита да използва една от „резервираните“ нишки. Резервацията гарантира, че SQL Server ще остане около конфигурирания си максимален брой нишки по всяко време, но физическата нишка може да не е налична веднага от пула нишки . Когато това се случи, операционната система ще трябва да стартира нова нишка, което може да отнеме известно време. За повече информация вижте Unusual THREADPOOL Waits от Джош Дарнел.
4. Настройка за сканиране на заявка
Плановете за редовия режим се изпълняват итеративно мода, започвайки от корена. Планът, който имаме в момента, все още не е способен на този начин на изпълнение. Той все още е до голяма степен шаблон, дори ако вече съдържа доста специфична за изпълнението информация.
SQL Server трябва да преобразува текущата структура в дърво от итератори , всеки с методи като Open
, GetRow
и Close
. Методите на итератора са свързани с техните деца чрез указатели на функции. Например, извикване на GetRow
в корена рекурсивно извиква GetRow
на дъщерни оператори, докато се достигне ниво на листа и ред започне да „бълбука“ на дървото. За освежаване на подробностите вижте Итератори, планове на заявки и защо се изпълняват назад.
Край на част 1
Постигнахме добър напредък в настройването на контекста на изпълнение за родителската задача. В част 2 ще проследим, докато SQL Server конструира дървото за сканиране на заявки, необходимо за итеративно изпълнение.