Database
 sql >> Base de Dados >  >> RDS >> Database

Limites de Otimização - Agrupamento e Agregação de Dados, Parte 4


Este artigo é o quarto de uma série sobre limites de otimização. A série abrange agrupamento e agregação de dados, explicando os diferentes algoritmos que o SQL Server pode usar e o modelo de custo que o ajuda a escolher entre os algoritmos. Neste artigo, concentro-me nas considerações de paralelismo. Abordo as diferentes estratégias de paralelismo que o SQL Server pode usar, os limites para escolher entre um plano serial e um plano paralelo e a lógica de custeio que o SQL Server aplica usando um conceito chamado grau de paralelismo para custeio (DOP para custeio).

Continuarei usando a tabela dbo.Orders no banco de dados de exemplo PerformanceV3 em meus exemplos. Antes de executar os exemplos neste artigo, execute o código a seguir para eliminar alguns índices desnecessários:
DROP INDEX IF EXISTS idx_nc_sid_od_cid ON dbo.Orders;DROP INDEX IF EXISTS idx_unc_od_oid_i_cid_eid ON dbo.Orders;

Os únicos dois índices que devem ser deixados nesta tabela são idx_cl_od (agrupados com orderdate como a chave) e PK_Orders (não agrupados com orderid como a chave).

Estratégias de paralelismo


Além de precisar escolher entre várias estratégias de agrupamento e agregação (Stream Aggregate pré-ordenado, Sort + Stream Aggregate, Hash Aggregate), o SQL Server também precisa escolher entre um plano serial ou paralelo. Na verdade, ele pode escolher entre várias estratégias de paralelismo diferentes. O SQL Server usa lógica de custo que resulta em limites de otimização que, sob diferentes condições, tornam uma estratégia preferida às outras. Já discutimos em profundidade a lógica de custeio que o SQL Server usa em planos seriais nas partes anteriores da série. Nesta seção, apresentarei várias estratégias de paralelismo que o SQL Server pode usar para lidar com agrupamento e agregação. Inicialmente, não entrarei em detalhes da lógica de custeio, mas apenas descreverei as opções disponíveis. Mais adiante no artigo, explicarei como funcionam as fórmulas de custeio e um fator importante nessas fórmulas chamado DOP para custeio.

Como você aprenderá mais tarde, o SQL Server leva em consideração o número de CPUs lógicas na máquina em suas fórmulas de custeio para planos paralelos. Nos meus exemplos, a menos que eu diga o contrário, presumo que o sistema de destino tenha 8 CPUs lógicas. Se você quiser experimentar os exemplos que vou fornecer, para obter os mesmos planos e valores de custo que eu, você precisa executar o código em uma máquina com 8 CPUs lógicas também. Se sua máquina tiver um número diferente de CPUs, você pode emular uma máquina com 8 CPUs - para fins de custo - assim:
DBCC OPTIMIZER_WHATIF(CPUs, 8);

Mesmo que esta ferramenta não seja oficialmente documentada e suportada, é bastante conveniente para fins de pesquisa e aprendizado.

A tabela Pedidos em nosso banco de dados de exemplo tem 1.000.000 de linhas com IDs de pedido no intervalo de 1 a 1.000.000. Para demonstrar três estratégias de paralelismo diferentes para agrupamento e agregação, filtrarei os pedidos em que o ID do pedido for maior ou igual a 300.001 (700.000 correspondências) e agruparei os dados de três maneiras diferentes (por custid [20.000 grupos antes da filtragem], por empid [500 grupos] e por shipperid [5 grupos]), e calcule a contagem de pedidos por grupo.

