Почему «Начать транзакцию» до того, как «Вставить запрос» блокирует всю таблицу?

11

Я использую SQL Server 2005 Express.

В сценарии я добавил Begin Transactionкоманду непосредственно перед INSERTоператором в хранимой процедуре. Когда я выполнил эту хранимую процедуру, она заблокировала всю таблицу, и все одновременные соединения отображали зависший экран до тех пор, пока это не INSERTзакончилось.

Почему вся таблица блокируется и как мне решить эту проблему в SQL Server 2005 Express?

Edited

Запрос, как показано ниже:

INSERT INTO <table2> SELECT * FROM <table1> WHERE table1.workCompleted = 'NO'
RPK
источник
2
Он не блокирует таблицу в postgresql.
Скотт Марлоу
Нужно больше @RPK. С таблицей DDL и образцом вкладышей мы можем дать вам точное объяснение того, что происходит. Без этого мы просто догадываемся.
Марк Стори-Смит
Этот вопрос слишком расплывчатый. Я удаляю любые ссылки на другие СУБД и ограничиваю ответы SqlServer. Если ОП или любой другой читатель хочет обсудить достоинства этой базовой концепции на других платформах, то мы должны обсудить ее один раз для каждой платформы. Сделать это декартовым соединением вредно, на одной странице будет слишком много разных потоков разговоров.
Jcolebrand

Ответы:

25

Этот ответ может оказаться полезным для первоначального вопроса, но в первую очередь для устранения неточной информации в других сообщениях. Это также выдвигает на первый план раздел ерунды в BOL.

И, как указано в документации INSERT , он получит эксклюзивную блокировку на столе. Единственный способ сделать SELECT для таблицы - это использовать NOLOCK или установить уровень изоляции транзакции.

Связанный раздел BOL гласит:

Оператор INSERT всегда получает монопольную блокировку (X) для таблицы, которую он изменяет, и удерживает эту блокировку до завершения транзакции. С эксклюзивной блокировкой (X) никакие другие транзакции не могут изменять данные; Операции чтения могут выполняться только с использованием подсказки NOLOCK или считывания незафиксированного уровня изоляции. Для получения дополнительной информации см. Блокировка в компоненте Database Engine .

Примечание: по состоянию на 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. Intent-Эксклюзивная блокировка на MyTable
  2. Intent-Эксклюзивный замок на странице 1: 211
  3. RangeInsert-NullResource для записи кластеризованного индекса для вставляемого значения
  4. Эксклюзивный замок на ключ

Замки затем отпускаются в обратном порядке. Ни в коем случае не было эксклюзивной блокировки на столе.

Но это только одна партия вставки! Это не то же самое, что два, три или десятки, работающие параллельно.

Да, именно так. SQL Server (и, возможно, любой механизм реляционных баз данных) не имеет представления о том, какие другие пакеты могут выполняться при обработке оператора и / или пакета, поэтому последовательность получения блокировки не меняется.

Как насчет более высоких уровней изоляции, например, Serializable?

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

Марк Стори-Смит
источник
2
Очень информативно. Хорошая работа @Mark!
Jcolebrand
0

Я не делаю много работы с T-SQL, но читаю документацию ...

Это сделано так, как указано в НАЧАЛО СДЕЛКИ :

В зависимости от текущих настроек уровня изоляции транзакции многие ресурсы, полученные для поддержки операторов Transact-SQL, выданных соединением, блокируются транзакцией до тех пор, пока она не будет завершена оператором COMMIT TRANSACTION или ROLLBACK TRANSACTION.

И, как указано в документации INSERT , он получит эксклюзивную блокировку на столе. Единственный способ сделать SELECT для таблицы - это использовать NOLOCKили установить уровень изоляции транзакции.


источник
4
Раньше не замечал этого довольно плохо сформулированного заявления в BOL. Потребуется исключительная блокировка чего-либо в иерархии ресурсов, но это, безусловно, не всегда таблица.
Марк Стори-Смит
6
-1 для документов (не ваша вина) - легко доказать, что это не так в изоляции моментальных снимков, поэтому одеяло «всегда получает эксклюзивную (X) блокировку» неверно. Не уверен насчет других уровней изоляции.
Джек говорит, попробуйте topanswers.xyz