sábado, 18 de agosto de 2012

Forma rápida de verificar queries que estão pesando muito no servidor - Parte II

Olá pessoal, demorei a retomar os posts do blog pela correria do mestrado, mas vamos voltar ao assunto de verificar rapidamente quais são as queries que estão sobrecarregando o seu servidor. Como foi explicado no último post, muitas vezes é melhor utilizar as DMVs e DMFs (dynamic management views / dynamic management functions) do que configurar o Profiler, gerar um workload e fazer a análise subsequente em função da velocidade de resposta. Novamente, estou falando de SQL Server 2005 e 2008, não testei os pontos abaixo em 2012.

O cerne da query que coloquei no post anterior é a DMV sys.dm_exec_query_stats, que contém uma série de dados relevantes para a avaliação das consultas no cache, porém ela falha em alguns pontos. Vou apresentar alguns deles para apoiar a sua decisão, para ver se o melhor é usar o Profiler, fazer uso da DMV ou outra ferramenta:

Queries com a opção RECOMPILE não são capturadas

Não há muito o que dizer além do que está dito no título. Se você faz uso de queries com RECOMPILE, a DMV não vai armazenar informações pertinentes à sua execução e podem ser justamente as consultas causando a sobrecarga no seu banco de dados.

Queries estruturalmente similares não podem ser agrupadas

Isso é um problema maior no SQL Server 2005 do que no 2008, mas para explicar o problema vou lançar um exemplo. Digamos que executamos duas queries simples, como as que coloco abaixo:

SELECT Nome FROM Tabela WHERE id = 1
SELECT Nome FROM Tabela WHERE id = 2

Tanto no SQL Server 2005 quanto no SQL Server 2008, ele vai armazenar a instrução de maneira isolada no cache. Então no campo text retornado pela DMF sys.dm_exec_sql_text , não poderemos usar um GROUP BY, COMPUTE ou qualquer outra função de agrupamento pois em um tem o trecho "id = 1" e no outro "id = 2". No SQL Server 2008, tem algo bem legal que permite que consigamos agrupar as duas queries, que são os novos campos query_hash e query_plan_hash retornados pela DMV sys.dm_exec_query_stats. Estes dois campos geram um hash da consulta desconsiderando os parâmetros, permitindo que você agrupe as consultas estruturalmente iguais mas com parâmetros diferentes.

Algumas informações de execução da procedure não são consideradas

Alguns recursos de temporização como o WAITFOR não são considerados nas DMV e aparecem no Profiler.

Existem alguns outros pontos, mas acredito que os três pontos acima são os de maior criticidade. E até pegando o ponto do agrupamento, dá para alterar a query anterior, desta vez somente para SQL Server 2008, para fazer uso do query_hash e ter estatísticas acerca de queries estruturalmente idênticas. Porém, dica de outro, a Microsoft já lançou uma ferramenta muito legal que já faz isso pra você:

Query Hash Statistics

E cumprindo o que prometi no post anterior, aí vai uma lista de coisas que dá para se analisar com a sys.dm_exec_query_stats no SQL Server 2008 R2. Vou colar a tabela do MSDN (ver artigo aqui) e adicionar alguns comentários em vermelho para deixar alguns pontos mais claros.