Use o código a seguir para criar índices para dar suporte às consultas agrupadas:
CREATE INDEX idx_oid_i_eid ON dbo.Orders(orderid) INCLUDE(empid);CREATE INDEX idx_oid_i_sid ON dbo.Orders(orderid) INCLUDE(shipperid);CREATE INDEX idx_oid_i_cid ON dbo.Orders(orderid) INCLUDE(custid); 
As consultas a seguir implementam a filtragem e o agrupamento mencionados anteriormente:
-- Consulta 1:Serial SELECT custid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=300001GROUP BY custidOPTION(MAXDOP 1); -- Consulta 2:Paralela, não local/global SELECT custid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=300001GROUP BY custid; -- Consulta 3:Local paralelo global paralelo SELECT empid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=300001GROUP BY empid; -- Consulta 4:Local paralelo global serial SELECT shipperid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=300001GROUP BY shipperid;

Observe que a Consulta 1 e a Consulta 2 são iguais (ambas agrupadas por custid), apenas a primeira força um plano serial e a segunda obtém um plano paralelo em uma máquina com 8 CPUs. Eu uso esses dois exemplos para comparar as estratégias serial e paralela para a mesma consulta.

A Figura 1 mostra os planos estimados para todas as quatro consultas:

Figura 1:Estratégias de paralelismo

Por enquanto, não se preocupe com os valores de custo mostrados na figura e a menção do termo DOP para custeio. Eu vou chegar a esses mais tarde. Primeiro, concentre-se em entender as estratégias e as diferenças entre elas.

A estratégia usada no plano serial para a Consulta 1 deve ser familiar para você das partes anteriores da série. O plano filtra os pedidos relevantes usando uma busca no índice de cobertura que você criou anteriormente. Então, com o número estimado de linhas a serem agrupadas e agregadas, o otimizador prefere a estratégia Hash Aggregate à estratégia Sort + Stream Aggregate.

O plano para a Consulta 2 usa uma estratégia de paralelismo simples que emprega apenas um operador agregado. Um operador de busca de índice paralelo distribui pacotes de linhas para os diferentes encadeamentos de forma round-robin. Cada pacote de linhas pode conter vários IDs de clientes distintos. Para que um único operador agregado possa calcular as contagens de grupos finais corretas, todas as linhas que pertencem ao mesmo grupo devem ser tratadas pelo mesmo encadeamento. Por esse motivo, um operador de troca de Paralelismo (Fluxos de Repartição) é usado para reparticionar os fluxos pelo conjunto de agrupamento (custid). Finalmente, um operador de troca de Paralelismo (Gather Streams) é usado para reunir os fluxos de vários threads em um único fluxo de linhas de resultado.

Os planos para a Consulta 3 e a Consulta 4 empregam uma estratégia de paralelismo mais complexa. Os planos começam de forma semelhante ao plano da Consulta 2, onde um operador de busca de índice paralelo distribui pacotes de linhas para diferentes encadeamentos. Em seguida, o trabalho de agregação é feito em duas etapas:um operador de agregação agrupa e agrega localmente as linhas do thread atual (observe o membro de resultado parcialagg1004) e um segundo operador de agregação agrupa e agrega globalmente os resultados dos agregados locais (observe o globalagg1005 membro resultado). Cada uma das duas etapas agregadas — local e global — pode usar qualquer um dos algoritmos agregados que descrevi anteriormente na série. Ambos os planos para Query 3 e Query 4 começam com um Hash Aggregate local e prosseguem com um Sort + Stream Aggregate global. A diferença entre os dois é que o primeiro usa paralelismo em ambas as etapas (portanto, uma troca de Repartição Streams é usada entre os dois e uma troca de Gather Streams após o agregado global), e o último manipula o agregado local em uma zona paralela e o global agregado em uma zona serial (portanto, uma troca Gather Streams é usada entre os dois).

