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

Desempenho de partições sys.

sys.partitions parece ser um UNION ALL de dois conjuntos de resultados (armazenamento de linhas e armazenamento de colunas) e a maioria das minhas consultas estão resultando em duas varreduras de sysrowsets. Existe algum filtro que eu possa colocar em uma consulta de sys.partitions se eu souber que a linha que estou procurando é rowstore?
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 o sys.sysrowsets e TABLE ALUCOUNT objetos uma vez. Mas não há muito que você ou eu possamos fazer sobre isso agora.

A coluna cmprlevel vem de sys.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 qualquer OUTER 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 se TABLE 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 a sys.sysrowsets e ALUCOUNT , e há vários que aparecem na lista, mas apenas dois são promissores:sys.internal_partitions e sys.system_internals_partitions .

sys.internal_partitions


Eu tentei sys.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 em sys.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 tentar sys.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 em sys.sysschobjs , mas agora apenas um verificar em sys.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 em sys.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 de sys.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 de sys.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 os s em internals 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 se sys.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.