Nome da colunaTipo de dadosDescrição
sql_handle varbinary(64) É um token que se refere ao lote ou procedimento armazenado de que a consulta faz parte.
Pode ser usado sql_handle, junto com statement_start_offset e statement_end_offset, para recuperar o texto SQL da consulta, chamando a função de gerenciamento dinâmico sys.dm_exec_sql_text.
statement_start_offset int Indica, em bytes, começando com 0, a posição inicial da consulta que a linha descreve dentro do texto de seu lote ou objeto persistente.
statement_end_offset int Indica, em bytes, começando com 0, a posição final da consulta que a linha descreve dentro do texto de seu lote ou objeto persistente. Um valor de -1 indica o fim do lote.
plan_generation_num bigint Um número de sequência que pode ser usado para distinguir entre instâncias de planos após uma recompilação.
plan_handle varbinary(64) Um token que se refere ao plano compilado de que a consulta faz parte. Este valor pode ser transmitido à função de gerenciamento dinâmico sys.dm_exec_query_plan para a obtenção do plano de consulta.
creation_time datetime Hora em que o plano foi compilado.
last_execution_time datetime Hora do início da execução do plano.
execution_count bigint Número de vezes que o plano foi executado desde sua última compilação.
total_worker_time bigint Tempo total da CPU, relatado em microssegundos (mas preciso somente em milissegundos), que foi consumido pelas execuções desse plano desde que foi compilado.
last_worker_time bigint Tempo de CPU, relatado em microssegundos (mas preciso somente em milissegundos), consumido na última vez em que o plano foi executado.
min_worker_time bigint Tempo de CPU mínimo, relatado em microssegundos (mas preciso somente em milissegundos), que esse plano já consumiu durante uma única execução.
max_worker_time bigint Tempo de CPU máximo, relatado em microssegundos (mas preciso somente em milissegundos), que esse plano já consumiu durante uma única execução.
total_physical_reads bigint Número total de leituras físicas efetuadas por execuções deste plano desde sua compilação.
Novamente, leitura física é pertinente ao acesso ao disco para recuperação de dados da tabela. Logical reads são leituras que acessam o cache de dados e também o disco. O uso de cache é basicamente o resultado da substração Logical Reads - Physical Reads.
last_physical_reads bigint Número de leituras físicas efetuadas na última vez em que o plano foi executado.
min_physical_reads bigint Número mínimo de leituras físicas que este plano efetuou durante uma única execução.
max_physical_reads bigint Número máximo de leituras físicas que este plano efetuou durante uma única execução.
total_logical_writes bigint Número total de gravações lógicas efetuadas por execuções deste plano desde sua compilação.
last_logical_writes bigint Número de gravações lógicas efetuadas na última vez em que o plano foi executado.
min_logical_writes bigint Número mínimo de gravações lógicas que este plano efetuou durante uma única execução.
max_logical_writes bigint Número máximo de gravações lógicas que este plano efetuou durante uma única execução.
total_logical_reads bigint Número total de leituras lógicas efetuadas por execuções deste plano desde sua compilação.
last_logical_reads bigint Número de leituras lógicas efetuadas na última vez em que o plano foi executado.
min_logical_reads bigint Número mínimo de leituras lógicas que este plano efetuou durante uma única execução.
max_logical_reads bigint Número máximo de leituras lógicas que este plano efetuou durante uma única execução.
total_clr_time bigint O tempo, relatado em microssegundos (mas preciso somente em milissegundos), consumido dentro de objetos CLR (Common Language Runtime) Microsoft .NET Framework por execuções desse plano desde sua compilação. Os objetos CLR podem ser procedimentos armazenados, funções, disparadores, tipos e agregados.
Para quem usa objetos CLR, é uma ótima ficar de olho nestes indicadores pois pode existir uma regra de negócio no seu objeto que causa uma flutuação grande no tempo de execução. 
last_clr_time bigint Tempo, relatado em microssegundos (mas preciso somente em milissegundos), consumido pela execução dentro de objetos CLR .NET Framework durante a última execução desse plano. Os objetos CLR podem ser procedimentos armazenados, funções, disparadores, tipos e agregados.
min_clr_time bigint Tempo mínimo, relatado em microssegundos (mas preciso somente em milissegundos), que esse plano já consumiu dentro de objetos CLR .NET Framework durante uma única execução. Os objetos CLR podem ser procedimentos armazenados, funções, disparadores, tipos e agregados.
max_clr_time bigint Tempo máximo, relatado em microssegundos (mas preciso somente em milissegundos), que esse plano já consumiu dentro de CLR .NET Framework durante uma única execução. Os objetos CLR podem ser procedimentos armazenados, funções, disparadores, tipos e agregados.
total_elapsed_time bigint Tempo total decorrido, relatado em microssegundos (mas preciso somente em milissegundos), para execuções concluídas desse plano.
Conforme disse antes, instruções de espera como WAITFOR não são consideradas nesse contadores "_elapsed_time".
last_elapsed_time bigint Tempo decorrido, relatado em microssegundos (mas preciso somente em milissegundos), para a execução completa mais recente desse plano.
min_elapsed_time bigint Tempo decorrido mínimo, relatado em microssegundos (mas preciso somente em milissegundos), para qualquer execução concluída desse plano.
max_elapsed_time bigint Tempo decorrido máximo, relatado em microssegundos (mas preciso somente em milissegundos), para qualquer execução concluída desse plano.
query_hash binary(8) Valor de hash binário calculado na consulta e usado para identificar consultas com lógica semelhante. Você pode usar o hash de consulta para determinar o recurso agregado usado para consultas que são diferentes apenas nos valores literais. Para obter mais informações, consulte Localizando e ajustando consultas semelhantes usando consulta e hashes de plano de consulta.
query_plan_hash binary(8) Valor de hash binário calculado no plano de execução de consulta e usado para identificar planos de execução de consulta semelhantes. Você pode usar o hash de plano de consulta para localizar o custo cumulativo de consultas com planos de execução semelhantes. Para obter mais informações, consulte Localizando e ajustando consultas semelhantes usando consulta e hashes de plano de consulta.
total_rows bigint O número total de linhas retornadas pela consulta. Não pode ser nulo.
last_rows bigint O número total de linhas retornadas pela última execução da consulta. Não pode ser nulo.
min_rows bigint O número mínimo de linhas retornadas pela consulta no número de vezes em que o plano foi executado desde sua última compilação. Não pode ser nulo.
max_rows bigint O número máximo de linhas retornadas pela consulta no número de vezes em que o plano foi executado desde sua última compilação. Não pode ser nulo.

Então é isso pessoal. Qualquer dúvida ou caso queiram "encomendar" uma query, é só lançar nos comentários que responderei o mais rápido possível. Obrigado por visitar o blog e espero publicar novos posts logo mais. Abraços!

Nenhum comentário:

Postar um comentário