Ao fazer sua pesquisa sobre otimização de consultas em geral e paralelismo especificamente, é bom estar familiarizado com ferramentas que permitem controlar vários aspectos de otimização para ver seus efeitos. Você já sabe como forçar um plano serial (com uma dica MAXDOP 1), e como emular um ambiente que, para fins de custeio, possui um certo número de CPUs lógicas (DBCC OPTIMIZER_WHATIF, com a opção CPUs). Outra ferramenta útil é a dica de consulta ENABLE_PARALLEL_PLAN_PREFERENCE (introduzida no SQL Server 2016 SP1 CU2), que maximiza o paralelismo. O que quero dizer com isso é que, se um plano paralelo for suportado para a consulta, o paralelismo será preferido em todas as partes do plano que podem ser tratadas em paralelo, como se fosse gratuito. Por exemplo, observe na Figura 1 que, por padrão, o plano da Consulta 4 trata a agregação local em uma zona serial e a agregação global em uma zona paralela. Aqui está a mesma consulta, só que desta vez com a dica de consulta ENABLE_PARALLEL_PLAN_PREFERENCE aplicada (vamos chamá-la de Consulta 5):
SELECT shipperid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=300001GROUP BY shipperidOPTION(USE HINT('ENABLE_PARALLEL_PLAN_PREFERENCE'));

O plano para a Consulta 5 é mostrado na Figura 2:

Figura 2:Maximizando o paralelismo

Observe que, desta vez, os agregados locais e globais são tratados em zonas paralelas.

Escolha de plano serial/paralelo


Lembre-se de que durante a otimização de consultas, o SQL Server cria vários planos candidatos e escolhe aquele com o menor custo entre os que produziu. O termo custo é um pouco impróprio, uma vez que o plano candidato com o menor custo deve ser, de acordo com as estimativas, aquele com o menor tempo de execução, não aquele com a menor quantidade de recursos usados ​​em geral. Por exemplo, entre um plano candidato serial e um paralelo produzido para a mesma consulta, o plano paralelo provavelmente usará mais recursos, pois precisa usar operadores de troca que sincronizam as threads (distribuem, reparticionam e reúnem streams). No entanto, para que o plano paralelo precise de menos tempo para concluir a execução do que o plano serial, as economias obtidas ao fazer o trabalho com vários threads precisam superar o trabalho extra realizado pelos operadores de troca. E isso precisa ser refletido pelas fórmulas de custeio que o SQL Server usa quando o paralelismo está envolvido. Não é uma tarefa simples de fazer com precisão!

Além do custo do plano paralelo precisar ser menor que o custo do plano serial para ser preferido, o custo da alternativa do plano serial precisa ser maior ou igual ao limite de custo para paralelismo . Esta é uma opção de configuração do servidor definida como 5 por padrão que impede que consultas com um custo bastante baixo sejam tratadas com paralelismo. O pensamento aqui é que um sistema com um grande número de pequenas consultas em geral se beneficiaria mais do uso de planos seriais, em vez de desperdiçar muitos recursos na sincronização de threads. Você ainda pode ter várias consultas com planos seriais em execução ao mesmo tempo, utilizando com eficiência os recursos multi-CPU da máquina. Na verdade, muitos profissionais do SQL Server gostam de aumentar o limite de custo para paralelismo de seu padrão de 5 para um valor mais alto. Um sistema executando um número bastante pequeno de grandes consultas simultaneamente se beneficiaria muito mais com o uso de planos paralelos.

Para recapitular, para que o SQL Server prefira um plano paralelo à alternativa serial, o custo do plano serial precisa ser pelo menos o limite de custo para paralelismo e o custo do plano paralelo precisa ser menor que o custo do plano serial (implicando potencialmente menor tempo de execução).

Antes de chegar aos detalhes das fórmulas de custeio reais, vou ilustrar com exemplos diferentes cenários em que é feita uma escolha entre planos seriais e paralelos. Certifique-se de que seu sistema assuma 8 CPUs lógicas para obter custos de consulta semelhantes aos meus, se você quiser experimentar os exemplos.

Considere as seguintes consultas (vamos chamá-las de Consulta 6 e Consulta 7):
-- Consulta 6:Serial SELECT empid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=400001GROUP BY empid; -- Consulta 7:SELECT paralelo forçado empid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=400001GROUP BY empidOPTION(USE HINT('ENABLE_PARALLEL_PLAN_PREFERENCE'));

Os planos para essas consultas são mostrados na Figura 3.

