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

Uma olhada em DBCC CHECKCONSTRAINTS e E/S


Um elemento comum usado no projeto de banco de dados é a restrição. As restrições vêm em uma variedade de sabores (por exemplo, padrão, exclusivo) e impõem a integridade da(s) coluna(s) em que existem. Quando bem implementadas, as restrições são um componente poderoso no design de um banco de dados porque impedem que dados que não atendem a critérios definidos entrem em um banco de dados. No entanto, as restrições podem ser violadas usando comandos como WITH NOCHECK e IGNORE_CONSTRAINTS . Além disso, ao usar o REPAIR_ALLOW_DATA_LOSS opção com qualquer DBCC CHECK comando para reparar a corrupção do banco de dados, as restrições não são consideradas.

Consequentemente, é possível ter dados inválidos no banco de dados – dados que não seguem uma restrição ou dados que não mantêm mais o relacionamento esperado de chave primária-estrangeira. SQL Server inclui o DBCC CHECKCONSTRAINTS instrução para encontrar dados que violam restrições. Após a execução de qualquer opção de reparo, execute DBCC CHECKCONSTRAINTS para todo o banco de dados para garantir que não haja problemas, e pode haver momentos em que seja apropriado executar CHECKCONSTRAINTS para uma restrição de seleção ou uma tabela. Manter a integridade dos dados é fundamental e, embora não seja comum executar DBCC CHECKCONSTRAINTS em uma base programada para encontrar dados inválidos, quando você precisar executá-lo, é uma boa ideia entender o impacto no desempenho que isso pode criar.

DBCC CHECKCONSTRAINTS pode ser executado para uma única restrição, uma tabela ou todo o banco de dados. Como outros comandos de verificação, pode levar um tempo considerável para ser concluído e consumirá recursos do sistema, principalmente para bancos de dados maiores. Ao contrário de outros comandos de verificação, CHECKCONSTRAINTS não usa um instantâneo de banco de dados.

Com eventos estendidos, podemos examinar o uso de recursos quando executamos DBCC CHECKCONSTRAINTS para a mesa. Para mostrar melhor o impacto, executei o script Create Enlarged AdventureWorks Tables.sql de Jonathan Kehayias (blog | @SQLPoolBoy) para criar tabelas maiores. O script de Jonathan cria apenas os índices para as tabelas, portanto, as instruções abaixo são necessárias para adicionar algumas restrições selecionadas:
USE [AdventureWorks2012];
GO
 
ALTER TABLE [Sales].[SalesOrderDetailEnlarged]
WITH CHECK ADD CONSTRAINT [FK_SalesOrderDetailEnlarged_SalesOrderHeaderEnlarged_SalesOrderID]
FOREIGN KEY([SalesOrderID])
REFERENCES [Sales].[SalesOrderHeaderEnlarged] ([SalesOrderID])
ON DELETE CASCADE;
GO
 
ALTER TABLE [Sales].[SalesOrderDetailEnlarged]
WITH CHECK ADD CONSTRAINT [CK_SalesOrderDetailEnlarged_OrderQty]
CHECK (([OrderQty]>(0)))
GO
 
ALTER TABLE [Sales].[SalesOrderDetailEnlarged]
WITH CHECK ADD CONSTRAINT [CK_SalesOrderDetailEnlarged_UnitPrice]
CHECK (([UnitPrice]>=(0.00)));
GO
 
ALTER TABLE [Sales].[SalesOrderHeaderEnlarged]
WITH CHECK ADD CONSTRAINT [CK_SalesOrderHeaderEnlarged_DueDate]
CHECK (([DueDate]>=[OrderDate]))
GO
 
ALTER TABLE [Sales].[SalesOrderHeaderEnlarged]
WITH CHECK ADD CONSTRAINT [CK_SalesOrderHeaderEnlarged_Freight]
CHECK (([Freight]>=(0.00)))
GO

