Один з найпростіших способів прискорення запитів - це їх расспараллеліваніе.
Це робиться досить просто через:
* Хінт
* Через установки сесій:
- N число потоків
У теорії цього досить, щоб прискорити запит в рази.
Але є ряд ситуацій в яких паралельність навпаки заважає.
Для початку розберемося з термінологією - подивимося на паралельний план з join 2 таблиць:
- запит 1
Таблиця T2 має особливість: fk_id_skew нерівномірно заповнений і має перекіс в сторону 1 - вона зустрічається значно частіше за інших.
- запит 2
Отже, виконав простий запит:
- запит 3
* Regexp_replace в цьому запиті потрібен, щоб дані добиралися не миттєво і було видно в статистиці витрати CPU.
* Хинти вставлені, щоб запит в плані виглядав також як написаний тут.
Час виконання виконання запиту = 49сек.
Додамо хинт parallel (8) замін no_parallel.
Час виконання = 8с. що в 6 разів швидше.
Розберемо для розуміння план запиту:
- план 1
Основоположні фази:
* PX BLOCK ITERATOR - читання таблиці частинами в кілька потоків
* PX SEND - 1 потік посилає дані іншому. Важливо знати, що тільки один producer (PX SEND) може бути активний в один час, що накладає обмеження на паралельний план виконання, детальніше: Друга частина з розподілу даних в паралельних запитах
** RANGE - дані будуть розбиті на діапазони (часто при сортуванні)
** HASH - діапазон даних на основі їх хеша (hash join, group by)
** RANDOM - помилкової відправки
** BROADCAST - відправка таблиці в усі потоки (часто на маленькій таблиці, спільно з подальшою ROUND ROBIN правої таблиці. Може бути проблемою продуктивності, якщо ліва таблиця значно більше, ніж вказано в статистиці, тому що дані дублюються в усі потоки)
** ROUND ROBIN - дані відправляються в потоки по колу
Про способи розподілу даних по потоках потрібно поговорити окремо:
Варто зауважити, що дані б'ються за значеннями в стовпчиках рядків, а не просто по рядках.
Це потрібно, щоб один і той самий діапазон даних з різних таблиць потрапив в один потік для join.
Якби Oracle робив не так, то в 1 потік могли б потрапити абсолютно різні дані і join не можна було б зробити.
На це варто звернути увагу, тому що це може бути і причиною вповільнень виконання паралельного запиту при сильному перекосі даних (Про причини замделеннія паралельних запитів далі)
** P-> P - дані з однієї паралельної групи передаються в іншу паралельну групу
** P-> S - паралельність в послідовне виконання (вузьке місце або кінець запиту - друга з основних причин уповільнення паралельного запиту)
** PCWP - паралельність з батьком: скануємо таблицю і відразу робимо join з іншого
** PCWC - навпаки: передаємо фільтр з зовнішнього потоку і застосовуємо при скануванні
* PX RECEIVE - отримання даних з одного паралельного потоку в інший
* PX SEND QC - відправка даних координатору
* PX COORDINATOR - приймач всіх паралельних запитів
* TQ - Номер потоку
1. Наявність в плані події "P-> S - паралельність в послідовне виконання", крім перед "PX COORDINATOR"
Це говорить нам про те, що Oracle змушений був зібрати всі потоки в одну послідовність (sequence), що дало пляшкове горлечко очікування виконання самого довго потоку усіма іншими.
Наведу приклад з rownum. Додамо відбір номера рядка з кожної таблиці:
План змінився,
* Для розрахунку COUNT rownum паралельний процес читання таблиці з диска "PX BLOCK ITERATOR" вибудовується в чергу "P-> S", що зводить нанівець всі перімещуство розподіленого читання.
* Тепер JOIN не виконується в окремому потоці (: TQ10002)
тому обидва потоку вже були раніше перетворені в послідовний набір даних і не можуть використовуватися одночасно.
Як наслідок, час виконання запиту стало навіть більше (51 с), що не паралельна версія (49 с) через зайвих витрат на підтримку паралельності, яка не використовується
2. PX SEND skew - Перекіс даних
при формуванні диапозонов даних в один з потоків.
Продемонструвати це просто використовуючи заздалегідь створений перекошений стовпець t_2.fk_id_skew.
Якщо виконати запит, але для join таблиць використовувати умова: t_2.fk_id_skew = t_1.id
Те загальний план паралельного запиту не зміниться (див. План 1), але ось час виконання зросте до 38с.
Причина криється в тому, що в колонці t_2.fk_id_skew криється 1 500 000 значень = 1 і 3 500 000 інших. І при виконанні "PX SEND HASH" велика частина рядків таблиці потрапляють в один потік для обробки, замість того, щоб рівномірно розподілитися.
Рис.1 - Більшу частину часу запит виконувався в один потік, інші потоки його чекали.
Мал. 2. - Це ж підтверджується на вкладці PARALLEL: 37c від загального часу працював 1 потік.
Oracle чинить правильно, тому що не можна ж зробити join даних з різних діапазонів.
Для порівняння погляньте статистику виконання для добре розпаралеленого запиту (План 1) з умовою без перекосів t_2.fk_id_uniform = t_1.id
Все виконувалося в 8 потоків і кожен потік рівномірно обробив тільки свою рівну частину.
3. Bloom filters
Не вдаючись в механіку створення бітових векторів bloom filter опишу перевага їх використання.
Приклад запиту:
1. На таблиці T1 з фільтром "filter (" T1 "." MOD "= 42)" створюється bloom filter - PX JOIN FILTER CREATE
2. Фільтр з п.1 застосовується на таблицю T2 - PX JOIN FILTER USE
Тим самим обмежуючи розмір правої таблиці.
3. Відфільтрована таблиця T2 з'єднується з HASH JOIN BUFFERED
Детальний опис bloom filter: Bloom filter
bloom filter гарні в:
* Паралельних запитах - зменшується кількість переданих даних між потоками за рахунок передфільтрації правої таблиці
* RAC системах - зменшує число переданих по мережі даних між нодамі
* InMemory таблицях - здійснюючи inmemory передфільтрації таблиці і здійснення не inmemory join на швидко відфільтрованої правої таблиці
4. Partition Wise
В цілому схоже на попередній пункт, але фільтр накладається на партіціі правої таблиці, також зменшуючи сканування.
Дедектіруется в плані по фразах:
* PART JOIN FILTER CREATE (: BF0000) - створення фільтра на лівій таблиці
* Pstart | Pstop =: BF0000 |: BF0000 - застосування фільтра в операції читання правої таблиці PCWC