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.