Quantcast
Channel: artigos TechNet
Viewing all articles
Browse latest Browse all 8688

Caçando Registros Fantasma no SQL Server

$
0
0
Revisão Atual postado em artigos TechNet por Durval Ramos em 7/3/2014 5:58:09

Introdução

Este artigo simula o comportamento de uma tabela com a ocorrência dos registros fantasmas, como este problema aparece e como é possível acabar com esta falha. 

O processo chamado GhostRecordCleanUp (limpeza de registro fantasma) é executado regularmente e expurga os registros remanescentes dos arquivos de banco de dados (MDF e NDF) que foram marcados para exclusão no arquivo de Log, mas que devido à uma ou mais falhas não foram excluídos fisicamente.

Este processo ocorre de forma transparente para o usuário do SQL Server, mas este artigo vai demonstrar todos os passos desta execução.

É importante esclarecer que esta situação, onde ao menos um registro já foi marcado para ser excluído porém ainda está disponível no banco de dados para todas às operações de consulta, pode gerar números incorretos em aplicações críticas e diversos erros de consistência de dados.

Isto gera um grave problema em aplicações onde a exatidão da informação é crucial para a tomada de decisão de grandes negócios. O nível de isolamento também podem permitir que os registros fantasmas apareçam para outros usuários, mas isto faz parte de um [[outro artigo]]. 

Vamos caçar os Registros Fantasmas!

Como o "Fantasma" aparece

O registro Fantasma pode aparecer em três situações:
  • Durante a execução de comando DELETE ou 
  • Durante a execução de comandos INSERT simultâneos ou
  • Durante a execução de comandos DELETE e INSERT em contextos diferentes, mas relacionados com os mesmos dados indexados
A contagem de registros Fantasma exibe o número de linhas em um índice ou heap(tabela sem índice clusterizado) onde é marcado um ou mais registros para exclusão no banco de dados, mas que ainda não foram excluídos pelo mecanismo do banco de dados. Na verdade, os registros não são apagado do banco de dados imediatamente, eles são apenas marcados como apagados no seu respectivo arquivo de log, isto é um procedimento lógico de exclusão que melhora a performance de execução de comandos com alto uso de I/O em disco.

Durante este momento, o registro pode ser visualizado e contabilizado em diversas consultas, inclusive um ou mais registros fantasmas podem ser manipulados em outro(s) comando(s) Transact-SQL como UPDATE ou DELETE.

Em comandos INSERT, existe a possibilidade de um registro possuir mais de uma "versão" para um mesmo índice. Isto pode ocorrer caso um novo registro tenha seus campos indexados com informações idênticas à outro registro que foi excluído (exceto quando for executado o comando TRUNCATE na tabela), mas que ainda não foi concluído seu processo para efetivamente excluir este registro fisicamente nos arquivos do banco de dados.

Podemos agrupar estes níveis de isolamento em 2 tipos: os Otimistas e os Pessimistas. O isolamento Otimista é baseado na alta concorrência entre às conexões e permite que todos possam ler, inserir, atualizar e apagar registros sem restrições para outras conexões.


Construindo o ambiente para Teste

Para que possamos reproduzir como este problema ocorre, vamos criar uma tabela com dois tipos de índice: um clusterizado (PRIMARY KEY) e outro não clusterizado para o mesmo campo, que neste caso será o campo "ID_EXAMPLE".

--CRIANDO A TABELA PARA DEMONSTRAÇÃO
CREATETABLEdbo.TB_EXAMPLE(
  ID_EXAMPLEintNOTNULLIDENTITY(1,1),
  NM_EXAMPLEvarchar(25)NOTNULL,
  DT_CREATEdatetimeNULLDEFAULT(GETDATE())
);
GO

--CRIANDO 2 ÍNDICES PARA O MESMO CAMPO, PARA IDENTIFICAR O COMPORTAMENTO EM CADA CASO
CREATECLUSTEREDINDEX PK_EXAMPLEONdbo.TB_EXAMPLE(ID_EXAMPLE ASC)
WITH(PAD_INDEX=OFF,STATISTICS_NORECOMPUTE=OFF, SORT_IN_TEMPDB= OFF,DROP_EXISTING=OFF,ONLINE= OFF,ALLOW_ROW_LOCKS=ON,ALLOW_PAGE_LOCKS=ON) ON[PRIMARY]

CREATENONCLUSTEREDINDEXIX_EXAMPLEON dbo.TB_EXAMPLE(ID_EXAMPLE ASC)
WITH(PAD_INDEX=OFF,STATISTICS_NORECOMPUTE=OFF, SORT_IN_TEMPDB= OFF,DROP_EXISTING=OFF,ONLINE= OFF,ALLOW_ROW_LOCKS=ON,ALLOW_PAGE_LOCKS=ON) ON[PRIMARY]
GO


