Что теряется, когда я создаю внешний ключ, используя `WITH NOCHECK`?

11

Я знаю, что если я EXISTS()вызову значение поиска FK, то, если это ограничение FK является доверенным, результат будет немедленным.

И если ему не доверяют (например, когда я создаю FK с использованием WITH NOCHECK), то SQL Server должен пойти и проверить таблицу, чтобы увидеть, действительно ли значение там.

Есть ли что-то еще, что я теряю при использовании NOCHECK?

Vaccano
источник

Ответы:

13

Как вы обнаружили на своем existsпримере, SQL Server может использовать тот факт, что внешний ключ является доверенным при построении плана запроса.

Есть ли что-то еще, что я теряю при использовании NOCHECK?

Помимо того факта, что вы можете добавлять значения в столбец, которых там не должно быть, как ответил Ste Bov, у вас будет больше сценариев, в которых план запроса будет лучше, когда внешний ключ является доверенным.

Вот один пример с индексированным представлением .

У вас есть две таблицы с доверенным ограничением FK.

create table dbo.Country
(
  CountryID int primary key,
  Name varchar(50) not null
);

create table dbo.City
(
  CityID int identity primary key,
  Name varchar(50),
  IsBig bit not null,
  CountryID int not null
);

alter table dbo.City 
  add constraint FK_CountryID 
  foreign key (CountryID) 
  references dbo.Country(CountryID);

Существует не так много стран, но есть миллионы городов, и некоторые из них являются большими городами.

Пример данных:

-- Three countries
insert into dbo.Country(CountryID, Name) values
(1, 'Sweden'),
(2, 'Norway'),
(3, 'Denmark');

-- Five big cities
insert into dbo.City(Name, IsBig, CountryID) values
('Stockholm', 1, 1),
('Gothenburg', 1, 1),
('Malmoe', 1, 1),
('Oslo', 1, 2),
('Copenhagen', 1, 3);

-- 300 small cities
insert into dbo.City(Name, IsBig, CountryID)
select 'NoName', 0, Country.CountryID
from dbo.Country
  cross apply (
              select top(100) *
              from sys.columns
              ) as T;

Наиболее часто выполняемые запросы в этом приложении связаны с определением количества крупных городов в каждой стране. Чтобы ускорить процесс, мы добавляем индексированное представление.

create view dbo.BigCityCount with schemabinding
as
select count_big(*) as BigCityCount,
       City.CountryID,
       Country.Name as CountryName
from dbo.City
  inner join dbo.Country
    on City.CountryID = Country.CountryID
where City.IsBig = 1 
group by City.CountryID,
         Country.Name;

 go

create unique clustered index CX_BigCityCount
  on dbo.BigCityCount(CountryID);

Через некоторое время появляется требование добавить новую страну

insert into dbo.Country(CountryID, Name) values(4, 'Finland');

План запроса для этой вставки не имеет сюрпризов.

введите описание изображения здесь

Вставка кластерного индекса в Countryтаблицу.

Теперь, если ваш внешний ключ не был доверенным

alter table dbo.City nocheck constraint FK_CountryID;

и вы добавляете новую страну

insert into dbo.Country(CountryID, Name) values(5, 'Iceland');

Вы бы в конечном итоге с этой не очень красивой картиной.

введите описание изображения здесь

Нижняя ветвь предназначена для обновления индексированного представления. Он выполняет полное сканирование таблицы, Cityчтобы выяснить, есть ли в стране CountryID = 5строки с таблицами City.

Когда ключ является доверенным, SQL Server знает, что в нем не может быть строк City, соответствующих новой строке в Country.

Микаэль Эрикссон
источник
4

Вы теряете оптимизации запросов. На практике единственная оптимизация, которую я помню, это устранение избыточных соединений. Например, если у вас есть вид:

select *
from Orders o
join Customers c on o.CustomerID = c.ID

И при использовании представления вы не используете столбцы, cтогда это объединение может быть удалено, если есть правильная настройка FK.

Ваш EXISTSпример - особый случай удаления избыточного соединения. Я не думаю, что этот конкретный пример практически актуален.

Вы также теряете строгую целостность данных, которую обеспечивает доверенное ограничение.

USR
источник
3

Опция NOCHECK делает именно то, что написано на банке.

Он в основном используется для добавления внешнего ключа на полпути через существование таблицы, где есть новые отношения, которые, возможно, не требовались (по крайней мере, это мое понимание).

Это означает, что столбец с внешним ключом МОЖЕТ иметь в нем значения, которые не соответствуют назначенному значению, к которому он должен относиться.

Это означает, что когда у вас есть опция NOCHECK на SQL Server, вам нужно пойти и проверить, является ли это значение ключа первичным ключом. Если NOCHECK не задан, то SQL Server предполагает, что все, что находится в этом столбце, определенно существует, поскольку запись не может существовать в таблице, если она еще не была первичным ключом, и вы не можете удалить первичный ключ, не удалив строку в вопрос.

Просто NOCHECK - это внешний ключ, которому вы не можете доверять, чтобы иметь отношение к чему-либо.

Вы на самом деле не теряете ничего, кроме доверия к тому, что первичный ключ гарантированно будет там.

Ste Bov
источник
Из вашего ответа не ясно, есть ли какое-либо применение внешнего ключа после первоначального создания ограничения. Не могли бы вы уточнить, что произойдет, если вы попытаетесь INSERTсоздать новую строку, которая относится к несуществующей родительской строке, или если вы DELETEпозже попытаетесь использовать строку, которая имеет дочерние строки?
jpmc26