Често срещан елемент, използван при проектирането на база данни, е ограничението. Ограниченията се предлагат в различни варианти (например по подразбиране, уникални) и налагат целостта на колоната(ите), в която съществуват. Когато се прилагат добре, ограниченията са мощен компонент в дизайна на база данни, тъй като пречат на данни, които не отговарят на зададените критерии, да попаднат в база данни. Въпреки това, ограниченията могат да бъдат нарушени с помощта на команди като WITH NOCHECK
и IGNORE_CONSTRAINTS
. Освен това, когато използвате REPAIR_ALLOW_DATA_LOSS
опция с произволен DBCC CHECK
команда за поправяне на повреда в базата данни, ограниченията не се вземат предвид.
Следователно е възможно да има невалидни данни в базата данни - или данни, които не се придържат към ограничение, или данни, които вече не поддържат очакваната връзка между първичен и външен ключ. SQL Server включва DBCC CHECKCONSTRAINTS
изявление за намиране на данни, които нарушават ограниченията. След като се изпълни която и да е опция за ремонт, изпълнете DBCC CHECKCONSTRAINTS
за цялата база данни, за да се гарантира, че няма проблеми и може да има моменти, когато е подходящо да се изпълни CHECKCONSTRAINTS
за ограничение за избор или таблица. Поддържането на целостта на данните е от решаващо значение и макар че не е типично да се изпълняват DBCC CHECKCONSTRAINTS
на планирана база, за да намерите невалидни данни, когато трябва да ги стартирате, добре е да разберете въздействието върху производителността, което може да създаде.
DBCC CHECKCONSTRAINTS
може да се изпълни за едно ограничение, таблица или цялата база данни. Подобно на други команди за проверка, това може да отнеме значително време за завършване и ще изразходва системни ресурси, особено за по-големи бази данни. За разлика от други команди за проверка, CHECKCONSTRAINTS
не използва моментна снимка на базата данни.
С разширени събития можем да изследваме използването на ресурси, когато изпълняваме DBCC CHECKCONSTRAINTS
за масата. За да покажа по-добре въздействието, стартирах скрипта Create Enlarged AdventureWorks Tables.sql от Jonathan Kehayias (блог | @SQLPoolBoy), за да създам по-големи таблици. Скриптът на Джонатан създава само индексите за таблиците, така че изявленията по-долу са необходими за добавяне на няколко избрани ограничения:
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
Можем да проверим какви ограничения съществуват с помощта на sp_helpconstraint
:
EXEC sp_helpconstraint '[Sales].[SalesOrderDetailEnlarged]'; GO
sp_helpconstraint изход
След като ограниченията съществуват, можем да сравним използването на ресурсите за DBCC CHECKCONSTRAINTS
за едно ограничение, таблица и цялата база данни с помощта на разширени събития. Първо ще създадем сесия, която просто улавя sp_statement_completed
събития, включва sql_text
действие и изпраща изхода към 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
След това ще започнем сесията и ще стартираме всеки от DBCC CHECKCONSTRAINT
команди, след което изведете буфера на пръстена във временна таблица за манипулиране. Обърнете внимание, че DBCC DROPCLEANBUFFERS
изпълнява преди всяка проверка, така че всяка да започва от студен кеш, като запазва поле за тестване на ниво.
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
Разбор на ring_buffer
във временна таблица може да отнеме известно време (около 20 секунди на моята машина), но многократното запитване на данните е по-бързо от временна таблица, отколкото чрез ring_buffer
. Ако погледнем изхода, ще видим, че има няколко оператора, изпълнени за всеки DBCC CHECKCONSTRAINTS
:
SELECT * FROM #EventData WHERE [sql_text] LIKE 'DBCC%';
Изход за разширени събития
Използване на разширени събития за копаене във вътрешната работа на CHECKCONSTRAINTS
е интересна задача, но това, което наистина ни интересува, е потреблението на ресурси – по-специално I/O. Можем да обобщим physical_reads
за всяка команда за проверка за сравняване на I/O:
SELECT [sql_text], SUM([physical_reads]) AS [Total Reads] FROM #EventData WHERE [sql_text] LIKE 'DBCC%' GROUP BY [sql_text];
Агрегиран I/O за проверки
За да провери ограничение, SQL Server трябва да прочете данните, за да намери редове, които могат да нарушат ограничението. Дефиницията на CK_SalesOrderDetailEnlarged_OrderQty
ограничението е [OrderQty] > 0
. Ограничението на външния ключ, FK_SalesOrderDetailEnlarged_SalesOrderHeaderEnlarged_SalesOrderID
, установява връзка на SalesOrderID
между [Sales].[SalesOrderHeaderEnlarged]
и [Sales].[SalesOrderDetailEnlarged]
маси. Интуитивно може да изглежда, че проверката на ограничението на външния ключ ще изисква повече I/O, тъй като SQL Server трябва да чете данни от две таблици. Въпреки това, [SalesOrderID]
съществува в крайното ниво на IX_SalesOrderHeaderEnlarged_SalesPersonID
неклъстериран индекс в [Sales].[SalesOrderHeaderEnlarged]
таблица и в IX_SalesOrderDetailEnlarged_ProductID
индекс на [Sales].[SalesOrderDetailEnlarged]
маса. Като такъв, SQL Server сканира тези два индекса, за да сравни [SalesOrderID]
стойности между двете таблици. Това изисква малко над 19 000 четения. В случай на CK_SalesOrderDetailEnlarged_OrderQty
ограничение, [OrderQty]
колоната не е включена в нито един индекс, така че се извършва пълно сканиране на клъстерирания индекс, което изисква над 72 000 четения.
Когато всички ограничения за дадена таблица са проверени, изискванията за I/O са по-високи, отколкото ако е проверено едно ограничение, и те се увеличават отново, когато се провери цялата база данни. В примера по-горе, [Sales].[SalesOrderHeaderEnlarged]
и [Sales].[SalesOrderDetailEnlarged]
таблиците са непропорционално по-големи от другите таблици в базата данни. Това не е необичайно в реалните сценарии; много често базите данни имат няколко големи таблици, които съставляват голяма част от базата данни. Когато изпълнявате CHECKCONSTRAINTS
за тези таблици, имайте предвид потенциалната консумация на ресурси, необходима за проверката. Извършвайте проверки в неработно време, когато е възможно, за да сведете до минимум въздействието на потребителите. В случай, че проверките трябва да се изпълняват през нормалното работно време, разбирането какви ограничения съществуват и какви индекси съществуват, за да поддържат валидирането, може да помогне да се прецени ефектът от проверката. Можете първо да изпълните проверки в среда за тестване или разработка, за да разберете въздействието върху производителността, но след това може да съществуват вариации въз основа на хардуер, сравними данни и т.н. И накрая, не забравяйте, че всеки път, когато изпълнявате команда за проверка, която включва REPAIR_ALLOW_DATA_LOSS
опция, следвайте поправката с DBCC CHECKCONSTRAINTS
. Поправката на база данни не взема предвид никакви ограничения, тъй като повредата е фиксирана, така че освен потенциална загуба на данни, може да се окажете с данни, които нарушават едно или повече ограничения във вашата база данни.