Figura 3:Custo serial

Aqui, o custo do plano paralelo [forçado] é menor que o custo do plano serial; no entanto, o custo do plano serial é menor que o limite de custo padrão para paralelismo de 5, portanto, o SQL Server escolheu o plano serial por padrão.

Considere as seguintes consultas (vamos chamá-las de Consulta 8 e Consulta 9):
-- Consulta 8:Parallel SELECT empid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=300001GROUP BY empid; -- Consulta 9:SELECT serial forçado empid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=300001GROUP BY empidOPTION(MAXDOP 1);

Os planos para essas consultas são mostrados na Figura 4.

Figura 4:Custo serial>=limite de custo para paralelismo, custo paralelo

Aqui, o custo do plano serial [forçado] é maior ou igual ao limite de custo para paralelismo e o custo do plano paralelo é menor que o custo do plano serial, portanto, o SQL Server escolheu o plano paralelo por padrão.

Considere as seguintes consultas (vamos chamá-las de Consulta 10 e Consulta 11):
-- Consulta 10:Serial SELECT *FROM dbo.OrdersWHERE orderid>=100000; -- Consulta 11:SELECT paralelo forçado *FROM dbo.OrdersWHERE orderid>=100000OPTION(USE HINT('ENABLE_PARALLEL_PLAN_PREFERENCE'));

Os planos para essas consultas são mostrados na Figura 5.

Figura 5:Custo serial>=limite de custo para paralelismo, custo paralelo>=custo serial

Aqui, o custo do plano serial é maior ou igual ao limite de custo para paralelismo; no entanto, o custo do plano serial é menor do que o custo do plano paralelo [forçado], portanto, o SQL Server escolheu o plano serial por padrão.

Há outra coisa que você precisa saber sobre como tentar maximizar o paralelismo com a dica ENABLE_PARALLEL_PLAN_PREFERENCE. Para que o SQL Server possa usar um plano paralelo, deve haver algum ativador de paralelismo, como um predicado residual, uma classificação, uma agregação e assim por diante. Um plano que aplica apenas uma varredura de índice ou busca de índice sem um predicado residual e sem qualquer outro ativador de paralelismo será processado com um plano serial. Considere as seguintes consultas como exemplo (vamos chamá-las de Consulta 12 e Consulta 13):
-- Consulta 12 SELECT *FROM dbo.OrdersOPTION(USE HINT('ENABLE_PARALLEL_PLAN_PREFERENCE')); -- Consulta 13 SELECT *FROM dbo.OrdersWHERE orderid>=100000OPTION(USE HINT('ENABLE_PARALLEL_PLAN_PREFERENCE'));

Os planos para essas consultas são mostrados na Figura 6.

Figura 6:ativador de paralelismo

A consulta 12 obtém um plano serial apesar da dica, pois não há ativador de paralelismo. A consulta 13 obtém um plano paralelo, pois há um predicado residual envolvido.

Calculando e testando DOP para custeio


A Microsoft teve que calibrar as fórmulas de custeio na tentativa de fazer com que um custo de plano paralelo menor do que o custo do plano serial refletisse um tempo de execução menor e vice-versa. Uma ideia potencial era pegar o custo de CPU do operador serial e simplesmente dividi-lo pelo número de CPUs lógicas na máquina para produzir o custo de CPU do operador paralelo. O número lógico de CPUs na máquina é o principal fator que determina o grau de paralelismo da consulta, ou DOP em resumo (o número de threads que podem ser usados ​​em uma zona paralela no plano). O pensamento simplista aqui é que, se um operador levar T unidades de tempo para ser concluído ao usar um encadeamento e o grau de paralelismo da consulta for D, o operador levaria tempo T/D para concluir ao usar D encadeamentos. Na prática, as coisas não são tão simples. Por exemplo, geralmente você tem várias consultas sendo executadas simultaneamente e não apenas uma, caso em que uma única consulta não obterá todos os recursos de CPU da máquina. Então, a Microsoft teve a ideia de grau de paralelismo para custeio (DOP para custeio, em suma). Essa medida é normalmente menor que o número de CPUs lógicas na máquina e é o fator pelo qual o custo de CPU do operador serial é dividido para calcular o custo de CPU do operador paralelo.

