Чому "Почати транзакцію" перед "Вставити запит" блокує всю таблицю?


11

Я використовую SQL Server 2005 Express.

У сценарії я додав Begin Transactionкоманду безпосередньо перед INSERTоператором у збереженій процедурі. Коли я виконав цю збережену процедуру, вона заблокувала всю таблицю, і всі паралельні з'єднання показали вивішений дисплей до моменту INSERTзакінчення цього .

Чому вся таблиця блокується і як я можу подолати цю проблему в SQL Server 2005 Express?

Відредаговано

Запит наведений нижче:

INSERT INTO <table2> SELECT * FROM <table1> WHERE table1.workCompleted = 'NO'

2
Він не блокує таблицю в postgresql.
Скотт Марлоу

Вам потрібно більше @RPK За допомогою таблиці DDL та зразка вставок ми можемо дати точне пояснення того, що відбувається. Без цього ми просто здогадуємось.
Марк Сторі-Сміт

Це питання занадто розпливчасте. Я видаляю будь-які посилання на інші СУБД і обмежую відповіді на SqlServer. Якщо ОП або будь-який інший читач хоче обговорити достоїнства цієї основної концепції на інших платформах, то нам слід було б обговорити це один раз на платформі. Згубно зробити це декартовим приєднанням, на одній сторінці буде занадто багато різних тем розмов.
jcolebrand

Відповіді:


25

Ця відповідь може виявитися корисною для початкового запитання, але насамперед стосується недостовірної інформації в інших публікаціях. Він також висвітлює розділ дурниць у BOL.

І як зазначено в документації INSERT , вона придбає ексклюзивний замок на столі. Єдиний спосіб зробити SELECT проти таблиці - це використовувати NOLOCK або встановити рівень ізоляції транзакції.

Пов'язаний розділ BOL говорить:

Оператор INSERT завжди набуває ексклюзивного (X) блокування на таблиці, яку він модифікує, і тримає цей замок до завершення транзакції. За допомогою ексклюзивного блокування (X) жодна інша транзакція не може змінювати дані; Операції зчитування можуть відбуватися лише за допомогою підказки NOLOCK або зчитування невдалого рівня ізоляції. Для отримання додаткової інформації див. Блокування в Механізмі бази даних .

Примітка: Станом на 2014-8-27 роки BOL було оновлено, щоб усунути невірно вказані вище твердження.

На щастя, це не так. Якби так, вставки в таблицю відбувалися б послідовно, і всі читачі були б заблоковані з усієї таблиці до завершення транзакції з вставкою. Це зробило б SQL Server таким же ефективним сервером баз даних, як NTFS. Не надто.

Здоровий глузд говорить про те, що це не може бути так, але як зауважує Пол Рендалл, " зробіть собі прихильність, нікому не довіряйте ". Якщо ви не можете довіряти нікому, включаючи BOL , я думаю, нам доведеться це просто довести.

Створіть базу даних і заповніть макетну таблицю з купою рядків, зазначивши, що DatabaseId повертається.

SET STATISTICS IO OFF;
SET STATISTICS TIME OFF;

USE [master]
GO

IF EXISTS (SELECT name FROM sys.databases WHERE name = N'LockDemo')
DROP DATABASE [LockDemo]
GO

DECLARE @DataFilePath NVARCHAR(4000)
SELECT 
    @DataFilePath = SUBSTRING(physical_name, 1, CHARINDEX(N'master.mdf', LOWER(physical_name)) - 1)
FROM 
    master.sys.master_files
WHERE 
    database_id = 1 AND file_id = 1