Podemos verificar quais restrições existem usando sp_helpconstraint :
EXEC sp_helpconstraint '[Sales].[SalesOrderDetailEnlarged]';
GO


saída sp_helpconstraint

Uma vez que as restrições existam, podemos comparar o uso de recursos para DBCC CHECKCONSTRAINTS para uma única restrição, uma tabela e todo o banco de dados usando Eventos Estendidos. Primeiro vamos criar uma sessão que simplesmente capture sp_statement_completed eventos, inclui o sql_text ação e envia a saída para o ring_buffer :
CREATE EVENT SESSION [Constraint_Performance] ON SERVER
ADD EVENT sqlserver.sp_statement_completed
(
  ACTION(sqlserver.database_id,sqlserver.sql_text)
)
ADD TARGET package0.ring_buffer
(
  SET max_events_limit=(5000)
)
WITH 
(
    MAX_MEMORY=32768 KB, EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS,
    MAX_DISPATCH_LATENCY=30 SECONDS, MAX_EVENT_SIZE=0 KB,
    MEMORY_PARTITION_MODE=NONE, TRACK_CAUSALITY=OFF, STARTUP_STATE=OFF
);
GO

Em seguida, iniciaremos a sessão e executaremos cada um dos DBCC CHECKCONSTRAINT comandos e, em seguida, envie o buffer de anel para uma tabela temporária para manipular. Observe que DBCC DROPCLEANBUFFERS é executado antes de cada verificação para que cada uma inicie do cache frio, mantendo um campo de teste de nível.
ALTER EVENT SESSION [Constraint_Performance]
ON SERVER
STATE=START;
GO
 
USE [AdventureWorks2012];
GO
 
DBCC DROPCLEANBUFFERS;
GO
DBCC CHECKCONSTRAINTS ('[Sales].[CK_SalesOrderDetailEnlarged_OrderQty]') WITH NO_INFOMSGS;
GO
DBCC DROPCLEANBUFFERS;
GO
DBCC CHECKCONSTRAINTS ('[Sales].[FK_SalesOrderDetailEnlarged_SalesOrderHeaderEnlarged_SalesOrderID]') WITH NO_INFOMSGS;
GO
DBCC DROPCLEANBUFFERS;
GO
DBCC CHECKCONSTRAINTS ('[Sales].[SalesOrderDetailEnlarged]') WITH NO_INFOMSGS;
GO
DBCC DROPCLEANBUFFERS;
GO
DBCC CHECKCONSTRAINTS WITH ALL_CONSTRAINTS, NO_INFOMSGS;
GO
 
DECLARE @target_data XML;
 
SELECT @target_data = CAST(target_data AS XML)
  FROM sys.dm_xe_sessions AS s
  INNER JOIN sys.dm_xe_session_targets AS t 
  ON t.event_session_address = s.[address]
  WHERE s.name = N'Constraint_Performance'
  AND t.target_name = N'ring_buffer';
 
SELECT
  n.value('(@name)[1]', 'varchar(50)') AS event_name,
  DATEADD(HOUR ,DATEDIFF(HOUR, SYSUTCDATETIME(), SYSDATETIME()),n.value('(@timestamp)[1]', 'datetime2')) AS [timestamp],
  n.value('(data[@name="duration"]/value)[1]', 'bigint') AS duration,
  n.value('(data[@name="physical_reads"]/value)[1]', 'bigint') AS physical_reads,
  n.value('(data[@name="logical_reads"]/value)[1]', 'bigint') AS logical_reads,
  n.value('(action[@name="sql_text"]/value)[1]', 'varchar(max)') AS sql_text,
  n.value('(data[@name="statement"]/value)[1]', 'varchar(max)') AS [statement]
INTO #EventData
FROM @target_data.nodes('RingBufferTarget/event[@name=''sp_statement_completed'']') AS q(n);
GO
 
ALTER EVENT SESSION [Constraint_Performance]
ON SERVER
STATE=STOP;
GO

