Database
 sql >> Database >  >> RDS >> Database

Uno sguardo a DBCC CHECKCONSTRAINTS e I/O

Un elemento comune utilizzato nella progettazione di database è il vincolo. I vincoli sono disponibili in una varietà di versioni (ad es. predefinito, univoco) e impongono l'integrità delle colonne su cui esistono. Se implementati correttamente, i vincoli sono un potente componente nella progettazione di un database perché impediscono ai dati che non soddisfano i criteri stabiliti di entrare in un database. Tuttavia, i vincoli possono essere violati utilizzando comandi come WITH NOCHECK e IGNORE_CONSTRAINTS . Inoltre, quando si utilizza il REPAIR_ALLOW_DATA_LOSS opzione con qualsiasi DBCC CHECK comando per riparare il danneggiamento del database, i vincoli non vengono considerati.

Di conseguenza, è possibile che nel database siano presenti dati non validi:dati che non aderiscono a un vincolo o dati che non mantengono più la relazione chiave primaria-esterna prevista. SQL Server include DBCC CHECKCONSTRAINTS dichiarazione per trovare dati che violano i vincoli. Dopo l'esecuzione di qualsiasi opzione di riparazione, eseguire DBCC CHECKCONSTRAINTS per l'intero database per garantire che non ci siano problemi e potrebbero esserci momenti in cui è opportuno eseguire CHECKCONSTRAINTS per un vincolo selezionato o una tabella. Mantenere l'integrità dei dati è fondamentale e, sebbene non sia tipico eseguire DBCC CHECKCONSTRAINTS su base programmata per trovare dati non validi, quando è necessario eseguirlo, è una buona idea comprendere l'impatto sulle prestazioni che potrebbe creare.

DBCC CHECKCONSTRAINTS può essere eseguito per un singolo vincolo, una tabella o l'intero database. Come altri comandi di controllo, il completamento può richiedere molto tempo e consumerà risorse di sistema, in particolare per i database più grandi. A differenza di altri comandi di verifica, CHECKCONSTRAINTS non utilizza uno snapshot del database.

Con gli eventi estesi possiamo esaminare l'utilizzo delle risorse quando eseguiamo DBCC CHECKCONSTRAINTS per la tavola. Per mostrare meglio l'impatto, ho eseguito lo script Create Enlarged AdventureWorks Tables.sql di Jonathan Kehayias (blog | @SQLPoolBoy) per creare tabelle più grandi. Lo script di Jonathan crea solo gli indici per le tabelle, quindi le istruzioni seguenti sono necessarie per aggiungere alcuni vincoli selezionati:

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

Possiamo verificare quali vincoli esistono usando sp_helpconstraint :

EXEC sp_helpconstraint '[Sales].[SalesOrderDetailEnlarged]';
GO


sp_helpconstraint output

Una volta che esistono i vincoli, possiamo confrontare l'utilizzo delle risorse per DBCC CHECKCONSTRAINTS per un singolo vincolo, una tabella e l'intero database utilizzando gli eventi estesi. Per prima cosa creeremo una sessione che catturi semplicemente sp_statement_completed eventi, include sql_text azione e invia l'output al 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

Successivamente avvieremo la sessione ed eseguiremo ciascuno dei DBCC CHECKCONSTRAINT comandi, quindi invia il buffer dell'anello a una tabella temporanea da manipolare. Nota che DBCC DROPCLEANBUFFERS viene eseguito prima di ogni controllo in modo che ciascuno si avvii dalla cache fredda, mantenendo un campo di test di livello.

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

Analisi del ring_buffer in una tabella temporanea potrebbe richiedere del tempo aggiuntivo (circa 20 secondi sulla mia macchina), ma l'interrogazione ripetuta dei dati è più veloce da una tabella temporanea che tramite il ring_buffer . Se osserviamo l'output, vediamo che ci sono diverse istruzioni eseguite per ogni DBCC CHECKCONSTRAINTS :

SELECT *
FROM #EventData
WHERE [sql_text] LIKE 'DBCC%';


Output eventi estesi

Utilizzo degli eventi estesi per approfondire il funzionamento interno di CHECKCONSTRAINTS è un compito interessante, ma ciò che ci interessa davvero qui è il consumo di risorse, in particolare l'I/O. Possiamo aggregare le physical_reads per ogni comando di controllo per confrontare l'I/O:

SELECT [sql_text], SUM([physical_reads]) AS [Total Reads]
FROM #EventData
WHERE [sql_text] LIKE 'DBCC%'
GROUP BY [sql_text];


I/O aggregato per i controlli

Per controllare un vincolo, SQL Server deve leggere i dati per trovare le righe che potrebbero violare il vincolo. La definizione del CK_SalesOrderDetailEnlarged_OrderQty il vincolo è [OrderQty] > 0 . Il vincolo della chiave esterna, FK_SalesOrderDetailEnlarged_SalesOrderHeaderEnlarged_SalesOrderID , stabilisce una relazione su SalesOrderID tra [Sales].[SalesOrderHeaderEnlarged] e [Sales].[SalesOrderDetailEnlarged] tavoli. Intuitivamente potrebbe sembrare che il controllo del vincolo della chiave esterna richieda più I/O, poiché SQL Server deve leggere i dati da due tabelle. Tuttavia, [SalesOrderID] esiste nel livello foglia di IX_SalesOrderHeaderEnlarged_SalesPersonID indice non cluster su [Sales].[SalesOrderHeaderEnlarged] tabella e nel IX_SalesOrderDetailEnlarged_ProductID indice su [Sales].[SalesOrderDetailEnlarged] tavolo. Pertanto, SQL Server esegue la scansione di questi due indici per confrontare [SalesOrderID] valori tra le due tabelle. Ciò richiede poco più di 19.000 letture. Nel caso di CK_SalesOrderDetailEnlarged_OrderQty vincolo, il [OrderQty] colonna non è inclusa in nessun indice, quindi viene eseguita una scansione completa dell'indice cluster, che richiede oltre 72.000 letture.

Quando vengono verificati tutti i vincoli per una tabella, i requisiti di I/O sono maggiori rispetto a quando viene verificato un singolo vincolo e aumentano nuovamente quando viene verificato l'intero database. Nell'esempio sopra, [Sales].[SalesOrderHeaderEnlarged] e [Sales].[SalesOrderDetailEnlarged] le tabelle sono sproporzionatamente più grandi di altre tabelle nel database. Questo non è raro negli scenari del mondo reale; molto spesso i database hanno diverse tabelle di grandi dimensioni che costituiscono una grande porzione del database. Durante l'esecuzione di CHECKCONSTRAINTS per queste tabelle, tenere presente il potenziale consumo di risorse richiesto per il controllo. Esegui controlli durante le ore di riposo quando possibile per ridurre al minimo l'impatto sull'utente. Nel caso in cui i controlli debbano essere eseguiti durante il normale orario lavorativo, capire quali vincoli esistono e quali indici esistono per supportare la convalida può aiutare a valutare l'effetto del controllo. Puoi eseguire prima i controlli in un ambiente di test o sviluppo per comprendere l'impatto sulle prestazioni, ma in seguito possono esistere variazioni in base all'hardware, a dati comparabili e così via. E infine, ricorda che ogni volta che esegui un comando di controllo che include REPAIR_ALLOW_DATA_LOSS opzione, segui la riparazione con DBCC CHECKCONSTRAINTS . La riparazione del database non tiene conto di alcun vincolo poiché il danneggiamento è stato risolto, quindi oltre alla potenziale perdita di dati, potresti ritrovarti con dati che violano uno o più vincoli nel tuo database.