Ett vanligt element som används i databasdesign är begränsningen. Begränsningar finns i en mängd olika smaker (t.ex. standard, unik) och upprätthåller integriteten hos kolumnen/kolumnerna där de finns. När de implementeras väl är begränsningar en kraftfull komponent i designen av en databas eftersom de förhindrar att data som inte uppfyller fastställda kriterier kommer in i en databas. Däremot kan begränsningar överträdas med kommandon som WITH NOCHECK
och IGNORE_CONSTRAINTS
. Dessutom, när du använder REPAIR_ALLOW_DATA_LOSS
alternativ med valfri DBCC CHECK
kommando för att reparera databaskorruption, beaktas inte begränsningar.
Följaktligen är det möjligt att ha ogiltiga data i databasen – antingen data som inte följer en begränsning eller data som inte längre upprätthåller den förväntade primär-främmande nyckelrelationen. SQL Server inkluderar DBCC CHECKCONSTRAINTS
uttalande för att hitta data som bryter mot begränsningar. När något reparationsalternativ har körts, kör DBCC CHECKCONSTRAINTS
för hela databasen för att säkerställa att det inte finns några problem, och det kan finnas tillfällen då det är lämpligt att köra CHECKCONSTRAINTS
för en vald begränsning eller en tabell. Att upprätthålla dataintegritet är avgörande, och även om det inte är vanligt att köra DBCC CHECKCONSTRAINTS
på en schemalagd basis för att hitta ogiltiga data, när du behöver köra den är det en bra idé att förstå vilken resultateffekt det kan skapa.
DBCC CHECKCONSTRAINTS
kan köras för en enda begränsning, en tabell eller hela databasen. Liksom andra kontrollkommandon kan det ta avsevärd tid att slutföra och kommer att förbruka systemresurser, särskilt för större databaser. Till skillnad från andra kontrollkommandon, CHECKCONSTRAINTS
använder inte en ögonblicksbild av databasen.
Med Extended Events kan vi undersöka resursanvändning när vi kör DBCC CHECKCONSTRAINTS
för bordet. För att bättre visa effekten körde jag skriptet Create Enlarged AdventureWorks Tables.sql från Jonathan Kehayias (blogg | @SQLPoolBoy) för att skapa större tabeller. Jonathans skript skapar bara indexen för tabellerna, så påståendena nedan är nödvändiga för att lägga till några utvalda begränsningar:
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
Vi kan verifiera vilka begränsningar som finns med sp_helpconstraint
:
EXEC sp_helpconstraint '[Sales].[SalesOrderDetailEnlarged]'; GO
sp_helpconstraint output
När begränsningarna väl finns kan vi jämföra resursanvändningen för DBCC CHECKCONSTRAINTS
för en enda begränsning, en tabell och hela databasen med hjälp av utökade händelser. Först skapar vi en session som helt enkelt fångar sp_statement_completed
händelser, inkluderar sql_text
action och skickar utdata till 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
Därefter startar vi sessionen och kör var och en av DBCC CHECKCONSTRAINT
kommandon, mata sedan ut ringbufferten till en temporär tabell för att manipulera. Observera att DBCC DROPCLEANBUFFERS
körs före varje kontroll så att var och en startar från kall cache, med ett nivåtestfält.
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
Parsar ring_buffer
in i en temporär tabell kan det ta lite extra tid (cirka 20 sekunder på min maskin), men upprepad sökning av data är snabbare från en temporär tabell än via ring_buffer
. Om vi tittar på utdata ser vi att det finns flera programsatser som körs för varje DBCC CHECKCONSTRAINTS
:
SELECT * FROM #EventData WHERE [sql_text] LIKE 'DBCC%';
Utgång för utökade händelser
Använda utökade händelser för att gräva in i CHECKCONSTRAINTS
s inre funktioner är en intressant uppgift, men det vi verkligen är intresserade av här är resursförbrukning – speciellt I/O. Vi kan aggregera physical_reads
för varje kontrollkommando för att jämföra I/O:
SELECT [sql_text], SUM([physical_reads]) AS [Total Reads] FROM #EventData WHERE [sql_text] LIKE 'DBCC%' GROUP BY [sql_text];
Aggregerad I/O för kontroller
För att kontrollera en begränsning måste SQL Server läsa igenom data för att hitta några rader som kan bryta mot begränsningen. Definitionen av CK_SalesOrderDetailEnlarged_OrderQty
begränsning är [OrderQty] > 0
. Den främmande nyckelbegränsningen, FK_SalesOrderDetailEnlarged_SalesOrderHeaderEnlarged_SalesOrderID
, upprättar en relation på SalesOrderID
mellan [Sales].[SalesOrderHeaderEnlarged]
och [Sales].[SalesOrderDetailEnlarged]
tabeller. Intuitivt kan det tyckas som om kontroll av den främmande nyckeln skulle kräva mer I/O, eftersom SQL Server måste läsa data från två tabeller. Men [SalesOrderID]
finns i bladnivån för IX_SalesOrderHeaderEnlarged_SalesPersonID
icke-klustrade index på [Sales].[SalesOrderHeaderEnlarged]
tabellen och i IX_SalesOrderDetailEnlarged_ProductID
index på [Sales].[SalesOrderDetailEnlarged]
tabell. Som sådan skannar SQL Server dessa två index för att jämföra [SalesOrderID]
värden mellan de två tabellerna. Detta kräver drygt 19 000 läsningar. I fallet med CK_SalesOrderDetailEnlarged_OrderQty
begränsning, [OrderQty]
kolumnen ingår inte i något index, så en fullständig genomsökning av det klustrade indexet sker, vilket kräver över 72 000 läsningar.
När alla begränsningar för en tabell är kontrollerade är I/O-kraven högre än om en enskild begränsning kontrolleras, och de ökar igen när hela databasen kontrolleras. I exemplet ovan visas [Sales].[SalesOrderHeaderEnlarged]
och [Sales].[SalesOrderDetailEnlarged]
tabeller är oproportionerligt större än andra tabeller i databasen. Detta är inte ovanligt i verkliga scenarier; mycket ofta har databaser flera stora tabeller som utgör en stor del av databasen. När du kör CHECKCONSTRAINTS
för dessa tabeller, var medveten om den potentiella resursförbrukning som krävs för kontrollen. Kör kontroller under öppettider när det är möjligt för att minimera användarens påverkan. I händelse av att kontroller måste köras under normal kontorstid, kan förståelse av vilka begränsningar som finns och vilka index som finns för att stödja validering, hjälpa till att mäta effekten av kontrollen. Du kan utföra kontroller i en test- eller utvecklingsmiljö först för att förstå prestandapåverkan, men variationer kan sedan finnas baserat på hårdvara, jämförbara data, etc. Och slutligen, kom ihåg att varje gång du kör ett kontrollkommando som innehåller REPAIR_ALLOW_DATA_LOSS
alternativ, följ reparationen med DBCC CHECKCONSTRAINTS
. Databasreparation tar inte hänsyn till några begränsningar eftersom korruption är åtgärdad, så förutom att potentiellt förlora data kan du sluta med data som bryter mot en eller flera begränsningar i din databas.