Normalmente, o DOP para custeio é calculado como o número de CPUs lógicas dividido por 2, usando divisão inteira. Há exceções, no entanto. Quando o número de CPUs é 2 ou 3, o DOP para custeio é definido como 2. Com 4 ou mais CPUs, o DOP para custeio é definido como #CPUs / 2, novamente, usando divisão inteira. Isso é até um certo máximo, que depende da quantidade de memória disponível para a máquina. Em uma máquina com até 4.096 MB de memória o DOP máximo para custeio é 8; com mais de 4.096 MB, o DOP máximo para custeio é 32.

Para testar essa lógica, você já sabe como emular um número desejado de CPUs lógicas usando DBCC OPTIMIZER_WHATIF, com a opção CPUs, assim:
DBCC OPTIMIZER_WHATIF(CPUs, 8);

Usando o mesmo comando com a opção MemoryMBs, você pode emular uma quantidade desejada de memória em MBs, assim:
DBCC OPTIMIZER_WHATIF(MemoryMBs, 16384);

Use o código a seguir para verificar o status existente das opções emuladas:
DBCC TRACEON(3604); DBCC OPTIMIZER_WHATIF(Status); DBCC TRACEOFF(3604);

Use o seguinte código para redefinir todas as opções:
DBCC OPTIMIZER_WHATIF(ResetAll);

Aqui está uma consulta T-SQL que você pode usar para calcular DOP para custeio com base em um número de entrada de CPUs lógicas e quantidade de memória:
DECLARE @NumCPUs AS INT =8, @MemoryMBs AS INT =16384; SELECT CASE WHEN @NumCPUs =1 THEN 1 WHEN @NumCPUs <=3 THEN 2 WHEN @NumCPUs>=4 THEN (SELECT MIN(n) FROM ( VALUES(@NumCPUs / 2), (MaxDOP4C)) AS D2(n)) END AS DOP4CFROM ( VALUES( CASE WHEN @MemoryMBs <=4096 THEN 8 ELSE 32 END ) ) AS D1(MaxDOP4C);

Com os valores de entrada especificados, esta consulta retorna 4.

A Tabela 1 detalha o DOP para custeio que você obtém com base no número lógico de CPUs e na quantidade de memória em sua máquina.
#CPUs DOP para custeio quando MemoryMBs <=4096 DOP para custeio quando MemoryMBs> 4096
1 1 1
2-5 2 2
6-7 3 3
8-9 4 4
10-11 5 5
12-13 6 6
14-15 7 7
16-17 8 8
18-19 8 9
20-21 8 10
22-23 8 11
24-25 8 12
26-27 8 13
28-29 8 14
30-31 8 15
32-33 8 16
34-35 8 17
36-37 8 18
38-39 8 19
40-41 8 20
42-43 8 21
44-45 8 22
46-47 8 23
48-49 8 24
50-51 8 25
52-53 8 26
54-55 8 27
56-57 8 28
58-59 8 29
60-61 8 30
62-63 8 31
>=64 8 32

Tabela 1:DOP para custeio

Como exemplo, vamos revisitar a Consulta 1 e a Consulta 2 mostradas anteriormente:
-- Consulta 1:SELECT serial forçado custid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=300001GROUP BY custidOPTION(MAXDOP 1); -- Consulta 2:Naturalmente paralelo SELECT custid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=300001GROUP BY custid;

Os planos para essas consultas são mostrados na Figura 7.

Figura 7:DOP para custo

A consulta 1 força um plano serial, enquanto a consulta 2 obtém um plano paralelo no meu ambiente (emulando 8 CPUs lógicas e 16.384 MB de memória). Isso significa que o DOP para custeio no meu ambiente é 4. Como mencionado, o custo de CPU de um operador paralelo é calculado como o custo de CPU do operador serial dividido pelo DOP para custeio. Você pode ver que esse é realmente o caso em nosso plano paralelo com os operadores Index Seek e Hash Aggregate que são executados em paralelo.

