Em muitas cargas de trabalho do SQL Server, especialmente OLTP, o log de transações do banco de dados pode ser um gargalo que aumenta o tempo que uma transação leva para ser concluída. A maioria das pessoas supõe que o subsistema de E/S é o gargalo real, pois ele não consegue acompanhar a quantidade de log de transações gerada pela carga de trabalho.
Latência de gravação do log de transações
A latência das operações de gravação no log de transações pode ser monitorada usando o
sys.dm_io_virtual_file_stats
DMV e correlacionado com o WRITELOG
esperas que estão ocorrendo no sistema. Gravei um vídeo de demonstração da análise de E/S do log de transações em 2011, então não vou repetir tudo isso neste post. Você pode obter o vídeo aqui e o código de demonstração aqui (adequado para execução imediata em produção). Se a latência de gravação for maior do que o esperado para seu subsistema de E/S, o subsistema de E/S não poderá acompanhar, como é a suposição geral. Isso significa que o subsistema de E/S precisa ser melhorado? Não necessariamente.
Em muitos sistemas clientes, descobri que uma proporção significativa de registros de log gerados é desnecessária e, se você puder reduzir o número de registros de log gerados, reduzirá a quantidade de logs de transações gravados no disco. Isso deve se traduzir em uma redução na latência de gravação, reduzindo assim o tempo de conclusão da transação.
Há duas causas principais da geração de registros de log estranhos:índices não clusterizados não utilizados e índices fragmentados.
Índices não clusterizados não utilizados
Sempre que um registro é inserido em uma tabela, um registro deve ser inserido em cada índice não clusterizado definido na tabela (com exceção dos índices filtrados com filtros apropriados, que ignorarei a partir deste ponto). Isso significa que registros de log extras são gerados, pelo menos um por índice não clusterizado, para cada inserção de tabela. A mesma coisa se aplica à exclusão de um registro em uma tabela – os registros correspondentes devem ser excluídos de todos os índices não clusterizados. Para uma atualização de um registro de tabela, os registros de índice não clusterizados são atualizados somente se as colunas de chave de índice não clusterizado ou as colunas incluídas fizerem parte da atualização.
Essas operações são necessárias, é claro, para manter cada índice não clusterizado correto em relação à tabela, mas se o índice não clusterizado não for utilizado pela carga de trabalho, as operações e os registros de log produzidos por eles serão uma sobrecarga desnecessária. Além disso, se esses índices não utilizados ficarem fragmentados (o que discutirei mais adiante neste post), as tarefas regulares de manutenção de índice também operarão neles, gerando ainda mais registros de log (do índice
REBUILD
ou REORGANIZE
operações) completamente desnecessariamente. Índices não usados vêm de uma variedade de fontes, como alguém criando por engano um índice por coluna de tabela, alguém criando todos os índices sugeridos pelos DMVs de índice ausentes ou alguém criando todos os índices sugeridos pelo Orientador de Otimização de Banco de Dados. Também pode ser que as características da carga de trabalho tenham mudado e, portanto, os índices que costumavam ser úteis não estão mais sendo usados.
De onde quer que tenham vindo, os índices não utilizados devem ser removidos para reduzir sua sobrecarga. Você pode determinar quais índices não são usados usando o DMV sys.dm_db_index_usage_stats, e eu recomendo que você leia as postagens de meus colegas Kimberly L. Tripp (aqui) e Joe Sack (aqui e aqui), pois eles explicam como usar o DMV corretamente.
Fragmentação do índice
A maioria das pessoas pensa na fragmentação do índice como um problema que afeta as consultas que precisam ler grandes quantidades de dados. Embora este seja um dos problemas que a fragmentação pode causar, a fragmentação também é um problema devido à forma como ocorre.
A fragmentação é causada por uma operação chamada divisão de página. A causa mais simples de uma divisão de página é quando um registro de índice deve ser inserido em uma página específica (por causa de seu valor de chave) e a página não possui espaço livre suficiente. Nesse cenário, as seguintes operações ocorrerão:
- Uma nova página de índice é alocada e formatada
- Alguns dos registros da página inteira são movidos para a nova página, criando assim espaço livre na página necessária
- A nova página está vinculada à estrutura do índice
- O novo registro é inserido na página necessária
Todas essas operações geram registros de log e, como você pode imaginar, isso pode ser significativamente maior do que o necessário para inserir um novo registro em uma página que não requer uma divisão de página. Em 2009 eu postei no blog uma análise do custo de divisão de página em termos de log de transações e encontrei alguns casos em que uma divisão de página gerou mais de 40 vezes mais log de transações do que uma inserção normal!
O primeiro passo para reduzir o custo extra é remover índices não utilizados, como descrevi acima, para que eles não gerem divisões de página. A segunda etapa é identificar os índices restantes que estão se tornando fragmentados (e, portanto, devem estar sofrendo divisões de página) usando o
sys.dm_db_index_physical_stats
DMV (ou o novo SQL Sentry Fragmentation Manager) e criando proativamente espaço livre neles usando um fator de preenchimento de índice. Um fator de preenchimento instrui o SQL Server a deixar espaço vazio nas páginas de índice quando o índice é compilado, reconstruído ou reorganizado para que haja espaço para permitir que novos registros sejam inseridos sem exigir uma divisão de página, reduzindo assim os registros de log extras gerados. É claro que nada vem de graça – a desvantagem ao usar fillfactors é que você está provisionando proativamente espaço extra nos índices para evitar que mais registros de log sejam gerados – mas isso geralmente é uma boa compensação a ser feita. Escolher um fator de preenchimento é relativamente fácil e eu escrevi sobre isso aqui.
Resumo
Reduzir a latência de gravação de um arquivo de log de transações nem sempre significa mudar para um subsistema de E/S mais rápido ou segregar o arquivo em sua própria parte do subsistema de E/S. Com algumas análises simples dos índices em seu banco de dados, você pode reduzir significativamente a quantidade de registros de log de transações sendo gerados, levando a uma redução proporcional na latência de gravação.
Existem outros problemas mais sutis que podem afetar o desempenho do log de transações, e vou explorá-los em um post futuro.