EXEC ('
CREATE DATABASE [LockDemo] ON  PRIMARY 
( NAME = N''LockDemo'', FILENAME = N''' + @DataFilePath + N'LockDemo.mdf' + ''', SIZE = 2MB , MAXSIZE = UNLIMITED, FILEGROWTH = 2MB )
 LOG ON 
( NAME = N''LockDemo_log'', FILENAME = N''' + @DataFilePath + N'LockDemo_log.ldf' + ''', SIZE = 1MB , MAXSIZE = UNLIMITED , FILEGROWTH = 1MB )
')

GO

USE [LockDemo]
GO

SELECT DB_ID() AS DatabaseId

CREATE TABLE [dbo].[MyTable]
(
    [id] [int] IDENTITY(1,1) PRIMARY KEY CLUSTERED
    , [filler] CHAR(4030) NOT NULL DEFAULT REPLICATE('A', 4030) 
)
GO

INSERT MyTable DEFAULT VALUES;
GO 100

Налаштуйте трасу профілера, яка буде відслідковувати блокування: придбане та блокування: випущені події, фільтрація на DatabaseId з попереднього сценарію, встановлення шляху до файлу та відмітка повернутої TraceId.

declare @rc int
declare @TraceID int
declare @maxfilesize BIGINT
declare @databaseid INT
DECLARE @tracefile NVARCHAR(4000)

set @maxfilesize = 5 
SET @tracefile = N'D:\Temp\LockTrace'
SET @databaseid = 9

exec @rc = sp_trace_create @TraceID output, 0, @tracefile, @maxfilesize, NULL 
if (@rc != 0) goto error

declare @on bit
set @on = 1
exec sp_trace_setevent @TraceID, 24, 32, @on
exec sp_trace_setevent @TraceID, 24, 1, @on
exec sp_trace_setevent @TraceID, 24, 57, @on
exec sp_trace_setevent @TraceID, 24, 3, @on
exec sp_trace_setevent @TraceID, 24, 51, @on
exec sp_trace_setevent @TraceID, 24, 12, @on
exec sp_trace_setevent @TraceID, 60, 32, @on
exec sp_trace_setevent @TraceID, 60, 57, @on
exec sp_trace_setevent @TraceID, 60, 3, @on
exec sp_trace_setevent @TraceID, 60, 51, @on
exec sp_trace_setevent @TraceID, 60, 12, @on
exec sp_trace_setevent @TraceID, 23, 32, @on
exec sp_trace_setevent @TraceID, 23, 1, @on
exec sp_trace_setevent @TraceID, 23, 57, @on
exec sp_trace_setevent @TraceID, 23, 3, @on
exec sp_trace_setevent @TraceID, 23, 51, @on
exec sp_trace_setevent @TraceID, 23, 12, @on

-- DatabaseId filter
exec sp_trace_setfilter @TraceID, 3, 0, 0, @databaseid

-- Set the trace status to start
exec sp_trace_setstatus @TraceID, 1

-- display trace id for future references
select TraceID=@TraceID
goto finish

error: 
select ErrorCode=@rc

finish: 
go

Вставте рядок і зупиніть слід:

USE LockDemo
GO
INSERT MyTable DEFAULT VALUES
GO
EXEC sp_trace_setstatus 3, 0
EXEC sp_trace_setstatus 3, 2
GO

Відкрийте файл трасування і вам слід знайти таке:

Вікно профіля

Послідовність взятих замків:

  1. Ексклюзивний замок на MyTable
  2. Блокування з намірною ексклюзивністю на сторінці 1: 211
  3. RangeInsert-NullResource на кластерному записі індексу для введеного значення
  4. Ексклюзивний замок на ключі

Потім замки відпускаються у зворотному порядку. Жодного разу на столі не придбали ексклюзивний замок.

Але це лише одна партійна вставка! Це не те саме, що два, три чи десятки, що працюють паралельно.

Так. Сервер SQL (і, мабуть, будь-який механізм реляційних баз даних) не має передбачення щодо того, які інші партії можуть бути запущені при обробці оператора та / або партії, тому послідовність придбання блокування не змінюється.

Що з вищими рівнями ізоляції, наприклад, серіалізацією?

Для цього конкретного прикладу взяті точно такі ж замки. Не вірте мені, спробуйте!


2
Дуже інформативні. Приємна робота @Mark!
jcolebrand

0

Я не дуже працюю над T-SQL, але читаю документацію ...

Це задумано, як зазначено в ПОЧАТКОВІЙ ПЕРЕВАГІ :

Залежно від поточних налаштувань рівня ізоляції транзакцій, багато ресурсів, отриманих для підтримки операторів Transact-SQL, виданих з'єднанням, блокуються транзакцією, поки вона не завершиться або оператором COMMIT TRANSACTION, або ROLLBACK TRANSACTION.

І як зазначено в документації INSERT , вона придбає ексклюзивний замок на столі. Єдиний спосіб зробити SELECT проти таблиці - це використовувати NOLOCKабо встановити рівень ізоляції транзакції.


4
Раніше не помічав цього досить погано сформульованого твердження в BOL. Буде потрібно ексклюзивне блокування чогось в ієрархії ресурсів, але це, безумовно, не завжди в таблиці.
Марк Сторі-Сміт

6
-1 для документів (не з вашої вини) - легко довести, що це неправда в ізоляції знімків, тому ковдра "завжди набуває ексклюзивний (X) замок" - це неправильно. Не впевнений у інших рівнях ізоляції.
Джек каже, спробуйте topanswers.xyz
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.