Quanto aos custos dos operadores de câmbio, eles são compostos por um custo inicial e algum custo constante por linha, que você pode facilmente fazer engenharia reversa.

Observe que na estratégia de agregação e agrupamento paralelo simples, que é a usada aqui, as estimativas de cardinalidade nos planos serial e paralelo são as mesmas. Isso porque apenas um operador agregado é empregado. Mais tarde você verá que as coisas são diferentes ao usar a estratégia local/global.

As consultas a seguir ajudam a ilustrar o efeito do número de CPUs lógicas e do número de linhas envolvidas no custo da consulta (10 consultas, com incrementos de 100 mil linhas):
SELECT empid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=900001GROUP BY empid; SELECT empid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=800001GROUP BY empid; SELECT empid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=700001GROUP BY empid; SELECT empid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=600001GROUP BY empid; SELECT empid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=500001GROUP BY empid; SELECT empid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=400001GROUP BY empid; SELECT empid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=300001GROUP BY empid; SELECT empid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=200001GROUP BY empid; SELECT empid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=100001GROUP BY empid; SELECT empid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=000001GROUP BY empid;

A Figura 8 mostra os resultados.

Figura 8:Custo da consulta em relação a #CPUs e #rows

A linha verde representa os custos das diferentes consultas (com os diferentes números de linhas) usando um plano serial. As demais linhas representam os custos dos planos paralelos com diferentes números de CPUs lógicas e seus respectivos DOP para custeio. A linha vermelha representa o ponto em que o custo da consulta serial é 5 — o limite de custo padrão para a configuração de paralelismo. À esquerda deste ponto (menos linhas a serem agrupadas e agregadas), normalmente o otimizador não considerará um plano paralelo. Para poder pesquisar os custos de planos paralelos abaixo do limite de custo para paralelismo, você pode fazer uma das duas coisas. Uma opção é usar a dica de consulta ENABLE_PARALLEL_PLAN_PREFERENCE, mas como lembrete, essa opção maximiza o paralelismo em vez de apenas forçá-lo. Se esse não for o efeito desejado, você pode simplesmente desabilitar o limite de custo para paralelismo, assim:
EXEC sp_configure 'mostrar opções avançadas', 1; RECONFIGURAR; EXEC sp_configure 'limite de custo para paralelismo', 0; EXEC sp_configure 'mostrar opções avançadas', 0; RECONFIGURAR;

Obviamente, essa não é uma jogada inteligente em um sistema de produção, mas perfeitamente útil para fins de pesquisa. Foi o que fiz para produzir as informações para o gráfico da Figura 8.

Começando com 100 mil linhas e adicionando incrementos de 100 mil, todos os gráficos parecem implicar que se o limite de custo para o paralelismo não fosse um fator, um plano paralelo sempre seria preferido. Esse é realmente o caso de nossas consultas e o número de linhas envolvidas. No entanto, tente números menores de linhas, começando com 10 mil e aumentando em incrementos de 10 mil usando as cinco consultas a seguir (novamente, mantenha o limite de custo para paralelismo desativado por enquanto):
SELECT empid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=990001GROUP BY empid; SELECT empid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=980001GROUP BY empid; SELECT empid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=970001GROUP BY empid; SELECT empid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=960001GROUP BY empid; SELECT empid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=950001GROUP BY empid;

A Figura 9 mostra os custos de consulta com planos serial e paralelo (emulando 4 CPUs, DOP para custear 2).

Figura 9:Serial / limite de plano paralelo

Como você pode ver, há um limite de otimização até o qual o plano serial é preferido e acima do qual o plano paralelo é preferido. Conforme mencionado, em um sistema normal em que você mantém o limite de custo para a configuração de paralelismo no valor padrão de 5 ou superior, o limite efetivo é, de qualquer forma, mais alto do que neste gráfico.

