Esta série de cinco partes se aprofunda na maneira como os planos paralelos do modo de linha do SQL Server são iniciados. Esta primeira parte cobre o papel da tarefa pai (coordenador) na preparação do plano para execução paralela. Ele inclui inicializar cada operador e adicionar perfis ocultos para coletar dados de desempenho de tempo de execução, como contagem real de linhas e tempo decorrido.
Configuração
Para fornecer uma base concreta para a análise, seguiremos como uma determinada consulta paralela inicia a execução. Usei o Stack Overflow 2013 público banco de dados (detalhes do download). A forma de plano desejada também pode ser obtida em relação ao conjunto de dados menor do Stack Overflow 2010, se isso for mais conveniente. Ele pode ser baixado no mesmo link. Eu adicionei um índice não clusterizado:
CREATE NONCLUSTERED INDEX PP ON dbo.Posts ( PostTypeId ASC, CreationDate ASC );
Meu ambiente de teste é o SQL Server 2019 CU9 em um laptop com 8 núcleos e 16 GB de memória alocados para a instância. Nível de compatibilidade 150 é usado exclusivamente. Menciono esses detalhes para ajudá-lo a reproduzir o plano de destino, se desejar. Os fundamentos da execução paralela no modo de linha não foram alterados desde o SQL Server 2005, portanto, a discussão a seguir é amplamente aplicável.
A consulta de teste retorna o número total de perguntas e respostas, agrupadas por mês e ano:
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 );
Eu usei dicas para obter um plano de modo de linha de forma específico. A execução é limitada ao DOP 2 para tornar alguns dos detalhes mostrados posteriormente mais concisos.
O plano de execução estimado é (clique para ampliar):
Plano de fundo
O otimizador de consulta produz um único plano compilado para um lote. Cada instrução no lote é marcada para execução em série ou paralela, dependendo da elegibilidade e dos custos estimados.
Um plano paralelo contém trocas (operadores de paralelismo). As trocas podem aparecer em fluxos de distribuição , fluxos de partição , ou reúna streams Formato. Cada um desses tipos de troca usa os mesmos componentes subjacentes, apenas conectados de maneira diferente, com um número diferente de entradas e saídas. Para obter mais informações sobre a execução paralela no modo de linha, consulte Planos de execução paralela – ramificações e threads.
downgrade DOP
O grau de paralelismo (DOP) para um plano paralelo pode ser rebaixado em tempo de execução, se necessário. Uma consulta paralela pode começar solicitando DOP 8, mas ser progressivamente rebaixada para DOP 4, DOP 2 e, finalmente, DOP 1 devido à falta de recursos do sistema naquele momento. Se você gostaria de ver isso em ação, veja este pequeno vídeo de Erik Darling.
A execução de um plano paralelo em um único thread também pode acontecer quando um plano paralelo armazenado em cache é reutilizado por uma sessão limitada a DOP 1 por uma configuração de ambiente (por exemplo, máscara de afinidade ou governador de recursos). Consulte Mito:o SQL Server armazena em cache um plano serial com cada plano paralelo para obter detalhes.
Seja qual for a causa, o downgrade DOP de um plano paralelo em cache não resultar na compilação de um novo plano serial. O SQL Server reutiliza o plano paralelo existente desativando as trocas. O resultado é um plano ‘paralelo’ que é executado em um único thread. As trocas ainda aparecem no plano, mas são ignoradas em tempo de execução.
O SQL Server não pode promover um plano serial para execução paralela adicionando trocas em tempo de execução. Isso exigiria uma nova compilação.
Inicialização do plano paralelo
Um plano paralelo tem todas as trocas necessárias para utilizar threads de trabalho extras, mas há trabalho de configuração adicional necessário em tempo de execução antes que a execução paralela possa começar. Um exemplo óbvio é que threads de trabalho adicionais devem ser alocados para tarefas específicas dentro do plano, mas há muito mais do que isso.
Vou começar no ponto em que um plano paralelo foi recuperado do cache do plano. Neste ponto, existe apenas o thread original que processa a solicitação atual. Esse encadeamento às vezes é chamado de “encadeamento de coordenação” em planos paralelos, mas prefiro os termos “tarefa pai ” ou “pai-trabalhador”. Caso contrário, não há nada de especial sobre este tópico; é o mesmo encadeamento que a conexão usa para processar solicitações de clientes e executar planos seriais até a conclusão.
Para enfatizar que existe apenas um único tópico agora, quero que você visualize o plano neste momento assim:
Estarei usando capturas de tela do Sentry One Plan Explorer quase exclusivamente neste post, mas apenas para esta primeira visualização, também mostrarei a versão do SSMS:
Em qualquer representação, a principal diferença é a falta de ícones de paralelismo em cada operador, mesmo que as trocas ainda estejam presentes. Apenas a tarefa pai existe agora, em execução no thread de conexão original. Nenhum thread de trabalho adicional tarefas já foram reservadas, criadas ou atribuídas. Mantenha a representação do plano acima em mente à medida que avançamos.
Criando o plano executável
O plano neste momento é essencialmente apenas um modelo que pode ser usado como base para qualquer execução futura. Para prepará-lo para uma execução específica, o SQL Server precisa preencher valores de tempo de execução, como o usuário atual, contexto de transação, valores de parâmetro, ids para quaisquer objetos criados em tempo de execução (por exemplo, tabelas e variáveis temporárias) e assim por diante.
Para um plano paralelo, o SQL Server precisa fazer um pouco de trabalho preparatório adicional para levar o maquinário interno ao ponto em que a execução possa começar. O thread de trabalho da tarefa pai é responsável por realizar quase todo esse trabalho (e certamente todo o trabalho que abordaremos na parte 1).
O processo de transformação do modelo de plano para uma execução específica é conhecido como criação do plano executável . Às vezes é difícil manter a terminologia correta, porque os termos geralmente são sobrecarregados e mal aplicados (até mesmo pela Microsoft), mas farei o possível para ser o mais consistente possível.
Contextos de execução
Você pode pensar em um contexto de execução como um modelo de plano preenchido com todas as informações de tempo de execução específicas necessárias para um encadeamento específico. O plano executável para uma série consiste em um único contexto de execução, onde um único thread executa todo o plano.
Um paralelo plano executável contém uma coleção de contextos de execução :Um para a tarefa pai e um por encadeamento em cada ramificação paralela. Cada thread de trabalho paralelo adicional executa sua parte do plano geral dentro de seu próprio contexto de execução. Por exemplo, um plano paralelo com três ramificações em execução no DOP 8 tem (1 + (3 * 8)) =25 contextos de execução. Os contextos de execução serial são armazenados em cache para reutilização, mas os contextos de execução paralela adicionais não são.
A tarefa pai sempre existe antes de qualquer tarefa paralela adicional, portanto, é atribuído contexto de execução zero . Os contextos de execução usados pelos trabalhadores paralelos serão criados posteriormente, depois que o contexto pai for totalmente inicializado. Os contextos adicionais são clonados do contexto pai e, em seguida, personalizado para sua tarefa específica (isso é abordado na parte 2).
Há uma série de atividades envolvidas na inicialização do contexto de execução zero. É impraticável tentar listar todos eles, mas será útil cobrir alguns dos principais aplicáveis à nossa consulta de teste. Ainda haverá muitos para uma única lista, então vou dividi-los em seções (um tanto arbitrárias):
1. Inicialização do contexto pai
Quando enviamos a instrução para execução, o contexto da tarefa pai (contexto de execução zero) é inicializado com:
- Uma referência à transação base (explícito, implícito ou auto-commit). Trabalhadores paralelos executarão subtransações, mas todas elas estão dentro do escopo da transação base.
- Uma lista de parâmetros de instrução e seus valores atuais.
- Um objeto de memória principal (PMO) usado para gerenciar concessões e alocações de memória.
- Um mapa vinculado dos operadores (nós de consulta) no plano executável.
- Uma fábrica para qualquer objeto grande necessário (blob) manipula.
- Bloqueie classes para acompanhar vários bloqueios mantidos por um período durante a execução. Nem todos os planos exigem classes de bloqueio como operadores de streaming completo normalmente bloqueiam e desbloqueiam linhas individuais em sequência.
- A estimativa de concessão de memória para a consulta.
- Conceder memória do modo de linha feedback estruturas para cada operador (SQL Server 2019).
Muitas dessas coisas serão usadas ou referenciadas por tarefas paralelas posteriormente, portanto, elas precisam existir primeiro no escopo pai.
2. Metadados de contexto pai
As próximas tarefas principais executadas são:
- Verificação do custo da consulta estimado está dentro do limite definido pelas opções de configuração de limite de custo do governador de consulta.
- Atualizando o uso do índice registros – expostos por meio de
sys.dm_db_index_usage_stats
. - Criando valores de expressão em cache (constantes de tempo de execução).
- Criando uma lista de operadores de criação de perfil usado para coletar métricas de tempo de execução, como contagens de linhas e tempos, se isso tiver sido solicitado para a execução atual. Os próprios criadores de perfil ainda não foram criados, apenas a lista.
- Tirando um instantâneo de esperas para o recurso de espera de sessão exposto por meio de
sys.dm_exec_session_wait_stats
.
3. DOP e concessão de memória
O contexto da tarefa pai agora:
- Calcula o grau de paralelismo do tempo de execução (DOP ). Isso é afetado pelo número de trabalhadores livres (consulte “downgrade DOP” anteriormente), onde eles podem ser colocados entre os nós e vários sinalizadores de rastreamento.
- Reserva o número necessário de threads. Esta etapa é pura contabilidade – os próprios threads podem não existir neste momento. O SQL Server controla o número máximo de threads que pode ter. Reservando conversas subtrai desse número. Quando os encadeamentos terminam, o número máximo é aumentado novamente.
- Define tempo limite de concessão de memória .
- Calcula a concessão de memória, incluindo a memória necessária para buffers de troca.
- Adquire a concessão de memória por meio do semáforo de recurso apropriado .
- Cria um objeto de gerenciador para lidar com subprocessos paralelos . A tarefa pai é o processo de nível superior; tarefas adicionais também são conhecidas como subprocessos .
Embora os encadeamentos estejam "reservados" neste momento, o SQL Server ainda pode encontrar
THREADPOOL
espera mais tarde quando tenta usar um dos threads 'reservados'. A reserva garante que o SQL Server permanecerá em torno de seu número máximo de threads configurado o tempo todo, mas o thread físico pode não estar imediatamente disponível no pool de threads . Quando isso acontecer, um novo thread precisará ser iniciado pelo sistema operacional, o que pode demorar um pouco. Para saber mais sobre isso, veja Unusual THREADPOOL Waits, de Josh Darnell. 4. Configuração de verificação de consulta
Os planos de modo de linha são executados em iterativo moda, começando pela raiz. O plano que temos no momento ainda não é capaz desse modo de execução. Ainda é em grande parte um modelo, mesmo que já contenha uma quantidade razoável de informações específicas de execução.
O SQL Server precisa converter a estrutura atual em uma árvore de iteradores , cada um com métodos como
Open
, GetRow
e Close
. Os métodos iteradores são conectados a seus filhos por meio de ponteiros de função. Por exemplo, chamando GetRow
na raiz chama recursivamente GetRow
em operadores filhos até que um nível de folha seja alcançado e uma linha comece a “borbulhar” na árvore. Para uma atualização sobre os detalhes, consulte Iteradores, planos de consulta e por que eles são executados para trás. Fim da Parte 1
Fizemos um bom progresso na configuração do contexto de execução para a tarefa pai. Na parte 2, acompanharemos enquanto o SQL Server constrói a árvore de verificação de consulta necessária para a execução iterativa.