Налаштування IDENTITY_INSERT ON
самостійно не усуває одночасність - це не ставить на столі жодних ексклюзивних замків, лише короткий замок стабільності схеми (Sch-S).
Отже, що теоретично може статися за поведінки за замовчуванням, це ви могли зробити це на сеансі 1:
BEGIN TRANSACTION;
-- 1
SET IDENTITY_INSERT dbo.tablename ON;
-- 2
INSERT dbo.tablename(id, etc) VALUES(100, 'foo'); -- next identity is now 101
-- 3
INSERT dbo.tablename(id, etc) VALUES(101, 'foo'); -- next identity is now 102
-- 4
SET IDENTITY_INSERT dbo.tablename OFF;
COMMIT TRANSACTION;
В іншому сеансі ви можете вставити рядки в таблицю в точках 1, 2, 3 або 4. Це може здатися гарною справою, за винятком того, що трапляється з будь-якою вставкою, яка відбувається між 2 і 3, це те, що спрацьовує автоматично створене значення іншим сеансом базується на результатах оператора 2 - таким чином, він генерує 101, і тоді оператор 3 завершиться невдачею з порушенням первинного ключа. Це досить просто, щоб налаштувати і протестувати себе за допомогою деяких WAITFOR
s:
-- session 1
-- DROP TABLE dbo.what;
CREATE TABLE dbo.what(id INT IDENTITY PRIMARY KEY);
GO
BEGIN TRANSACTION;
SET IDENTITY_INSERT dbo.what ON;
INSERT dbo.what(id) VALUES(32);
WAITFOR DELAY '00:00:05';
INSERT dbo.what(id) VALUES(33);
WAITFOR DELAY '00:00:05';
INSERT dbo.what(id) VALUES(34);
WAITFOR DELAY '00:00:05';
INSERT dbo.what(id) VALUES(35);
WAITFOR DELAY '00:00:05';
INSERT dbo.what(id) VALUES(36);
SET IDENTITY_INSERT dbo.what OFF;
COMMIT TRANSACTION;
Після запуску цієї партії запустіть цю партію в іншому вікні:
-- session 2
INSERT dbo.what DEFAULT VALUES;
WAITFOR DELAY '00:00:01';
GO 20
Сесія 2 повинна коли-небудь вставляти значення від 1 до 20, правда? За винятком того, що основна ідентифікація була оновлена вашими ручними вставками сеансу 1, у якийсь момент сеанс 2 підбере місце, де сеанс 1 припинено, і вставить 32, або 33, або 34 тощо. Це буде дозволено, тоді сеанс 1 завершиться невдачею на наступній вставці з порушенням ПК (яке виграш може бути лише питанням часу).
Один із способів вирішити це - викликати TABLOCK
на першій вставці:
INSERT dbo.what WITH (TABLOCK) (id) VALUES(32);
Це заблокує будь-яких інших користувачів, які намагаються вставити (або зробити що-небудь справді) в цю таблицю, поки не закінчите переміщення цих заархівованих рядків назад. Це стримує паралельність, звичайно, але саме так ви хочете, щоб блокування працювало. І сподіваємось, це не те, що відбувається з такою частою швидкістю, коли ти весь час блокуєш інших людей.
Кілька інших способів вирішення:
- перестаньте дбати про
IDENTITY
створене значення. Кого хвилює? Використовуйте UNIQUEIDENTIFIER
(можливо, згенеровану в окремій таблиці із IDENTITY
сурогатом), якщо вихідне значення є дуже важливим.
- змінити процес архівації на "м'яке видалення", коли щось позначено як архівоване спочатку, а архів не буде постійним до деякої пізнішої дати. Тоді будь-який процес намагається повернути їх назад, можна просто здійснити пряме оновлення та виправити м'який прапор видалення.