Veja este script SQL executado na imagem abaixo.



Vamos inserir alguns registros para que todos possam entender o processo e simular em seu próprio ambiente.

--INSERINDO 1000 REGISTROS DIFERENTES NA TABELA
INSERTINTOTB_EXAMPLE (NM_EXAMPLE)VALUES ('GHOST ITEM '+CONVERT(VARCHAR,ISNULL(@@IDENTITY, 0)))
GO 1000

--CONSULTANDO OS 1000 REGISTROS 
SELECT
*FROM TB_EXAMPLE
GO


Veja este script SQL executado na imagem abaixo.



Para que possamos visualizar como todo o processo ocorre, temos de alterar configurações de comportamento do SQL SERVER, nos TRACE 3604 (redireciona à saída do Log para o SSMS) e o TRACE 661 (controla a execução do processo de remoção de Registro de Fantasma). 

ATENÇÃO: Não efetue este tipo de alteração em "servidores de Produção". Estes procedimentos não estão documentados no BOL.

--4. Habilita o TRACE "3604" para visualizar os comandos DBCC no SSMS(SQL Server Management Studio)
DBCCTRACEON(3604,-1)
GO 

--5. Habilita o TRACE "661" (Tarefa "GHOST RECORD CLEANUP")
--para visualizar os registros marcados para exclusão em cada página ("registro/índice")
DBCCTRACEON(661,-1)
GO

Veja este script SQL executado na imagem abaixo.



Identificando o Registro Fantasma

Vamos executar a exclusão de alguns registros para esta demonstração, sem nenhum critério específico, vou optar por excluir os 700 primeiros registros. 

Logo à seguir, vamos obter qual foi a última transação executada e especificar à leitura dos dados que foram marcados para alteração ou, neste caso para exclusão.

Isso impede leituras sujas de outras consultas. Se não definirmos a transação que queremos ver, os dados podem ser alterados por outras transações de outras instruções individuais, o que pode modificar o conteúdo que queremos analisar. 

Para que possamos começar a identificar os registros fantasmas, utilizamos a função db_fnlog que executa a leitura sequencial do log. 

Podemos ver na imagem abaixo que logo após a execução do comando DELETE, todas às linhas excluídas estão marcadas no campo "Operation" com o valor "LOP_DELETE_ROWS" e, preventivamente estão disponibilizadas para qualquer outro comando Transact-SQL (SELECT, INSERT, UPDATE ou DELETE) com a marcação do campo "Context" com o valor "LCX_MARK_AS_GHOST".

--6. Apaga 700 registros na tabela numa só vez para que os registros fantasmas apareçam
DELETETOP(700)FROMTB_EXAMPLE;

--Os registros são marcados para exclusão, mas não foram apagados
DECLARE@TRAN_ID    CHAR(20)
SELECT  @TRAN_ID=[Transaction ID]FROMfn_dblog(null,null) 

SELECT*FROM fn_dblog(null,null)WHERE[Transaction ID]=@TRAN_ID;
GO


Veja este script SQL executado na imagem abaixo.


A consulta abaixo vai facilitar nosso acompanhamento desta operação de exclusão, exibindo a quantidade de registros inalterados e a quantidade de registros que estão marcados para serem excluídos para a tabela "TB_EXAMPLE", mas que ainda estão esperando sua execução física no banco de dados. 

Para coletar quais são este registros pendentes, utilizamos a VIEW de sistema chamada sys.dm_db_index_physical_stats.

Os campos que vão nos ajudar na identificação destes dados são:
  • O campo RECORD_COUNT exibe a contagem de registros existentes que não foram afetados na tabela
  • O campo GHOST_RECORD_COUNT é a contagem de registros fantasma que aguardam sua exclusão pela tarefa de limpeza (GHOST RECORD CLEANUP) em cada índice, e;
  • O campo INDEX_TYPE_DESC indica onde os registros estão armazenados, em qual índice de alocação os registros foram afetados.

--7. Exibe a situação atual da tabela
SELECTRECORD_COUNT, GHOST_RECORD_COUNT, INDEX_TYPE_DESC
FROMsys.dm_db_index_physical_stats(DB_ID(N'WI_Infra'),object_id('TB_EXAMPLE'),NULL,NULL ,'DETAILED')
WHEREINDEX_LEVEL= 0
GO

Veja este script SQL executado na imagem abaixo e o destaque dos índices: PK_EXAMPLE (índice clusterizado) e IX_EXAMPLE(índice não clusterizado).

Em ambos os índices, temos a mesma quantidade de registros simplesmente porque adotamos uma exclusão de dados comum para ambos, mas será que os Registros Fantasmas vão manter esta integridade de dados? Vamos ver mais adiante neste artigo.

