Sql parallel что это
Русские Блоги
Подсказки Oracle, параллельный режим Oracle (параллельный) / * + параллельный (t, 4) * / Важная роль в настройке SQL
/ * + параллель (t, 4) * / Важная роль в настройке SQL!
17 ноября 2013 г. 12:59:24Цветы в тумане 5566Количество чтений: более 5422
Поговорим о важной роли HINT / * + parallel (t, 4) * / в настройке SQL!
/ * + parallel (t, 4) * / может сыграть хороший эффект в таких операциях, как запрос к большой таблице,
На основе параллельного запроса необходимо запускать параллельные процессы, распределять задачи и системные ресурсы, а также объединять результирующие наборы. Они относительно ресурсоемки.
Однако целесообразно использовать параллельный HINT для сокращения времени выполнения транзакций, особенно в статистике отчетов в системе ODS.
Вообще говоря, параллельный HINT используется главным образом в следующих ситуациях
1. Количество данных в таблице очень велико и превышает 10 миллионов;
2. Узел базы данных состоит из нескольких процессоров;
3. Текущая нагрузка на систему низкая;
Простой тест заключается в следующем, эффект очевиден:
SQL> select /*+parallel(t,4)*/count(*) from t;
Истекшее время: 00: 01: 32.04
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=5411 Card=1)
1 0 SORT (AGGREGATE)
2 1 SORT* (AGGREGATE) :Q351880
3 2 TABLE ACCESS* (FULL) OF ‘t’ (Cost=5411 Car :Q351880
2 PARALLEL_TO_SERIAL SELECT /*+ PIV_SSF */ SYS_OP_MSR(COUNT(*)) F
ROM (SELECT /*+ NO_EXPAND ROWID(A2)
SQL> select count(*) from t;
Истекшее время: 00: 04: 34.02
0 SELECT STATEMENT Optimizer=CHOOSE
1 0 SORT (AGGREGATE)
2 1 TABLE ACCESS (FULL) OF ‘t’
Параллельный режим Oracle (Parallel)
6 сентября 2017 г. 18:00:39Кипяченая вода луисКоличество чтений: 5815
(1) Вставить ускорение
insert into /*+ append parallel nologging */ dcustcomposmsg
select /*+ parallel(e,18)*/ * from dcustcomposmsg_new e;
commit;
(2) Выберите ускорение создания
CREATE TABLE TEMP_DCUST_GRADE NOLOGGING PARALLEL 10 AS
SELECT /*+PARALLEL(D,10)*/ *
FROM DCUST_BASIC_INFO_D PARTITION (P_’||V_TOLL_NO||’) D
WHERE EXISTS(SELECT »A» FROM DCUSTHIGH PARTITION (P_’||V_REGION_CODE||’) A
WHERE D.ID_NO = A.ID_NO )
Заставить параллелизм выполнить текущий SQL. Эту версию можно использовать после Oracle 9i. В предыдущей версии нет среды для тестирования. То есть, с помощью этого описания, функция многопоточной обработки Oracle может быть принудительно включена. Например, он похож на компьютер с многоядерным процессором, но в большинстве случаев он не будет одновременно полностью многоядерным (более двух ядер более очевидно), использование параллельных инструкций будет работать одновременно с несколькими ядрами для повышения эффективности.
Но чтобы активировать эту функцию, она также потребляет ресурсы и производительность. Все обычно используются, когда количество возвращаемых записей превышает 1 миллион, и эффект будет более очевидным.
3. Синтаксис
/*+parallel(table_short_name,cash_number)*/
Это можно использовать после вставки, удаления, обновления, выбора (аналогично использованию правила, есть возможность поделиться использованием правила)
Инструкция для включения параллельной функции:
alter session enable parallel dml;
Этот оператор является оператором DML. Если он используется в программе, используйте метод execute, чтобы открыть его.
Давайте объясним это транзакцией в ERP. В эту таблицу записываются все транзакции, и объем данных за день относительно велик (в зависимости от объема бизнеса компании). Предположим, что теперь мы хотим проверить и сравнить ежемесячные покупки и продажи за последний год, поэтому оно обычно записывается как:
select to_char(transaction_date,’yyyymm’) txn_month,
Скахин Алексей / pihel
Личный блог. Заметки о программировании и не только
Страницы
пятница, 29 января 2016 г.
Oracle: оптимизация параллельных запросов
В теории этого достаточно, чтобы ускорить запрос в разы.
Но есть ряд ситуаций в которых параллельность наоборот мешает.
Итак, выполнил простой запрос:
— Запрос 3
* regexp_replace в этом запросе нужен, чтобы данные отбирались не мгновенно и были видны в статистике затраты CPU.
* Хинты вставлены, чтобы запрос в плане выглядел также как написан тут.
Время выполнения выполнения запроса = 49сек.
Добавим хинт parallel(8) замен no_parallel.
Время выполнения = 8с, что в 6 раз быстрей.
Разберем для понимания план запроса:
— План 1
Про способы распределения данных по потокам нужно поговорить отдельно:
Стоит заметить, что данные бьются по значениям в столбцах строк, а не просто по строкам.
Это нужно, чтобы один и тотже диапозон данных из разных таблиц попал в один поток для join.
Если бы Oracle делал не так, то в 1 поток могли бы попасть совершенно разные данные и join нельзя было бы совершить.
На это стоит обратить внимание, т.к. это может являться и причиной замедлений выполнения параллельного запроса при сильном перекосе данных (О причинах замделенния параллельных запросов дальше)
Самыми частыми причинами этого является:
* при добавлении pl/sql функции в запрос, которая не может быть распараллелена
Обойти можно только если функция детерменестическая, когда ее результат зависит только от параметров (никаких вызовов за пределы функции):
** Диррективы determenistic и result cache при создании функции
** Устаревший (но рабочий) deprecated способ с константым доступом к функции через pragma ( http://docs.oracle.com/cd/B28359_01/appdev.111/b28370/restrictreferences_pragma.htm )
Приведу пример с rownum. Добавим отбор номера строки из каждой таблицы:
План поменялся,
* для расчета COUNT rownum параллельный процесс чтения таблицы с диска «PX BLOCK ITERATOR» выстраивается в очередь «P->S», что сводит на нет все перимещуство распределенного чтения.
* теперь JOIN не выполняется в отдельном потоке ( :TQ10002 )
т.к. оба потока уже были раньше преобразованы в последовательный набор данных и не могут использоваться одновременно.
Как следствие, время выполнения запроса стало даже больше ( 51 с ), чем не параллельная версия (49 с ) из-за лишних издержек на поддержку параллельности, которая не используется
Продемонстрировать это просто используя заранее созданный перекошенный столбец 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» большая часть строк таблицы попадают в один поток для обработки, вместо того, чтобы равномерно распределиться.
Это хорошо видно в статистике выполнения. Для просмотра воспользуемся функцией «DBMS_SQLTUNE.REPORT_SQL_MONITOR».
Нас интересуют вкладки Parallel и Activity:
Oracle поступает верно, т.к. нельзя же сделать join данных из разных диапазонов.
Для сравнения взгляните статистику выполнения для хорошо распараллеленого запроса (План 1) с условием без перекосов t_2.fk_id_uniform = t_1.id
Все выполнялось в 8 потоков и каждый поток равномерно обработал только свою равную часть.
Собственно решения этой проблемы из коробки в Oracle 11 нет. Гистограммы, как при анализе статистики не помогают, т.к. параллелятся данные по значениям в столбцах.
Есть обходные маневры ( http://allthingsoracle.com/parallel-execution-skew-addressing-skew-using-manual-rewrites/ ):
* Разбиваем запрос на UNION, где первая часть без перекошенного значения в параллели и второй union с перкошенным без параллели
* Через генерацию сурогатных уникальных ключей для перекошенного значения на основе косвенных показателей и соединение по новому ключу.
В этом случае данные по равномерному уникальному ключу будут удачно распараллелены.
Замечу, что в Oracle 12 эту проблему уже решили через гистограммы. В плане это отобразится как «PX SEND HYBRID HASH (SKEW)» (HYBRID HASH), т.е. Oracle сам сгенерировал сурогатные ключи для перекошенного значения и удачно смогу распараллелить. (подробней: http://allthingsoracle.com/parallel-execution-skew-12c-hybrid-hash-distribution-with-skew-detection/ )
Параллельные запросы в PostgreSQL
В современных ЦП очень много ядер. Годами приложения посылали запросы в базы данных параллельно. Если это отчетный запрос ко множеству строк в таблице, он выполняется быстрее, когда задействует несколько ЦП, и в PostgreSQL это возможно, начиная с версии 9.6.
Понадобилось 3 года, чтобы реализовать функцию параллельных запросов — пришлось переписать код на разных этапах выполнения запросов. В PostgreSQL 9.6 появилась инфраструктура для дальнейшего улучшения кода. В последующих версиях и другие типы запросов выполняются параллельно.
Ограничения
Тестовая среда
Разработчики PostgreSQL попытались урезать время отклика запросов бенчмарка TPC-H. Загрузите бенчмарк и адаптируйте его к PostgreSQL. Это неофициальное использование бенчмарка TPC-H — не для сравнения баз данных или оборудования.
Параллельное последовательное сканирование
Оно может быть быстрее не из-за параллельного чтения, а потому что данные разбросаны по многим ядрам ЦП. В современных ОС файлы данных PostgreSQL хорошо кэшируются. С упреждающим чтением можно получить из хранилища блок больше, чем запрашивает демон PG. Поэтому производительность запроса не ограничена вводом-выводом диска. Он потребляет циклы ЦП, чтобы:
Выполним простой запрос select :
Последовательный скан дает слишком много строк без агрегации, так что запрос выполняется одним ядром ЦП.
Параллельная агрегация
Итоговый результат рассчитывается нодой «Finalize Aggregate». Если у вас свои функции агрегации, не забудьте пометить их как «parallel safe».
Количество рабочих процессов
Количество рабочих процессов можно увеличить без перезапуска сервера:
Теперь мы видим 4 воркера в выводе explain:
Что здесь происходит? Рабочих процессов стало в 2 раза больше, а запрос стал всего в 1,6599 раз быстрее. Расчеты интересные. У нас было 2 рабочих процесса и 1 лидер. После изменения стало 4+1.
Наше максимальное ускорение от параллельной обработки: 5/3 = 1,66(6) раз.
Как это работает?
Процессы
Выполнение запроса всегда начинается с лидирующего процесса. Лидер делает все непараллельное и часть параллельной обработки. Другие процессы, выполняющие те же запросы, называются рабочими процессами. Параллельная обработка использует инфраструктуру динамических фоновых рабочих процессов (с версии 9.4). Раз другие части PostgreSQL используют процессы, а не потоки, запрос с 3 рабочими процессами мог быть в 4 раза быстрее традиционной обработки.
Взаимодействие
Рабочие процессы общаются с лидером через очередь сообщений (на основе общей памяти). У каждого процесса 2 очереди: для ошибок и для кортежей.
Сколько нужно рабочих процессов?
Если не удалось выделить рабочий процесс, обработка будет однопроцессной.
На практике эти правила не всегда годятся для продакшена, так что можно изменить количество рабочих процессов для конкретной таблицы: ALTER TABLE … SET ( parallel_workers = N ).
Почему параллельная обработка не используется?
Кроме длинного списка ограничений есть еще проверки затрат:
parallel_setup_cost — чтобы обойтись без параллельной обработки коротких запросов. Этот параметр прикидывает время на подготовку памяти, запуск процесса и начальный обмен данными.
parallel_tuple_cost : общение лидера с рабочими может затягиваться пропорционально количеству кортежей от рабочих процессов. Этот параметр считает затраты на обмен данными.
Соединения вложенных циклов — Nested Loop Join
Сбор происходит на последнем этапе, так что Nested Loop Left Join — это параллельная операция. Parallel Index Only Scan появился только в версии 10. Он работает аналогично параллельному последовательному сканированию. Условие c_custkey = o_custkey считывает один порядок для каждой клиентской строки. Так что оно не параллельно.
Хэш-соединение — Hash Join
Каждый рабочий процесс создает свою хэш-таблицу до PostgreSQL 11. И если этих процессов больше четырех, производительность не повысится. В новой версии хэш-таблица общая. Каждый рабочий процесс может использовать WORK_MEM, чтобы создать хэш-таблицу.
Запрос 12 из TPC-H наглядно показывает параллельное хэш-соединение. Каждый рабочий процесс участвует в создании общей хэш-таблицы.
Соединение слиянием — Merge Join
Соединение слиянием непараллельно по своей природе. Не переживайте, если это последний этап запроса, — он все равно может выполняться параллельно.
Соединение по секциям
В PostgreSQL 11 соединение по секциям отключено по умолчанию: у него очень затратное планирование. Таблицы со схожим секционированием можно соединять секция за секцией. Так Postgres будет использовать хэш-таблицы поменьше. Каждое соединение секций может быть параллельным.
Главное, соединение по секциям бывает параллельным, только если эти секции достаточно большие.
Параллельное дополнение — Parallel Append
Parallel Append может использоваться вместо разных блоков в разных рабочих процессах. Обычно это бывает с запросами UNION ALL. Недостаток — меньше параллелизма, ведь каждый рабочий процесс обрабатывает только 1 запрос.
Здесь запущено 2 рабочих процесса, хотя включено 4.
Самые важные переменные
Итоги
Начиная с версии 9.6 параллельная обработка может серьезно улучшить производительность сложных запросов, которые сканируют много строк или индексов. В PostgreSQL 10 параллельная обработка включена по умолчанию. Не забывайте отключать ее на серверах с большой рабочей нагрузкой OLTP. Последовательные сканы или сканы индексов потребляют очень много ресурсов. Если вы не выполняете отчет по всему набору данных, запросы можно сделать производительнее, просто добавив недостающие индексы или используя правильное секционирование.
Параллельная обработка большого селекта в нескольких сессиях
Представьте: есть селект, который возвращает записи, каждую из которых нужно обработать, и то ли много записей, то ли обработка каждой записи занимает много времени, а процесс обработки одной записи не зависит от процессов других записей.
Классический пример для того, чтобы задействовать многопоточность или в случае баз данных выполнять обработку в нескольких сессиях. В Оракле для этого используется hint /*+ parallel() */ и pipelined functions. Это здорово, но если у вас Oracle standard edition(где parallel не работает) или вы хотите обработать не каждую запись по отдельности(из соображений, что лучше накопить работу, а потом в bulk, одним ударом, выполнить), а поделить весь вывод селекта на куски и каждый обработать отдельно?
Задача ставится так:
Java откроет по тексту селекта result set в default connection.
Первым делом надо выполнить
select count(*) from («Текст селекта»);
Создадим connection pool с размерностью, заданной в 3-м параметре.
Создадим отдельные сессии, присоединившись через jdbc connection.
Данные для этого возьмем из 4-го параметра, нам, по большому счету нужен только пароль, все остальное получим сами(может еще порт, если он отличен от 1521).
Будем получать данные из селекта в default connection и переписывать их в сессию из пула. Как только решим, что накопили достаточно, создадим thread, передадим ему эту connection как параметр и пусть работает, а мы продолжим со следующей сессией или, если все уже прочитано, подождем окончания всех потоков.
Напишем функцию обработки. Она получает все поля селекта как параметры.
Будет удобно, чтобы, например, первые два параметра были бы номер в порции и ее размерность. Это даст возможность в dbms info выводить процент выполнения в потоке.
По метадате селекта будем конструировать ее вызов в виде примерно так:
begin proc1(23,14000,’a1′,3,’tratata’,35,48); end;
Хранить будем только такую строку.
Вначале это был 2-х мерный массив (i,j), где i — это номер потока(в дальнейшем. ). Потом я увидел, что при большом числе записей, затраты Oracle на поддержку большого массива становятся чрезмерными и решил пользоваться также временной таблицей(temporary table).
Я положил границу в 200,000 записей. Если селект count(*) возвращает меньше 200,000 Java в-runtime использует 2-х мерный String массив, если больше — пишет во временную таблицу parallel_calls_tmp с одним полем varchar2(4000).
Итак, в PL/SQL пакете создаем функцию
FUNCTION run_pipe_parallel(pi_Select_Txt VARCHAR2,
pi_Proc_Name VARCHAR2,
pi_Parallel_Count VARCHAR2,
Pi_Password VARCHAR2) RETURN VARCHAR2 AS
LANGUAGE JAVA NAME ‘com.samtrest.ParallelRunner.run_parallel(java.lang.String, java.lang.String,java.lang.String, java.lang.String) return java.lang.String’;
Создаем таблицу
На стороне Java есть функция
Получение массива типов данных полей селекта
Так строим строку вызова:
Накапливаем в массиве или таблице
А теперь весь класс, который нужно загрузить в базу.
В принципе, я сейчас подумал, что сработает не только с Oracle, а с любой базой…
Если кому интересно, могу рассказать, что я добавил для того, чтобы работать не с симуляцией pipelined функции, а с выполнением отдельных batches…
Могу сказать, что я в результате получил выигрыш во времени: 12 часов в одной сессии против часа с половиной в 25 сессиях. При этом все 16 процессоров сервера были нагружены под 100%.
Понимание и использование параллелизма в SQL Server
Авторский перевод статьи, поэтому, если заметите какие-либо неточности, прошу отметить в комментариях.
SQLServer способен к неявному использованию параллелизма для ускорения запросов SQL. Как он это делает, и как вы можете быть уверены, что он это делает, не совсем очевидно для большинства из нас. Пол Уайт начинает серию, которая делает все это простым для понимания, начиная с легкого уровня подсчета фасоли.
Многие опытные профессионалы баз данных приобрели несколько искаженный взгляд на параллельное выполнение запросов. Иногда это является следствием неудачных опытов с более ранними версиями SQLServer. Однако, часто, этот взгляд является результатом заблуждений, или недостаточностью мастерства владения методами, необходимыми для эффективной разработки и настройки запросов для параллельного выполнения.
Это первая в серии статей, которая обеспечит читателя с глубокими знаниями, необходимой информацией для использования параллельных функций обработки запросов, доступных в Microsoft SQL Server. Часть первая представляет собой пошаговое руководство по основам параллелизма в SQ LServer, освещая такие понятия, как «параллельное сканирование и поиск» (“parallel scans and seeks”), «работники» (“workers”), «потоки» (“threads”), «задачи»(” tasks”), «контекст выполнения» (“execution contexts”) и «операторы обмена» (“exchange operators”), которые координируют параллельную деятельность.
Будущие статьи обеспечат более полное представление о внутренней работе databaseengine, и покажут, как целенаправленный параллелизм может принести пользу не только хранилищам данных и системам поддержки принятия решений, обычно связанных с его использованием.
Часто думают, что основная нагрузка на систему ложится, прежде всего, при обработке транзакций (OLTP), однако, системы часто содержат запросы и процедуры, которые могли бы выиграть от надлежащего использования параллелизма.
Допускаю, что эта и последующие статьи содержат довольно глубокие технические описания. Наиболее эффективное использование параллелизма требует хорошего понимания таких вещей, как планирование, оптимизация запросов, и механизмов выполнения. Тем не менее, есть надежда, что даже новичок в этой теме найдет её для себя информативной и полезной.
Что такое параллелизм?
Вы, наверное, слышали фразу «Много рук делают легкую работу». Идея состоит в том, что разделение задачи между несколькими людьми приводит к тому, что каждый человек делает меньше. С точки зрения отдельного человека, работа кажется гораздо легче, хотя это такое же количество работы, что делается в целом. Что еще более важно, если дополнительные люди могут выполнять их распределенные работы одновременно, то общее время, необходимое для выполнения этой задачи уменьшается.
Подсчет фасоли
Представьте, что вы держите в руках большую стеклянную банку полную фасоли, и вас попросили её посчитать. Предположим, что вы в состоянии считать в среднем пять фасолин в секунду, это займет у вас чуть более десяти минут, чтобы определить, что именно этот сосуд содержит 3027 фасоли.
Если четыре ваших друга предлагают помочь с задачей, вы можете выбрать из ряда возможных стратегий, но давайте рассмотрим тот вариант, который четко отражает стратегию SQL Server. Вы усадить своих друзей за столом с банкой в его центре и одним совком для извлечения бобов из банки. Вы попросите их, чтобы они использовали совок, когда нужно больше фасоли для подсчета. Каждый из друзей также имеет ручку и листок бумаги, чтобы сохранять текущую сумму количества фасоли, которое они подсчитали.
Данная задача хорошо подходит для параллельной работы, потому что каждый человек способен работать одновременно и независимо друг от друга. Желаемый результат получается гораздо быстрее, не увеличивая общую работу в целом.
Подсчет фасоли с SQL Server
SQL Server не может подсчитывать фасоль, поэтому мы попросим его подсчитать количество строк в таблице. Если таблица небольшая, SQL Server, скорее всего, будет использовать план выполнения, как показано на рисунке 1.
Рисунок 1: Последовательный план подсчета ( Serial Counting Plan )
С другой стороны, если таблица является достаточно большой, оптимизатор SQL Server может выбрать дополнительных работников, используя план запроса, как показано на рисунке 2.
Рисунок 2: Параллельный план подсчета ( Parallel Counting Plan)
Маленькие желтые значки со стрелками показывают операции, которые включают несколько работников. Каждому работнику назначается отдельная часть работы, и частичные результаты затем объединяют с получением конечного результата. Как показано в примере ручного подсчета фасоли, параллельный план имеет все шансы, чтобы завершиться значительно быстрее, чем последовательный план, потому что несколько работников будут активно подсчитывать строки одновременно.
Как параллелизм работает
Представьте себе на минуту, что SQL Server не имеет встроенную поддержку параллелизма. Вы могли бы попытаться повысить производительность оригинального запроса подсчета строк, вручную разбив запрос на одинаковые по размеру кусочки и запустив каждый из них одновременно на отдельном подключении к серверу.
Рисунок 3: Ручной параллелизм (Manual Parallelism)
Каждый запрос на рисунке 3 написан для обработки отдельного диапазона строк таблицы, гарантируя, что каждая строка из таблицы обрабатывается только один раз в целом. Если повезет, SQL Server запустит каждый запрос на отдельном блоке обработки, и вы могли бы рассчитывать на получение трех частичных результатов примерно в треть времени. Естественно, вам все равно потребуется выполнить дополнительную стадию объединения результатов, чтобы получить правильный конечный результат.
Параллельное выполнение как множество последовательных планов
Пример «Ручного параллелизма» не столь далек от того, как SQL Server фактически осуществляет свою параллельную работу с запросами. Вспомним план параллельных запросов на рисунке 2, и предположим, что SQL Server выделяет три дополнительных работника на запрос во время его выполнения. Концептуально, мы можем перерисовать параллельный план, чтобы показать, что SQL Server запускает три последовательных плана одновременно (это представление не совсем точно, но мы это исправим в ближайшее время).
Рисунок 4: Множество последовательных планов (Multiple Serial Plans)
Каждому дополнительному работнику присваивается один из трех ветвей плана, которые сливаются в оператор Сбора Потоков (Gather Streams operator). Обратите внимание, что только оператор Сбора Потоков (Gather Streams operator) содержит маленькую желтую иконку параллелизма; это сейчас единственный оператор, который взаимодействует с несколькими работниками. Эта общая стратегия подходит SQL Server по двум основным причинам. Во-первых, весь код, который SQL Server необходимо выполнить для реализации последовательных планов уже существует и был оптимизирован в течение многих лет и релизов продукта. Во-вторых, этот метод очень хорошо масштабируется: если больше работников доступно во время выполнения, SQL Server может легко добавить дополнительные ветви плана чтобы распределить работу на большее количество работников.
Параллельное сканирование (Parallel Scan) и поставщик параллельных страниц (Parallel Page Supplier)
Проблемой в концептуальном плане, показанном на рисунке 4, является то, что каждый оператор Index Scan будет считать каждую строку во всей совокупности ввода. Левая часть некорректна, план будет выдавать неправильные результаты и, вероятно, займет больше времени для выполнения, чем последовательная версия. Ручной пример параллелизма избежал этой проблемы, используя явное «ГДЕ» (“WHERE”) в каждом запросе и разделил входные строки на три одинаковых по размеру диапазона.
SQL Server не использует тот же подход, потому что, распределяя работу равномерно, можно предположить, что каждый запрос будет получать равную долю доступных вычислительных ресурсов, и что каждая строка данных потребует одинаковое усилие для обработки. В качестве простого примера, такого как подсчет строк в таблице (на сервере без другой деятельности) эти предположения вполне могут иметь место и три запроса могут действительно вернуть свои частичные результаты примерно за то же время.
В целом, однако, можно легко привести примеры, где эти предположения не будут допускаться в реальном мире, в связи с некоторым числом внешних или внутренних факторов. Например, один из запросов может быть запланирован на тот же логический процессор, который занят продолжительной массовой загрузкой, в то время как другие остаются без нагрузки. В качестве альтернативы рассмотрим запрос, который включает в себя операцию соединения (Join), где объем работ, необходимых для обработки конкретной строки сильно зависит от того, соответствует ли она условию соединения или нет. Если некоторые запросы содержат больше соединяющих строк, чем другие, то время выполнения может варьироваться в широких пределах и общая производительность будет ограничена скоростью самого медленного работника.
Вместо того чтобы выделять фиксированное количество строк для каждого работника, SQL Server использует функцию хранения под названием поставщик параллельных страниц (Parallel Page Supplier) для распределения строк среди работников по требованию. Вы не увидите Parallel Page Supplierв графическом плане запроса, потому что он не является частью процессора запросов, но мы можем продлить иллюстрацию рисунка 4, чтобы показать, где он будет находиться и его связи:
Рисунок 5: Поставщик параллельных страниц (Parallel Page Supplier)
Важным моментом является то, что это схема на основе спроса; Parallel Page Supplier отвечает на запросы работников, обеспечивая партию строк любому работнику, который должен еще поработать. Возвращаясь к аналогии подсчета фасоли, Parallel Page Supplier представлен совком, используемым для извлечения фасоли из банки. Один общий совок гарантирует, что нет двух людей, подсчитывающих ту же фасоль, с другой стороны, нет ничего, что может препятствовать человеку забирать больше фасоли для подсчета по мере необходимости. В частности, если один человек работает медленнее, чем другие, то этот человек просто реже пользуется совком, и другие работники будут подсчитывать больше фасоли, чтобы компенсировать это.
В SQL Server медленный работник делает меньше запросов к Parallel Page Supplier и таким образом обрабатывает меньше строк. Это не влияет на работу других работников, и они продолжают обработку строк в их максимальной производительности. Таким образом, схема на основе спроса обеспечивает определенную степень устойчивости к изменениям в рабочей пропускной способности. Вместо того чтобы быть связанным по скорости самого медленного работника, производительность схемы на основе спроса уменьшается незначительно, если у отдельного работника снижается производительность. Тем не менее, тот факт, что каждый работник может обрабатывать значительно отличающиеся количества строк, в зависимости от условий среды выполнения, может вызвать другие проблемы (к этой теме мы вернемся позже в этой серии).
Обратите внимание, что использование Parallel Page Supplier не мешает SQL Server использовать существующие оптимизации, такие как сканирование опережающего чтения (read-ahead scanning) (предварительную выборку данных из постоянного хранения). На самом деле, это может быть даже немного более эффективным для трех работников потребляющих строки из одного, базового физического сканирования, а не из трех отдельных сканов областей, которые мы видели в ручном примере параллелизма.
Parallel Page Supplier также не ограничивается использованием сканирования индексов; SQL Server использует Parallel Page Supplier всякий раз, когда несколько работников совместно читают структуру данных. Эта структура данных может быть массив, кластерная таблица или индекс, и операция может быть либо сканирования (scan) либо поиска (seek). Если последний пункт удивляет вас, считают, что Index Seek лишь частичное сканирование (scan) т.е. она стремится найти первую отобранную ( qualifying) строку, а затем сканирует до конца отобранного диапазона.
Контексты исполнения (Execution Contexts)
Обратимся теперь к отдельным серверным соединениям, используемым в ручном примере параллелизма для достижения одновременного выполнения. Это не было бы эффективным для SQL Server, фактически создать несколько новых соединений для выполнения каждого параллельного запроса, но реальный механизм во многом похож. Вместо того, чтобы создавать отдельное соединение для каждого последовательного запроса, SQL Server использует облегченную конструкцию, известную как контексты исполнения (Execution Contexts).
SQL Server запускает параллельный план, выводя контексты выполнения DOP для каждой параллельной области плана запроса, с использованием отдельного работника для запуска части последовательного плана содержащегося в каждом контексте. Для облегчения понимания концепции, на рисунке 6 показаны четыре контекста выполнения созданных для параллельного плана подсчета, над которым мы работали до сих пор. Каждый цвет определяет область контекста исполнения, и хотя это не показано явно, Parallel Page Supplier снова используется для координации индексов.
Рисунок 6: Контексты выполнения параллельного плана
Чтобы получить более конкретное представление абстрактных понятий, введенных в этом разделе, Рисунок 7 показывает информацию, полученную путем запуска параллельного запроса подсчета строк, с помощью опции SQL Server Management Studio (SSMS), «I nclude Actual Execution Plan ».
Рисунок 7: параллельный план подсчета строк
Выноски показывают количество строк, обработанных каждым работником (потоком) в двух различных точках в плане. Информация поступила из окна SSMS Properties, которое может быть доступно при нажатии на оператора (или соединительной линии) и клавиши F4. Кроме того, вы можете щелкнуть правой кнопкой мыши на операторе или линии и выбрать «Свойства» («Properties») из всплывающего меню.
Читая справа, мы видим сколько строк рассчитывает каждый из трех работников в параллельной части плана; прошу заметить, что два работника обрабатывают приблизительно равное количество строк (около 40 000), а третий получает всего 32 000 строк из Parallel Page Supplier. Как уже говорилось ранее, процесс основанный на спросе означает, что точное число строк, обработанных каждым работником зависит от временных показателей и загрузки процессоров (в числе прочего) и часто колеблется между выполнениями запросов, даже на легкозагруженой машине.
Левая часть диаграммы показывает три частичных результата (по одному от каждого параллельного работника, выполняется в своем собственном контексте исполнения), которые собираются вместе и подвел их к одному результату ‘thread zero’. Это причуда окна SSMS Properties что “thread zero” помечен как “thread 0”в параллельных частях графического плана, и как “all threads” в последовательной области. Если вы посмотрите на XML, на котором основан графический план, ‘Счетчик выполнения каждого потока’ всегда относится к «thread 0», никогда «All Threads».
Планировщики, работники и задачи (Schedulers, Workers, and Tasks)
В этой статье до сих пор используется взаимозаменяемые термины, такие как «thread» и «worker» (поток и работник). Теперь, кажется, настало время, чтобы точнее определить некоторые термины.
Планировщики (Schedulers)
Информация о планировщиках показана в просмотре системы динамического управления (DMV), sys.dm_os_schedulers.
Работники и Потоки (Workers and Threads)
Задачи (Tasks)
Онлайн книги так говорят о задачах: Задача представляет собой единицу работы, которая планируется на SQL Server. Работа может быть связана с одной или несколькими задачами. Например, параллельный запрос будет выполнять несколько задач.
Чтобы расширить этот довольно краткое определение, скажем, что задача – это часть работы выполняемая работником SQL Server. Работа, которая содержит только последовательные планы выполнения является одной задачей, и будет выполнена (от начала до конца) на одном подключении, предоставленном работнику. Это тот случай, когда даже если для выполнения запроса необходимо сделать паузу, чтобы дождаться некоего события для завершения (например, для чтения с диска). Одному работнику назначается одна задача, и он не может выполнять другие задачи, пока текущая задача не будет завершена полностью.
Контексты исполнения (Execution Contexts)
Если задача описывает работу, которую предстоит сделать, то контекст выполнения описывает где эта работа будет происходить. Каждая задача выполняется внутри одного контекста исполнения, которые были определены в колонке exec_context_id в sys.dm_os_tasksDMV(вы также можете увидеть контексты выполнения с помощью “ECID” колонки в просмотре обратной совместимости sys.sysprocesses).
Оператор обмена (The Exchange Operator)
Чтобы кратко резюмировать, мы видели, что SQL Server выполняет параллельный план путем совместного запуска нескольких экземпляров последовательного плана. Каждый последовательный план является одной задачей, выполняемой в отдельном рабочем потоке внутри собственного контекста выполнения. Последний ингредиент в параллельном плане оператор обмена, который является ‘клеем’ для SQL Serverи используется для соединения контекстов исполнения параллельного плана. В целом, комплексный план запроса может содержать любое количество последовательных или параллельных областей, соединенных операторами обмена.
До сих пор мы видели только один вариант оператора обмена, а именно «Gather streams», но оператор обмена может появиться в графических планов в других вариантах:
Рисунок 8: Логические операторы обмена
Все виды операторов обмена служат для перемещения строк между одним или несколькими работниками, распределяя отдельные строки среди них. Различные виды логических операторов используются SQL Server, чтобы ввести новую последовательную или параллельную область или перераспределить строки на границе раздела между двумя параллельными областями.
Один физический оператор обмена более гибкий, чем его три логических варианта. Он может не только разделить, объединить, или перераспределить строки среди рабочих, подключенных к нему, но также:
· использовать одну из пяти различных стратегий, чтобы определить какие исходящие данные направить на ввод строки
· при необходимости сохранять порядок сортировки входных строк
Большая часть этой гибкости проистекает из его внутренней конструкции, поэтому мы рассмотрим это в первую очередь.
Внутри обмена
Оператор обмена имеет две различных субкомпоненты:
· Производителей (Producers), которые соединяются с рабочими на его входе
· Потребители (Consumers), которые соединяются с работниками на его выходе
На рисунке 9 показано увеличенное изображение (разноцветное) “Gather Streams” оператора с рисунка 6.
Рисунок 9: Внутри оператора обмена Gather Streams
Каждый производитель собирает строки на его входе и упаковывает их в одном или нескольких буферах памяти. После того, как буфер заполнен, производитель отдает его потребителю. Каждый из производителей и потребителей работает на том же рабочем потоке в качестве контекста выполнения, к которому он подключен (общая раскраска). Потребитель обмена читает строку из буфера обмена, каждый раз, когда его попросил один из его родительских операторов (обозначенный красным цветом Stream Aggregate, в этом случае).
Одним из главных преимуществ данной конструкции является то, что сложности обычно связанные с обменом данными между несколькими потоками исполнения могут быть урегулированы SQL Server внутри одного оператора. Остальные, необменные операторы в плане работают последовательно, и не должны иметь дело с этим механизмом.
Оператор обмена использует буферы, чтобы свести к минимуму издержки и реализовать основной вид управления потоком (чтобы предотвратить быстрых производителей уйти слишком далеко вперед медленной работы потребителей, например). Точное расположение буферов зависит от типа обмена, требуется или нет сохранять порядок, и решение какому потребителю производитель строки должен её направить.
Маршрутизация Строки (Routing Rows)
Как уже отмечалось, оператор обмена может решать, к какому потребителю производитель должен направить конкретную строку. Это решение зависит от типа разделения (PartitioningType) указанного для обмена. Существует пять вариантов:
· Хэш (hash) – наиболее распространен. Потребитель выбирается исходя из оценки хэш функции по одному или нескольким значениям столбцов в текущей строке
· Круговая система (RoundRobin) – каждая новая строка посылается к следующему потребителю в определенной последовательности
· Трансляция (Broadcast) – каждая строка отправляется всем потребителям
· Спрос (Demand) – строка отсылается первому потребителю, который попросит её. Это единственный тип разделения, где строка направляется от производителя посредством потребителя внутри оператора обмена
· Диапазон (Range) – Каждому потребителю присваивается неперекрывающийся диапазон значений. Диапазон, в который попадает входной столбец определяет какой потребитель получит строку.
Типы разделения «Спрос» и «Диапазон» встречаются гораздо реже, чем первые три, и, как правило, их можно увидеть только в планах запросов, которые работают с секционированными таблицами (Partitioned Tables). Тип «Спрос» используется в совместно размещенных разделах соединений для назначения идентификатора раздела к следующему рабочему потоку. Тип «Диапазон» используется, например, при создании секционированных индексов. Типы разделения, которые использовались, и любые значения столбцов, используемые в процессе, видны в графическом плане запроса:
Рисунок 10: Информация о разделении обмена ( Exchange Partitioning Information )
Наиболее распространенные типы разделения будут подробно описаны в следующих публикациях.
Сохранение порядка ввода
Рисунок 11: An Order-Preserving Repartition Streams Exchange
Строки, прибывающие на трех входах в обмен в определенном порядке (отсортированные, с точки зрения отдельных работников).Сохраняющей порядок обмен, известный как обмен слияния ( merging exchange ), гарантирует, что работник (и) на его выходе получать строки в том же порядке сортировки (при том, что распределение, как правило, разное, конечно).
Рисунок 12: Атрибут «Сортировать по» обмена слияния ( The ‘Order By’ Attribute of a Merging Exchange )
Обратите внимание, что обмен слияния (Merging Exchange) не выполняет никакой сортировки; она ограничивается сохранением порядок сортировки строк, поступающих на его входы. Обмен слияния может быть гораздо менее эффективным, чем вариант, не сохраняющий порядок и это связано с определенными проблемами производительности. Это уже другая тема, которую мы рассмотрим более подробно позже в других публикациях.
Резюме
В введении в параллелизм мы использовали простой запрос, и связанный с ним реальный пример, чтобы исследовать модель, используемую SQL Server, чтобы позволить запросам автоматически извлечь выгоду из дополнительной вычислительной мощности, предоставляемой современными многоядерными серверами, не требуя от разработчика учитывать некоторые сложности, обычно связанные с многопоточной конструкцией.
Мы видели, что план параллельных запросов может содержать любое количество параллельных и последовательных областей, связанных оператором обмена. Параллельные зоны расширяются на несколько последовательных запросов, каждый из которых использует один рабочий поток для обработки задачи в пределах контекста выполнения. Операторы обмена используются для распределения строк между работниками, и эти операторы существуют только в параллельном плане запроса, и которые взаимодействуют непосредственно с более чем одним работником. Наконец, мы увидели, что SQL Server обеспечивает поставщика параллельных страниц, который позволяет нескольким работникам совместно сканировать таблицу или индекс, гарантируя правильные результаты.
Следующая часть этой серии основана на фундаментальных понятиях, рассмотренных в этом введении, и показывает как использовать контекст выполнения, рабочие потоки, и операторы обмена в запросах, которые используют параллельный хэш (parallel hash) и соединения слиянием (merge joins). Мы также более подробно рассмотрим типы разделения обмена (Exchange Partitioning Types), оптимизацию запросов, которая возможна только в параллельных планах; ту оптимизацию, которая может привести к выполнению параллельного запроса, используя меньше процессорного времени, чем аналогичный последовательный запрос и быстрее возвращая результат запроса.