Якщо я правильно розумію запит, мета - видалити партії рядків, в той же час операції з DML відбуваються на рядках по всій таблиці. Мета - видалити партію; однак, якщо будь-які базові рядки, що містяться в діапазоні, визначеному зазначеною партією, заблоковані, ми повинні пропустити цю партію і перейти до наступної партії. Потім ми повинні повернутися до будь-яких партій, які раніше не були видалені, і спробувати нашу початкову логіку видалення. Ми повинні повторювати цей цикл, поки всі необхідні партії рядків не будуть видалені.
Як було зазначено, доцільно використовувати підказку READPAST та рівень ізоляції READ COMMITTED (за замовчуванням), щоб пропустити минулі діапазони, які можуть містити заблоковані рядки. Я піду на крок далі і порекомендую використовувати рівень ізоляції СЕРІАЛІЗАЦІЙНОГО та нанизування делетів.
SQL Server використовує блокування Key-Range для захисту діапазону рядків, неявно включених у набір запису, який читається оператором Transact-SQL під час використання рівня ізоляції транзакційних серійних операцій ... докладніше тут:
https://technet.microsoft.com /en-US/library/ms191272(v=SQL.105).aspx
Завдяки обробці делетів наша мета полягає в тому, щоб виділити діапазон рядків і гарантувати, що жодних змін у цих рядках не відбудеться, поки ми їх видаляємо, тобто ми не хочемо фантомних читання чи вставок. Серіалізується рівень ізоляції призначений для вирішення цієї проблеми.
Перш ніж продемонструвати своє рішення, я хотів би додати, що ні я не рекомендую переключити рівень ізоляції вашої бази даних за замовчуванням на СЕРІАЛЬНИЙ, а також не рекомендую, щоб моє рішення було найкращим. Я просто хотів би представити його і подивитися, куди ми можемо піти звідси.
Кілька домашніх записок:
- Версія SQL Server, яку я використовую, - це Microsoft SQL Server 2012 - 11.0.5343.0 (X64)
- У моїй тестовій базі даних використовується повна модель відновлення
Для початку свого експерименту я встановлю тестову базу даних, зразок таблиці, і я заповнять таблицю 2 000 000 рядків.
USE [master];
GO
SET NOCOUNT ON;
IF DATABASEPROPERTYEX (N'test', N'Version') > 0
BEGIN
ALTER DATABASE [test] SET SINGLE_USER
WITH ROLLBACK IMMEDIATE;
DROP DATABASE [test];
END
GO
-- Create the test database
CREATE DATABASE [test];
GO
-- Set the recovery model to FULL
ALTER DATABASE [test] SET RECOVERY FULL;
-- Create a FULL database backup
-- in order to ensure we are in fact using
-- the FULL recovery model
-- I pipe it to dev null for simplicity
BACKUP DATABASE [test]
TO DISK = N'nul';
GO
USE [test];
GO
-- Create our table
IF OBJECT_ID('dbo.tbl','U') IS NOT NULL
BEGIN
DROP TABLE dbo.tbl;
END;
CREATE TABLE dbo.tbl
(
c1 BIGINT IDENTITY (1,1) NOT NULL
, c2 INT NOT NULL
) ON [PRIMARY];
GO
-- Insert 2,000,000 rows
INSERT INTO dbo.tbl
SELECT TOP 2000
number
FROM
master..spt_values
ORDER BY
number
GO 1000
На цьому етапі нам знадобляться один або кілька показників, за якими можуть діяти механізми блокування рівня ізоляції СЕРІАЛІЗАЦІЙНО.
-- Add a clustered index
CREATE UNIQUE CLUSTERED INDEX CIX_tbl_c1
ON dbo.tbl (c1);
GO
-- Add a non-clustered index
CREATE NONCLUSTERED INDEX IX_tbl_c2
ON dbo.tbl (c2);
GO
Тепер перевіримо, чи було створено 2 000 000 рядків
SELECT
COUNT(*)
FROM
tbl;
Отже, у нас є наша база даних, таблиця, індекси та рядки. Отже, давайте налагодимо експеримент для обкулювання делетів. По-перше, ми повинні вирішити, як найкраще створити типовий механізм видалення кусання.
DECLARE
@BatchSize INT = 100
, @LowestValue BIGINT = 20000
, @HighestValue BIGINT = 20010
, @DeletedRowsCount BIGINT = 0
, @RowCount BIGINT = 1;
SET NOCOUNT ON;
GO
WHILE @DeletedRowsCount < ( @HighestValue - @LowestValue )
BEGIN
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION
DELETE
FROM
dbo.tbl
WHERE
c1 IN (
SELECT TOP (@BatchSize)
c1
FROM
dbo.tbl
WHERE
c1 BETWEEN @LowestValue AND @HighestValue
ORDER BY
c1
);
SET @RowCount = ROWCOUNT_BIG();
COMMIT TRANSACTION;
SET @DeletedRowsCount += @RowCount;
WAITFOR DELAY '000:00:00.025';
CHECKPOINT;
END;
Як бачите, явну транзакцію я розмістив всередині циклу while. Якщо ви хочете обмежити залишки журналу, тоді сміливо розміщуйте його поза циклом. Крім того, оскільки ми перебуваємо у моделі ПОВНОГО відновлення, ви, можливо, бажаєте частіше створювати резервні копії журналу транзакцій під час виконання операцій видалення, щоб гарантувати, що ваш журнал транзакцій може не допустити шаленого зростання.
Отже, у мене є пара цілей з цією установкою. По-перше, я хочу, щоб були мої блокування ключа; так, я намагаюся зберегти партії якомога менше. Я також не хочу негативно впливати на паралельність мого "гігантського" столу; тому я хочу взяти свої замки і залишити їх якнайшвидше. Тож рекомендую зробити розмір партії невеликим.
Тепер я хочу навести дуже короткий приклад цього режиму видалення в дії. Ми повинні відкрити нове вікно в SSMS та видалити один рядок із нашої таблиці. Я зроблю це в рамках неявної транзакції, використовуючи стандартний рівень ізоляції READ COMMITTED.
DELETE FROM
dbo.tbl
WHERE
c1 = 20005;
Чи справді цей рядок видалено?
SELECT
c1
FROM
dbo.tbl
WHERE
c1 BETWEEN 20000 AND 20010;
Так, це було видалено.
Тепер, щоб побачити наші замки, давайте відкриємо нове вікно в SSMS та додамо фрагмент коду чи два. Я використовую sp_whoisactive Адама Механіка, який можна знайти тут: sp_whoisactive
SELECT
DB_NAME(resource_database_id) AS DatabaseName
, resource_type
, request_mode
FROM
sys.dm_tran_locks
WHERE
DB_NAME(resource_database_id) = 'test'
AND resource_type = 'KEY'
ORDER BY
request_mode;
-- Our insert
sp_lock 55;
-- Our deletions
sp_lock 52;
-- Our active sessions
sp_whoisactive;
Тепер ми готові почати. У новому вікні SSMS розпочнемо явну транзакцію, яка намагатиметься знову вставити один рядок, який ми видалили. У той же час, ми відключимо нашу операцію видалення кусань.
Код вставки:
BEGIN TRANSACTION
SET IDENTITY_INSERT dbo.tbl ON;
INSERT INTO dbo.tbl
( c1 , c2 )
VALUES
( 20005 , 1 );
SET IDENTITY_INSERT dbo.tbl OFF;
--COMMIT TRANSACTION;
Давайте розпочнемо обидві операції, починаючи з вставки та слідуючи за нашими делетами. Ми можемо побачити замки ключового діапазону та ексклюзивні замки.
Вкладиш генерував ці блокування:
Уключення видалення / вибору утримує ці блокування:
Наша вставка блокує наше видалення, як очікувалося:
Тепер давайте здійснимо операцію з вставкою і подивимося, що відбувається.
І як очікувалося, всі транзакції завершені. Тепер ми повинні перевірити, чи вставка була фантомом чи операція видалення також її видалила.
SELECT
c1
FROM
dbo.tbl
WHERE
c1 BETWEEN 20000 AND 20015;
Фактично вставку було видалено; тому жодна фантомна вставка не була дозволена.
Отже, на закінчення, я думаю, що справжній намір цієї вправи полягає в тому, щоб не намагатися відслідковувати кожен окремий рядок, сторінку або блокування на рівні таблиці та намагатися визначити, чи заблокований елемент партії, і тому вимагатиме операція видалення для чекати. Можливо, це був намір запитуючих; однак це завдання є геркулесовим і в основному непрактичним, якщо не неможливим. Справжня мета полягає в тому, щоб не виникнути небажаних явищ, як тільки ми виділили діапазон нашої партії із власними замками, а потім передуємо видаленню партії. Рівень ізоляції СЕРІАЛІЗАЦІЙНО досягає цієї мети. Ключ полягає в тому, щоб тримати свої дрібниці, журнал транзакцій під контролем та усунути небажані явища.
Якщо ви хочете швидкості, то не створюйте гігантські глибокі таблиці, які не можна розділити, і тому не зможете використовувати комутацію розділів для найшвидших результатів. Ключ до швидкості - це розділення та паралелізм; запорукою страждання є гризти і жити замикаючи.
Будь ласка, дайте мені знати, що ви думаєте.
Я створив декілька подальших прикладів рівня ізоляції СЕРІАЛІЗАЦІЙНОЇ дії. Вони повинні бути доступні за посиланнями нижче.
Операція видалення
Вставка операції
Операції рівності - Блокування діапазонів ключових значень на наступних ключових значеннях
Операції з рівності - Односильний вибір існуючих даних
Операції з рівності - Односильний вибір неіснуючих даних
Операції нерівності - блокування ключових діапазонів у діапазоні та наступних ключових значеннях