Abaixo vamos utilizar um comando DBCC (DataBase Consistency Checker) chamado PAGE, que está documentado em um KB. Com isto, podemos obter como os dados estão armazenados em uma estrutura de página do SQL Server.

Logo após a execução, podemos ver a alocação do conteúdo dos registros por página. Vamos concentrar nossa demonstração agora no intervalo do campo "Field" noarquivo lógico 1, entre as páginas 1321 à 1327. Neste intervalo temos a indicação que existe Registros Fantasmas, marcados no campo "VALUE" com parte da descrição com o texto "Has Ghost".

--8. A tarefa "Ghost Cleanup" verifica todas às paginas do banco de dados à procura de registros fantasmas
--Efetuando o despejo da paginação podemos ver que os registros fantasmas são marcados como "Has Ghost"
DBCCPAGE('WI_Infra',1,1,3)WITHTABLERESULTS
GO

Veja este script SQL executado na imagem abaixo.

Detalhando a página 1327 do arquivo lógico 1, podemos constatar que realmente temos uma alocação de registro fantasma, onde o parâmetro "m_ghostRecCnt" é verdadeiro.

--9. Despejando os detalhes de um determinado registro marcado como fantasma, podemos ver
--sua referência na página e o flag "m_ghostRecCnt" indicando este endereço como fantasma
DBCCPAGE('WI_Infra',1,1327,3)WITHTABLERESULTS
GO

Veja este script SQL executado na imagem abaixo.




Acabando com o Registro Fantasma

Para acabar com os Registros Fantasmas, vamos executar o mesmo comando executado automaticamente pelo SQL Server e que desabilitamos através do TRACE 661.

Vamos executar a tarefa "Ghost Cleanup Rows" para executar todas às pendências e expurgar às falhas de Registros Fantasmas. Destacamos abaixo o intervalo alterado na arquivo lógico 1 da página 1321 à 1327, onde o SQL Server identifica o limite da página 1320 e retoma na página 1328. 

--10. Executa a tarefa "Ghost Cleanup" nos registros marcados como fantasma
DBCCForceGhostCleanup;
GO

Veja este script SQL executado na imagem abaixo.



Após executar manualmente à tarefa de rotina do SQL Server, podemos verificar o resultado abaixo:

-- 11. Verificando se os registros fantasmas ainda existem
SELECTRECORD_COUNT, GHOST_RECORD_COUNT, INDEX_TYPE_DESC
FROMsys.dm_db_index_physical_stats(DB_ID(N'WI_Infra'),object_id('TB_EXAMPLE'),NULL,NULL ,'DETAILED')
WHEREINDEX_LEVEL= 0
GO


Veja este script SQL executado na imagem abaixo.

Pode parecer inesperado, mas esta é uma situação comum, sobrou um Registro Fantasma que não foi expurgado. Neste caso, o índice clusterizado ficará fragmentado porque referência deste registro está perdida. 

A solução neste caso é simples, basta executar um REBUILD do índice com problema e o Registro Fantasma é desalocado do banco de dados.

--12. Como sobrou um registro fanstama, forçamos a correção executando um REBUILD do índice
ALTERINDEXPK_EXAMPLE ONdbo.TB_EXAMPLEREBUILD;
GO

-- 13. Verificando se os registros fantasmas ainda existem após o REBUILD
SELECTRECORD_COUNT, GHOST_RECORD_COUNT, INDEX_TYPE_DESC
FROMsys.dm_db_index_physical_stats(DB_ID(N'WI_Infra'),object_id('TB_EXAMPLE'),NULL,NULL ,'DETAILED')
WHEREINDEX_LEVEL= 0
GO

Veja este script SQL executado na imagem abaixo.


Como alternativa, em alguns casos é possível resolver o problema destas sobras de Registros Fantasma (principalmente nos casos da inclusão de mais de um registro com chave idêntica - Event ID 2512) é recomendado a execução do comando DBCC CHECKDB com uma das cláusulas REPAIR.

Outra opção, é executar a procedure de sistema sp_clean_db_free_space

Conclusão

Este artigo comprovou que os Registros Fantasmas existem (e sempre vão existir), porque eles fazem parte do processo interno de manipulação de dados.

Podemos minimizar e corrigir os Registros Fantasmas que o SQL Server não consegue eliminar, realizando periodicamente os processos de manutenção de índices, como indicado.


Veja Também


Referências


Homenagem

Este artigo foi criado em memória do ator Harold Ramis (Dr. Egon) da trilogia dos filmesCaça Fantasmas, que animou parte da minha infância.


Outros Idiomas



Tags: Durval

Viewing all articles
Browse latest Browse all 8688


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>