Analisando o ring_buffer em uma tabela temporária pode levar algum tempo adicional (cerca de 20 segundos na minha máquina), mas a consulta repetida dos dados é mais rápida a partir de uma tabela temporária do que por meio do ring_buffer . Se observarmos a saída, veremos que existem várias instruções executadas para cada DBCC CHECKCONSTRAINTS :
SELECT *
FROM #EventData
WHERE [sql_text] LIKE 'DBCC%';


Saída de eventos estendidos

Usando eventos estendidos para explorar o funcionamento interno de CHECKCONSTRAINTS é uma tarefa interessante, mas o que realmente nos interessa aqui é o consumo de recursos – especificamente E/S. Podemos agregar o physical_reads para cada comando de verificação para comparar a E/S:
SELECT [sql_text], SUM([physical_reads]) AS [Total Reads]
FROM #EventData
WHERE [sql_text] LIKE 'DBCC%'
GROUP BY [sql_text];


E/S agregada para verificações

Para verificar uma restrição, o SQL Server precisa ler os dados para localizar quaisquer linhas que possam violar a restrição. A definição do CK_SalesOrderDetailEnlarged_OrderQty restrição é [OrderQty] > 0 . A restrição de chave estrangeira, FK_SalesOrderDetailEnlarged_SalesOrderHeaderEnlarged_SalesOrderID , estabelece um relacionamento em SalesOrderID entre [Sales].[SalesOrderHeaderEnlarged] e [Sales].[SalesOrderDetailEnlarged] mesas. Intuitivamente, pode parecer que a verificação da restrição de chave estrangeira exigiria mais E/S, pois o SQL Server deve ler dados de duas tabelas. No entanto, [SalesOrderID] existe no nível folha do IX_SalesOrderHeaderEnlarged_SalesPersonID índice não clusterizado em [Sales].[SalesOrderHeaderEnlarged] tabela e no IX_SalesOrderDetailEnlarged_ProductID índice no [Sales].[SalesOrderDetailEnlarged] tabela. Assim, o SQL Server verifica esses dois índices para comparar o [SalesOrderID] valores entre as duas tabelas. Isso requer pouco mais de 19.000 leituras. No caso do CK_SalesOrderDetailEnlarged_OrderQty restrição, a [OrderQty] A coluna não está incluída em nenhum índice, portanto, ocorre uma verificação completa do índice clusterizado, o que requer mais de 72.000 leituras.

Quando todas as restrições de uma tabela são verificadas, os requisitos de E/S são maiores do que se uma única restrição fosse verificada e aumentam novamente quando todo o banco de dados é verificado. No exemplo acima, o [Sales].[SalesOrderHeaderEnlarged] e [Sales].[SalesOrderDetailEnlarged] tabelas são desproporcionalmente maiores do que outras tabelas no banco de dados. Isso não é incomum em cenários do mundo real; muitas vezes os bancos de dados têm várias tabelas grandes que compreendem uma grande parte do banco de dados. Ao executar CHECKCONSTRAINTS para essas tabelas, esteja ciente do consumo potencial de recursos necessário para a verificação. Execute verificações fora do horário comercial quando possível para minimizar o impacto do usuário. Caso as verificações precisem ser executadas durante o horário comercial normal, entender quais restrições existem e quais índices existem para dar suporte à validação pode ajudar a avaliar o efeito da verificação. Você pode executar verificações em um ambiente de teste ou desenvolvimento primeiro para entender o impacto no desempenho, mas podem existir variações com base em hardware, dados comparáveis ​​etc.  E, por fim, lembre-se de que sempre que executar um comando de verificação que inclua o REPAIR_ALLOW_DATA_LOSS opção, siga o reparo com DBCC CHECKCONSTRAINTS . O reparo do banco de dados não leva em consideração nenhuma restrição, pois a corrupção é corrigida, portanto, além de potencialmente perder dados, você pode acabar com dados que violam uma ou mais restrições em seu banco de dados.