Проблеми не виникло б, якби змінна таблиці містила лише одне значення. З декількома рядками з'являється нова можливість для тупикової ситуації. Припустимо, два одночасні процеси (A & B) виконуються із табличними змінними, що містять (1, 2) та (2, 1) для однієї компанії.
Процес A зчитує призначення, не знаходить рядка та вставляє значення '1'. Він містить ексклюзивне блокування рядків зі значенням "1". Процес B зчитує призначення, не знаходить рядка та вставляє значення '2'. Він містить ексклюзивне блокування рядків зі значенням "2".
Тепер процес A повинен обробити рядок 2, а процес B повинен обробити рядок 1. Жоден процес не може досягти прогресу, оскільки він вимагає блокування, несумісного з ексклюзивним блокуванням, що утримується іншим процесом.
Щоб уникнути тупикових ситуацій з декількома рядами, рядки потрібно обробляти (а також доступ до таблиць) щоразу в одному порядку . Таблична змінна в плані виконання, показаному у запитанні, - це купа, тому рядки не мають внутрішнього порядку (вони, ймовірно, читаються в порядку вставки, хоча це не гарантується):
Відсутність послідовного порядку обробки рядків призводить безпосередньо до тупикової можливості. Другий погляд - відсутність ключової гарантії унікальності означає, що столовий котушок необхідний для забезпечення правильного захисту на Хеллоуїн. Котушка - це нетерпляча котушка, тобто всі рядки записуються в робочий стіл tempdb перед тим, як прочитати їх назад і перезапустити для оператора Insert.
Перевизначення TYPE
змінної таблиці, щоб включити кластер PRIMARY KEY
:
DROP TYPE dbo.CoUserData;
CREATE TYPE dbo.CoUserData
AS TABLE
(
MyKey integer NOT NULL PRIMARY KEY CLUSTERED,
MyValue integer NOT NULL
);
План виконання тепер показує сканування кластерного індексу, і гарантія унікальності означає, що оптимізатор може безпечно видалити котушку таблиці:
У тестах з 5000 ітераціями MERGE
оператора на 128 потоках жодних затримок із змінною кластерної таблиці не сталося. Я мушу наголосити, що це лише на основі спостереження; змінна кластерна таблиця також може ( технічно ) створювати свої рядки в різних порядках, але шанси на послідовне замовлення дуже сильно підвищуються. Спостережувану поведінку потрібно, звичайно, перевірити для кожного нового накопичувального оновлення, пакета обслуговування чи нової версії SQL Server.
Якщо визначення змінної таблиці неможливо змінити, існує інша альтернатива:
MERGE dbo.CompanyUser AS R
USING
(SELECT DISTINCT MyKey, MyValue FROM @DataTable) AS NewData ON
R.CompanyId = @CompanyID
AND R.UserID = @UserID
AND R.MyKey = NewData.MyKey
WHEN NOT MATCHED THEN
INSERT
(CompanyID, UserID, MyKey, MyValue)
VALUES
(@CompanyID, @UserID, NewData.MyKey, NewData.MyValue)
OPTION (ORDER GROUP);
Це також дозволяє домогтися усунення котушки (і послідовності послідовності порядку) ціною введення явного сортування:
Цей план також не створив тупиків, використовуючи той же тест. Сценарій відтворення нижче:
CREATE TYPE dbo.CoUserData
AS TABLE
(
MyKey integer NOT NULL /* PRIMARY KEY */,
MyValue integer NOT NULL
);
GO
CREATE TABLE dbo.Company
(
CompanyID integer NOT NULL
CONSTRAINT PK_Company
PRIMARY KEY (CompanyID)
);
GO
CREATE TABLE dbo.CompanyUser
(
CompanyID integer NOT NULL,
UserID integer NOT NULL,
MyKey integer NOT NULL,
MyValue integer NOT NULL
CONSTRAINT PK_CompanyUser
PRIMARY KEY CLUSTERED
(CompanyID, UserID, MyKey),
FOREIGN KEY (CompanyID)
REFERENCES dbo.Company (CompanyID),
);
GO
CREATE NONCLUSTERED INDEX nc1
ON dbo.CompanyUser (CompanyID, UserID);
GO
INSERT dbo.Company (CompanyID) VALUES (1);
GO
DECLARE
@DataTable AS dbo.CoUserData,
@CompanyID integer = 1,
@UserID integer = 1;
INSERT @DataTable
SELECT TOP (10)
V.MyKey,
V.MyValue
FROM
(
VALUES
(1, 1),
(2, 2),
(3, 3),
(4, 4),
(5, 5),
(6, 6),
(7, 7),
(8, 8),
(9, 9)
) AS V (MyKey, MyValue)
ORDER BY NEWID();
BEGIN TRANSACTION;
-- Test MERGE statement here
ROLLBACK TRANSACTION;