Mencionei anteriormente que quando o SQL Server escolhe a estratégia de paralelismo de agrupamento e agregação simples, as estimativas de cardinalidade dos planos serial e paralelo são as mesmas. A questão é como o SQL Server lida com as estimativas de cardinalidade para a estratégia de paralelismo local/global.

Para descobrir isso, usarei a Consulta 3 e a Consulta 4 de nossos exemplos anteriores:
-- Consulta 3:Local paralelo global paralelo SELECT empid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=300001GROUP BY empid; -- Consulta 4:Local paralelo global serial SELECT shipperid, COUNT(*) AS numordersFROM dbo.OrdersWHERE orderid>=300001GROUP BY shipperid;

Em um sistema com 8 CPUs lógicas e um DOP efetivo para valor de custo de 4, obtive os planos mostrados na Figura 10.

Figura 10:Estimativa de cardinalidade

A consulta 3 agrupa os pedidos por empid. 500 grupos distintos de funcionários são esperados eventualmente.

A consulta 4 agrupa os pedidos por shipperid. 5 grupos de expedidores distintos são esperados eventualmente.

Curiosamente, parece que a estimativa de cardinalidade para o número de grupos produzidos pelo agregado local é { número de grupos distintos esperados por cada thread } * { DOP para custeio }. Na prática, você percebe que o número geralmente será o dobro, pois o que conta é o DOP para execução (também conhecido como DOP), que é baseado principalmente no número de CPUs lógicas. Essa parte é um pouco complicada de emular para fins de pesquisa, pois o comando DBCC OPTIMIZER_WHATIF com a opção CPUs afeta o cálculo do DOP para custeio, mas o DOP para execução não será maior que o número real de CPUs lógicas que sua instância do SQL Server vê. Esse número é essencialmente baseado no número de agendadores com os quais o SQL Server começa. Você pode controle o número de agendadores O SQL Server começa usando o parâmetro de inicialização -P{ #schedulers }, mas essa é uma ferramenta de pesquisa um pouco mais agressiva em comparação com uma opção de sessão.

De qualquer forma, sem emular nenhum recurso, minha máquina de teste possui 4 CPUs lógicas, resultando em DOP para custeio 2 e DOP para execução 4. No meu ambiente, o agregado local no plano da Consulta 3 mostra uma estimativa de 1.000 grupos de resultados (500 x 2), e um real de 2.000 (500 x 4). Da mesma forma, o agregado local no plano para a Consulta 4 mostra uma estimativa de 10 grupos de resultados (5 x 2) e um real de 20 (5 x 4).

Quando terminar de experimentar, execute o seguinte código para limpeza:
-- Defina o limite de custo para paralelismo como padrão EXEC sp_configure 'mostrar opções avançadas', 1; RECONFIGURAR; EXEC sp_configure 'limite de custo para paralelismo', 5; EXEC sp_configure 'mostrar opções avançadas', 0; RECONFIGURE;GO -- Redefinir opções OPTIMIZER_WHATIF DBCC OPTIMIZER_WHATIF(ResetAll); -- Eliminar índices DROP INDEX idx_oid_i_sid ON dbo.Orders;DROP INDEX idx_oid_i_eid ON dbo.Orders;DROP INDEX idx_oid_i_cid ON dbo.Orders;

Conclusão


Neste artigo, descrevi várias estratégias de paralelismo que o SQL Server usa para lidar com agrupamento e agregação. Um conceito importante a ser entendido na otimização de consultas com planos paralelos é o grau de paralelismo (DOP) para custeio. Mostrei vários limites de otimização, incluindo um limite entre planos seriais e paralelos e o limite de custo de configuração para paralelismo. A maioria dos conceitos que descrevi aqui não são exclusivos de agrupamento e agregação, mas são igualmente aplicáveis ​​para considerações de plano paralelo no SQL Server em geral. No próximo mês, continuarei a série discutindo otimização com reescrita de consulta.