Esta pergunta foi postada no #sqlhelp por Jake Manske, e foi trazida à minha atenção por Erik Darling.
Não me lembro de ter tido um problema de desempenho com
sys.partitions
. Meu pensamento inicial (ecoado por Joey D'Antoni) foi que um filtro no data_compression
coluna deveria evite a varredura redundante e reduza o tempo de execução da consulta pela metade. No entanto, esse predicado não é empurrado para baixo, e a razão pela qual leva um pouco de descompactação. Por que sys.partitions está lento?
Se você observar a definição de
sys.partitions
, é basicamente o que Jake descreveu – um UNION ALL
de todas as partições columnstore e rowstore, com TRÊS referências explícitas a sys.sysrowsets
(fonte abreviada aqui):CREATE VIEW sys.partitions AS WITH partitions_columnstore(...cols...) AS ( SELECT ...cols..., cmprlevel AS data_compression ... FROM sys.sysrowsets rs OUTER APPLY OpenRowset(TABLE ALUCOUNT, rs .rowsetid, 0, 0, 0) ct-------- *** ^^^^^^^^^^^^^^ *** LEFT JOIN sys.syspalvalues cl ... WHERE .. . sysconv(bit, rs.status &0x00010000) =1 -- Considera apenas índices base columnstore ), partitions_rowstore(...cols...) AS ( SELECT ...cols..., cmprlevel AS data_compression ... FROM sys.sysrowsets rs -------- *** ^^^^^^^^^^^^^^ *** LEFT JOIN sys.syspalvalues cl ... WHERE ... sysconv(bit, rs .status &0x00010000) =0 -- Ignora índices base columnstore e linhas órfãs. ) SELECT ...cols... from partitions_rowstore p OUTER APPLY OpenRowset(TABLE ALUCOUNT, p.partition_id, 0, 0, p.object_id) ct union todos SELECT ...cols... FROM partitions_columnstore as P1 LEFT JOIN (SELECT ...cols... FROM sys.sysrowsets rs OUTER APP LY OpenRowset(TABLE ALUCOUNT, rs.rowsetid, 0, 0, 0) ct------- *** ^^^^^^^^^^^^^^ *** ) ...
Essa visão parece remendada, provavelmente devido a preocupações de compatibilidade com versões anteriores. Certamente poderia ser reescrito para ser mais eficiente, principalmente para referenciar apenas osys.sysrowsets
eTABLE ALUCOUNT
objetos uma vez. Mas não há muito que você ou eu possamos fazer sobre isso agora.
A colunacmprlevel
vem desys.sysrowsets
(um prefixo de alias na referência da coluna teria sido útil). Você esperaria que um predicado contra uma coluna ocorresse logicamente antes de qualquerOUTER APPLY
e poderia impedir uma das varreduras, mas não é isso que acontece. Executando a seguinte consulta simples:
SELECT * FROM sys.partitions AS p INNER JOIN sys.objects AS o ON p.object_id =o.object_id WHERE o.is_ms_shipped =0;
Produz o seguinte plano quando há índices columnstore nos bancos de dados (clique para ampliar):
Planeje sys.partitions, com índices columnstore presentes
E o seguinte plano quando não houver (clique para ampliar):
Planeje sys.partitions, sem nenhum índice columnstore presente
Estes são o mesmo plano estimado, mas o SentryOne Plan Explorer é capaz de destacar quando uma operação é ignorada em tempo de execução. Isso acontece para a terceira varredura no último caso, mas não sei se há alguma maneira de reduzir ainda mais essa contagem de varredura em tempo de execução; a segunda varredura acontece mesmo quando a consulta retorna zero linhas.
No caso de Jake, ele tem muito de objetos, portanto, realizar essa verificação duas vezes é perceptível, doloroso e uma vez demais. E honestamente não sei seTABLE ALUCOUNT
, uma chamada de loopback interna e não documentada, também precisa verificar alguns desses objetos maiores várias vezes.
Olhando para a fonte, eu me perguntei se havia algum outro predicado que pudesse ser passado para a visão que pudesse coagir a forma do plano, mas eu realmente não acho que haja algo que possa ter um impacto.
Outra visualização funcionará?
Poderíamos, no entanto, tentar uma visão completamente diferente. Procurei outras exibições que continham referências asys.sysrowsets
eALUCOUNT
, e há vários que aparecem na lista, mas apenas dois são promissores:sys.internal_partitions
esys.system_internals_partitions
.
sys.internal_partitions
Eu tenteisys.internal_partitions
primeiro:
SELECT * FROM sys.internal_partitions AS p INNER JOIN sys.objects AS o ON p.object_id =o.object_id WHERE o.is_ms_shipped =0;
Mas o plano não era muito melhor (clique para ampliar):
Planejar sys.internal_partitions
Existem apenas duas verificações emsys.sysrowsets
desta vez, mas as varreduras são irrelevantes de qualquer maneira porque a consulta não chega nem perto de produzir as linhas nas quais estamos interessados. Vemos apenas linhas para objetos relacionados a columnstore (como afirma a documentação).
sys.system_internals_partitions
Vamos tentarsys.system_internals_partitions
. Estou um pouco cauteloso com isso, porque não é suportado (veja o aviso aqui), mas tenha paciência comigo um momento:
SELECT * FROM sys.system_internals_partitions AS p INNER JOIN sys.objects AS o ON p.object_id =o.object_id WHERE o.is_ms_shipped =0;
No banco de dados com índices columnstore, há uma varredura emsys.sysschobjs
, mas agora apenas um verificar emsys.sysrowsets
(Clique para ampliar):
Planeje sys.system_internals_partitions, com índices columnstore presentes
Se executarmos a mesma consulta no banco de dados sem índices columnstore, o plano é ainda mais simples, com uma busca emsys.sysschobjs
(Clique para ampliar):
Planeje sys.system_internals_partitions, sem índices columnstore presentes
No entanto, isso não é muito o que buscamos, ou pelo menos não exatamente o que Jake buscava, porque também inclui artefatos de índices columnstore. Se adicionarmos esses filtros, a saída real agora corresponde à nossa consulta anterior, muito mais cara:
SELECT * FROM sys.system_internals_partitions AS p INNER JOIN sys.objects AS o ON p.object_id =o.object_id WHERE o.is_ms_shipped =0 AND p.is_columnstore =0 AND p.is_orphaned =0;
Como bônus, a verificação desys.sysschobjs
tornou-se uma busca mesmo no banco de dados com objetos columnstore. A maioria de nós não notará essa diferença, mas se você estiver em um cenário como o de Jake, talvez (clique para ampliar):
Plano mais simples para sys.system_internals_partitions, com filtros adicionais
sys.system_internals_partitions
expõe um conjunto de colunas diferente desys.partitions
(alguns são completamente diferentes, outros têm novos nomes), portanto, se você estiver consumindo a saída a jusante, terá que ajustá-los. Você também desejará validar se ele retorna todas as informações desejadas nos índices rowstore, otimizado para memória e columnstore, e não se esqueça desses montes incômodos. E, finalmente, esteja pronto para deixar de fora oss
eminternals
muitas, muitas vezes.
Conclusão
Como mencionei acima, essa visão do sistema não é oficialmente suportada, portanto, sua funcionalidade pode mudar a qualquer momento; ele também pode ser movido para a Conexão de Administrador Dedicado (DAC) ou removido completamente do produto. Sinta-se à vontade para usar esta abordagem sesys.partitions
não está funcionando bem para você, mas, por favor, certifique-se de ter um plano de backup. E certifique-se de que esteja documentado como algo que você teste de regressão quando começar a testar versões futuras do SQL Server ou após a atualização, apenas por precaução.