Een gemeenschappelijk element dat wordt gebruikt bij het ontwerpen van databases is de beperking. Beperkingen zijn er in verschillende smaken (bijvoorbeeld standaard, uniek) en versterken de integriteit van de kolom(men) waarop ze bestaan. Wanneer ze goed worden geïmplementeerd, zijn beperkingen een krachtig onderdeel in het ontwerp van een database, omdat ze voorkomen dat gegevens die niet aan de gestelde criteria voldoen, in een database terechtkomen. Beperkingen kunnen echter worden geschonden met commando's zoals WITH NOCHECK
en IGNORE_CONSTRAINTS
. Bovendien, bij gebruik van de REPAIR_ALLOW_DATA_LOSS
optie met een DBCC CHECK
opdracht om databasecorruptie te herstellen, er wordt geen rekening gehouden met beperkingen.
Bijgevolg is het mogelijk om ongeldige gegevens in de database te hebben - ofwel gegevens die niet voldoen aan een beperking, of gegevens die niet langer de verwachte primaire-buitenlandse sleutelrelatie behouden. SQL Server bevat de DBCC CHECKCONSTRAINTS
statement om gegevens te vinden die de beperkingen schenden. Nadat een reparatieoptie is uitgevoerd, voert u DBCC CHECKCONSTRAINTS
uit voor de hele database om ervoor te zorgen dat er geen problemen zijn, en er kunnen momenten zijn waarop het gepast is om CHECKCONSTRAINTS
uit te voeren voor een selecte beperking of een tabel. Het handhaven van de gegevensintegriteit is van cruciaal belang, en hoewel het niet gebruikelijk is om DBCC CHECKCONSTRAINTS
uit te voeren op een geplande basis om ongeldige gegevens te vinden. Wanneer u deze toch moet uitvoeren, is het een goed idee om de prestatie-impact te begrijpen die deze kan veroorzaken.
DBCC CHECKCONSTRAINTS
kan worden uitgevoerd voor een enkele beperking, een tabel of de hele database. Net als andere controlecommando's kan het veel tijd kosten om het te voltooien en systeembronnen in beslag nemen, vooral voor grotere databases. In tegenstelling tot andere controlecommando's, CHECKCONSTRAINTS
maakt geen gebruik van een database-snapshot.
Met Extended Events kunnen we het resourcegebruik onderzoeken wanneer we DBCC CHECKCONSTRAINTS
uitvoeren voor de tafel. Om de impact beter te laten zien, heb ik het Create Enlarged AdventureWorks Tables.sql-script van Jonathan Kehayias (blog | @SQLPoolBoy) uitgevoerd om grotere tabellen te maken. Het script van Jonathan maakt alleen de indexen voor de tabellen, dus de onderstaande instructies zijn nodig om een paar geselecteerde beperkingen toe te voegen:
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
We kunnen controleren welke beperkingen er zijn met behulp van sp_helpconstraint
:
EXEC sp_helpconstraint '[Sales].[SalesOrderDetailEnlarged]'; GO
sp_helpconstraint-uitvoer
Zodra de beperkingen bestaan, kunnen we het resourcegebruik vergelijken voor DBCC CHECKCONSTRAINTS
voor een enkele beperking, een tabel en de hele database met behulp van Extended Events. Eerst maken we een sessie die eenvoudig sp_statement_completed
. vastlegt evenementen, bevat de sql_text
actie, en stuurt de uitvoer naar de 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
Vervolgens starten we de sessie en voeren we elk van de DBCC CHECKCONSTRAINT
. uit commando's en voer vervolgens de ringbuffer uit naar een tijdelijke tabel om te manipuleren. Merk op dat DBCC DROPCLEANBUFFERS
wordt vóór elke controle uitgevoerd, zodat elke start vanuit de koude cache, waarbij een vlak testveld wordt behouden.
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
Ontleden van de ring_buffer
in een tijdelijke tabel kan wat extra tijd kosten (ongeveer 20 seconden op mijn computer), maar herhaald opvragen van de gegevens is sneller vanuit een tijdelijke tabel dan via de ring_buffer
. Als we naar de uitvoer kijken, zien we dat er verschillende instructies worden uitgevoerd voor elke DBCC CHECKCONSTRAINTS
:
SELECT * FROM #EventData WHERE [sql_text] LIKE 'DBCC%';
Uitgebreide uitvoer van gebeurtenissen
Extended Events gebruiken om in de werking van CHECKCONSTRAINTS
te graven is een interessante taak, maar waar we hier echt in geïnteresseerd zijn, is het verbruik van hulpbronnen - met name I/O. We kunnen de physical_reads
. samenvoegen voor elk controlecommando om de I/O te vergelijken:
SELECT [sql_text], SUM([physical_reads]) AS [Total Reads] FROM #EventData WHERE [sql_text] LIKE 'DBCC%' GROUP BY [sql_text];
Geaggregeerde I/O voor cheques
Om een beperking te controleren, moet SQL Server de gegevens lezen om rijen te vinden die de beperking kunnen schenden. De definitie van de CK_SalesOrderDetailEnlarged_OrderQty
beperking is [OrderQty] > 0
. De externe-sleutelbeperking, FK_SalesOrderDetailEnlarged_SalesOrderHeaderEnlarged_SalesOrderID
, legt een relatie op SalesOrderID
tussen de [Sales].[SalesOrderHeaderEnlarged]
en [Sales].[SalesOrderDetailEnlarged]
tafels. Intuïtief lijkt het alsof het controleren van de externe-sleutelbeperking meer I/O vereist, aangezien SQL Server gegevens uit twee tabellen moet lezen. Echter, [SalesOrderID]
bestaat op het bladniveau van de IX_SalesOrderHeaderEnlarged_SalesPersonID
niet-geclusterde index op de [Sales].[SalesOrderHeaderEnlarged]
tabel, en in de IX_SalesOrderDetailEnlarged_ProductID
index op de [Sales].[SalesOrderDetailEnlarged]
tafel. Als zodanig scant SQL Server die twee indexen om de [SalesOrderID]
. te vergelijken waarden tussen de twee tabellen. Hiervoor zijn iets meer dan 19.000 reads nodig. In het geval van de CK_SalesOrderDetailEnlarged_OrderQty
beperking, de [OrderQty]
kolom is in geen enkele index opgenomen, dus er vindt een volledige scan van de geclusterde index plaats, waarvoor meer dan 72.000 leesbewerkingen nodig zijn.
Wanneer alle beperkingen voor een tabel zijn gecontroleerd, zijn de I/O-vereisten hoger dan wanneer een enkele beperking wordt gecontroleerd, en nemen ze weer toe wanneer de hele database wordt gecontroleerd. In het bovenstaande voorbeeld is de [Sales].[SalesOrderHeaderEnlarged]
en [Sales].[SalesOrderDetailEnlarged]
tabellen zijn onevenredig groter dan andere tabellen in de database. Dit is niet ongebruikelijk in real-world scenario's; heel vaak hebben databases meerdere grote tabellen die een groot deel van de database uitmaken. Bij het uitvoeren van CHECKCONSTRAINTS
voor deze tabellen moet u rekening houden met het potentiële verbruik van hulpbronnen dat nodig is voor de controle. Voer indien mogelijk controles uit buiten kantooruren om de impact voor de gebruiker te minimaliseren. In het geval dat controles tijdens normale kantooruren moeten worden uitgevoerd, kan inzicht in welke beperkingen er zijn en welke indexen bestaan om validatie te ondersteunen, helpen om het effect van de controle te meten. U kunt eerst controles uitvoeren in een test- of ontwikkelomgeving om inzicht te krijgen in de prestatie-impact, maar er kunnen dan variaties bestaan op basis van hardware, vergelijkbare gegevens, enz. En ten slotte, onthoud dat elke keer dat u een controleopdracht uitvoert met de REPAIR_ALLOW_DATA_LOSS
optie, volg de reparatie met DBCC CHECKCONSTRAINTS
. Databasereparatie houdt geen rekening met beperkingen aangezien corruptie is verholpen, dus naast mogelijk verlies van gegevens, kunt u eindigen met gegevens die een of meer beperkingen in uw database schenden.