Почему удаление внешних ключей занимает много времени?

13

Я создал скрипт, который по одному удаляет все внешние ключи из базы данных, вот так:

ALTER TABLE MyTable1 DROP CONSTRAINT FK_MyTable1_col1
ALTER TABLE MyTable2 DROP CONSTRAINT FK_MyTable2_col1
ALTER TABLE MyTable2 DROP CONSTRAINT FK_MyTable2_col2

Что меня удивляет, так это то, что сценарий занимает много времени: в среднем 20 секунд для каждого DROP FK. Теперь я понимаю, что создание FK может иметь большое значение, потому что сервер должен пойти и проверить, что ограничение FK не нарушается с самого начала, а отбрасывается? Что делает сервер при отбрасывании FK, что занимает так много времени? Это как для моего собственного любопытства, так и для понимания, есть ли способ сделать вещи быстрее. Возможность удалить FK (а не просто отключить их) позволила бы мне быть намного быстрее во время миграции и, следовательно, минимизировать время простоя.

carlo.borreo
источник
1
Может быть, другой процесс устанавливает блокировки общей схемы в вашей базе данных, заставляя процесс удаления FK ожидать завершения этих процессов? Попробуйте запустить сброс FK, а затем немедленно проверить sp_who2 на предмет блокировки.
Даниэль
Я забыл упомянуть, что в этой базе данных нет других процессов. Но есть и на других базах данных на этом же сервере.
carlo.borreo

Ответы:

12

Для удаления ограничения требуется блокировка Sch-M (изменение схемы), которая будет блокировать других для запроса таблицы во время модификации. Вы, вероятно, ожидаете, чтобы получить эту блокировку, и должны подождать, пока все текущие запросы к этой таблице не будут завершены.
Запущенный запрос имеет блокировку Sch-S (стабильность схемы) для таблицы, и эта блокировка несовместима с блокировкой Sch-M.

Из режимов блокировки, блокировки схемы

Компонент Database Engine использует блокировки модификации схемы (Sch-M) во время операции языка определения данных таблицы (DDL), такой как добавление столбца или удаление таблицы. Во время удержания блокировка Sch-M предотвращает одновременный доступ к столу. Это означает, что блокировка Sch-M блокирует все внешние операции, пока блокировка не будет снята.

Некоторые операции на языке манипулирования данными (DML), такие как усечение таблиц, используют блокировки Sch-M для предотвращения доступа к затронутым таблицам параллельными операциями.

Компонент Database Engine использует блокировки стабильности схемы (Sch-S) при компиляции и выполнении запросов. Блокировки Sch-S не блокируют никакие транзакционные блокировки, включая эксклюзивные (X) блокировки. Поэтому другие транзакции, включая транзакции с X-блокировками для таблицы, продолжают выполняться во время компиляции запроса. Однако одновременные операции DDL и параллельные операции DML, которые получают блокировки Sch-M, не могут быть выполнены для таблицы.

Микаэль Эрикссон
источник
Иногда даже выделение таблицы в SSMS создает Sch-Sблокировку, и я подозреваю, что это является основной причиной проблем OP.
Джон Эйсбренер,
5

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

CREATE DATABASE [TestFK]
GO

Создание 2 таблиц.

 USE [TestFK]
 GO
CREATE TABLE dbo.[Address] (
      ADDRESSID   INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
       Address1    VARCHAR(50),
      City        VARCHAR(50),
      [State]     VARCHAR(10),
      ZIP     VARCHAR(10));
GO

CREATE TABLE dbo.Person (
       PersonID    INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
       LastName    VARCHAR(50) NOT NULL,
     FirstName   VARCHAR(50),
      AddressID   INT);
GO

Создание ограничения внешнего ключа для таблицы Person.

 USE [TestFK]
 GO
ALTER TABLE dbo.Person ADD CONSTRAINT FK_Person_AddressID FOREIGN KEY (AddressID)
REFERENCES dbo.Address(AddressID)
GO

Вставьте некоторые данные в обе таблицы.

USE [TestFK]
GO
INSERT dbo.Address (Address1,City,[State],Zip)
  SELECT '123 Easy St','Austin','TX','78701'
    UNION
 SELECT '456 Lakeview','Sunrise Beach','TX','78643'
GO
INSERT dbo.Person (LastName,FirstName,AddressID)
    SELECT 'Smith','John',1
   UNION
 SELECT 'Smith','Mary',1
   UNION
 SELECT 'Jones','Max',2
GO

Откройте новое окно запроса и запустите его (не закрывайте окно после завершения запроса).

   USE [TestFK]
   GO
   BEGIN TRAN
   INSERT dbo.Person (LastName,FirstName,AddressID)
    SELECT 'Smith1','John1',1
    UNION
    SELECT 'Smith1','Mary1',1
    UNION
    SELECT 'Jones1','Max1',2

Откройте другое окно запроса и запустите это.

USE [TestFK]
GO
ALTER TABLE dbo.person DROP CONSTRAINT FK_Person_AddressID

Вы увидите, что отбрасываемое ограничение будет продолжать работать (ждать), и теперь запустите запрос, чтобы увидеть, почему он работает дольше и каких блокировок он ожидает.

SELECT * FROM sys.dm_os_waiting_tasks 
WHERE blocking_session_id IS NOT NULL; 

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

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

SqlWorldWide
источник
Никто другой не использовал базу данных, но, с другой стороны, я не могу исключить, что у меня было открытое окно в этой базе данных. Я сделаю еще один эксперимент.
carlo.borreo
1
Когда ваш оператор отбрасывания ожидает завершения, запустите этот запрос из другого окна. Это даст вам то, что вы ждете. Получить запрос отсюда . В нем больше деталей, чем в моем примере.
SqlWorldWide