SQL Server allows you to temporarily disable your CHECK and FOREIGN KEY constraints. I would not recommend this as part of your regular production schedule, but there are certainly cases where this comes in handy.
One example is if you’re copying thousands of rows from another table, and you already know with 100% certainty that the data doesn’t violate any constraint. The overhead of checking could impact performance of your process; in the worst case making the execution time exceed your maintenance window. Disabling the constraints can result in a tremendous reduction of execution time.
Another example would be the case where you’re importing lots of data from a third party; the raw data might violate some constraints, but these errors are all corrected as part of the process before the constraints are re-enabled. I would personally prefer to import the raw data to a staging table, do the cleansing there and only then copy the data to the production table – which of course takes us back to the first example J.
However, there is a large caveat that many people seem to be unaware of – if you don’t take care while re-enabling the constraints, you might end up disabling the query optimizer’s potential to generate the best possible execution plan for your queries!
Let’s first look at the abridged syntax for disabling end re-enabling a constraint:
ALTER TABLE <tablename>
NOCHECK CONSTRAINT <constraintname>;
ALTER TABLE <tablename>
WITH { CHECK | NOCHECK }
CHECK CONSTRAINT <constraintname>;
The first syntax disables a constraint and the second syntax re-enables it. Note the “WITH {CHECK | NOCHECK}” clause. Specifying WITH CHECK signifies to SQL Server that you want it to check the re-enabled constraint for all rows in the table; if one or more fail to satisfy the constraint, the ALTER TABLE command fails. Specifying WITH NOCHECK (the default for existing constraints) means that existing rows are not checked. This is of course faster, but it has a severe side effect that you should really be aware of: you may know with 100% certainty that all rows in the table still abide by the constraint, but SQL Server doesn’t know this. As soon as you enable a constraint without checking the existing rows, SQL Server will mark the constraint as “not trusted”. This means that the query optimizer will no longer use it’s knowledge of the constraint to optimize your queries.
To see the effects of this, I set up a simple test with one table, three columns, and a CHECK constraint:
CREATE TABLE dbo.Test
(KeyColumn int NOT NULL,
CheckColumn int NOT NULL,
LongColumn char(4000) NOT NULL,
CONSTRAINT PK_Test PRIMARY KEY (KeyColumn),
CONSTRAINT CK_Test CHECK (CheckColumn > 0)
);
INSERT INTO dbo.Test (KeyColumn, CheckColumn, LongColumn)
SELECT 1, 1, REPLICATE('a', 4000)
UNION ALL
SELECT 2, 2, REPLICATE('b', 4000)
UNION ALL
SELECT 3, 3, REPLICATE('c', 4000)
UNION ALL
SELECT 4, 4, REPLICATE('d', 4000)
UNION ALL
SELECT 5, 5, REPLICATE('e', 4000)
UNION ALL
SELECT 6, 6, REPLICATE('f', 4000)
UNION ALL
SELECT 7, 7, REPLICATE('g', 4000);
go
If I now execute a query that, because of the CHECK constraint can’t possibly return any rows, I’ll get an execution plan that doesn’t even touch the table:
SELECT KeyColumn, CheckColumn
FROM dbo.Test
WHERE CheckColumn < 0
AND LEFT(LongColumn, 5) = RIGHT(LongColumn, 5);
But let’s now examine what happens if I, for whatever reason, disable and later re-enable the constraint:
ALTER TABLE dbo.Test
NOCHECK CONSTRAINT CK_Test;
-- Imagine something actuallly happening here
ALTER TABLE dbo.Test
CHECK CONSTRAINT CK_Test;
Because I didn’t specify the “WITH [CHECK | NOCHECK]” clause when re-enabling the constraint, the command defaults to not checking the existing population. As a result, SQL Server now feels that it can no longer trust this constraint, as can be seen in the catalog view sys.check_constraints:
SELECT LEFT(name, 20) AS name, is_not_trusted
FROM sys.check_constraints;
name is_not_trusted
-------------------- --------------
CK_Test 1
Since SQL Server no longer trusts this constraint, running the query that searches for negative values in CheckColumn will now cause an execution plan to be created that scans the entire table:
SET STATISTICS IO ON;
SELECT KeyColumn, CheckColumn
FROM dbo.Test
WHERE CheckColumn < 0
AND LEFT(LongColumn, 5) = RIGHT(LongColumn, 5);
KeyColumn CheckColumn
----------- -----------
Table 'Test'. Scan count 1, logical reads 6, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
To prevent this from happening, always make sure to use WITH CHECK when you re-enable a constraint:
ALTER TABLE dbo.Test
WITH CHECK CHECK CONSTRAINT CK_Test;
It looks a bit silly at first, the CHECK from the WITH CHECK option directly followed by the CHECK command to re-enable the constraint – but hey, I didn’t create the syntax, you know! You’ll get used to it, eventually.
Anyway, this results in the constraint being trusted again. The example query above will now, once more, return an empty result set without ever actually reading as much as a single page of the table’s data.
SELECT LEFT(name, 20) AS name, is_not_trusted
FROM sys.check_constraints;
name is_not_trusted
-------------------- --------------
CK_Test 0
Now whereas this example might seem a bit contrived, it should be noted that this also applies to FOREIGN KEY constraints. I chose to use a CHECK constraint here to keep the sample code relatively short, but using a trusted FOREIGN KEY constraint, the optimizer can actually completely remove joined tables from a query! That is of course much more a real-world scenario than searching for a value that’s not permitted – and one that can have an even bigger impact on performance!!
So the bottom line is to always make sure that you include the WITH CHECK option when re-enabling a constraint. Or, to conclude with an awful pun, always double-check that there’s a double CHECK in the command.