“쿼리 삽입”전에“트랜잭션 시작”이 전체 테이블을 잠그는 이유는 무엇입니까?


11

SQL Server 2005 Express를 사용하고 있습니다.

시나리오 에서 저장 프로 시저 Begin TransactionINSERT명령문 바로 앞에 명령을 추가했습니다 . 이 저장 프로 시저를 실행하면 전체 테이블이 잠기고 모든 동시 연결에이 시간이 INSERT끝날 때까지 중단 된 표시가 나타납니다 .

전체 테이블이 잠기는 이유는 무엇이며 SQL Server 2005 Express에서이 문제를 어떻게 극복 할 수 있습니까?

편집

쿼리는 다음과 같습니다.

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

2
postgresql에서 테이블을 잠그지 않습니다.
Scott Marlowe

더 많은 @RPK가 필요합니다. 표 DDL과 인서트 샘플을 사용하면 무슨 일이 일어나고 있는지 정확하게 설명 할 수 있습니다. 그것없이 우리는 단지 추측하고 있습니다.
Mark Storey-Smith

이 질문은 너무 모호합니다. 다른 DBMS에 대한 참조를 제거하고 SqlServer에 대한 응답을 제한하고 있습니다. OP 또는 다른 독자가 다른 플랫폼에서이 핵심 개념의 장점에 대해 논의하고 싶다면 플랫폼 당 한 번씩 논의해야합니다. 이것을 직교 조인으로 만드는 것은 해 롭습니다. 한 페이지에 너무 많은 대화 스레드가있을 것입니다.
jcolebrand

답변:


25

이 답변은 원래 질문에 도움이 될 수 있지만 주로 다른 게시물의 부정확 한 정보를 해결하기위한 것입니다. 또한 BOL에서 넌센스 섹션을 강조 표시합니다.

그리고 INSERT 문서에 명시된 바와 같이 테이블에 대한 독점 잠금을 획득합니다. 테이블에 대해 SELECT를 수행 할 수있는 유일한 방법은 NOLOCK을 사용하거나 트랜잭션의 격리 수준을 설정하는 것입니다.

BOL의 링크 된 섹션은 다음과 같습니다.

INSERT 문은 항상 수정하는 테이블에서 배타적 (X) 잠금을 획득하고 트랜잭션이 완료 될 때까지 해당 잠금을 보유합니다. 배타적 (X) 잠금을 사용하면 다른 트랜잭션은 데이터를 수정할 수 없습니다. 읽기 작업은 NOLOCK 힌트 또는 읽기 커밋되지 않은 격리 수준을 사용해야 만 수행 할 수 있습니다. 자세한 정보 는 데이터베이스 엔진 잠금을 참조하십시오 .

주의 : 2014-8-27 현재, 위에서 인용 한 잘못된 진술을 제거하기 위해 BOL이 업데이트되었습니다.

고맙게도 이것은 사실이 아닙니다. 이 경우 테이블에 대한 삽입이 연속적으로 발생하고 삽입 트랜잭션이 완료 될 때까지 모든 판독기가 전체 테이블에서 차단됩니다. 그러면 SQL Server가 NTFS만큼 효율적인 데이터베이스 서버가됩니다. 하지 매우.

상식은 그렇게 할 수 없다고 제안하지만 Paul Randall이 지적한 것처럼 " 자신에게 호의를 베풀지 말아라 " 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

lock : acquired 및 lock : released 이벤트를 추적하고, 이전 스크립트에서 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. 키 전용 잠금

그런 다음 잠금은 역순으로 해제됩니다. 테이블에서 독점 잠금을 획득 한 시점은 없습니다.

그러나 이것은 단지 하나의 배치 삽입입니다! 2, 3 또는 수십 개의 병렬 실행과 동일하지 않습니다.

네 그렇습니다. SQL Server (및 관계형 데이터베이스 엔진)는 명령문 및 / 또는 배치를 처리 할 때 다른 배치가 실행될 수 있는지에 대해 예측할 수 없으므로 잠금 획득 순서는 변하지 않습니다.

Serializable과 같은 더 높은 격리 수준은 어떻습니까?

이 특정 예에서는 정확히 동일한 잠금이 사용됩니다. 날 믿지마, 시도 해봐!


2
매우 유익합니다. 잘 했어 @ 마크!
jcolebrand

0

나는 많은 T-SQL 작업을하지 않지만 문서를 읽는 것에서 ...

이것은 BEGIN TRANSACTION에 명시된 바와 같이 의도적으로 설계된 것입니다 .

현재 트랜잭션 격리 수준 설정에 따라 연결에서 발급 된 Transact-SQL 문을 지원하기 위해 획득 한 많은 리소스는 트랜잭션이 COMMIT TRANSACTION 또는 ROLLBACK TRANSACTION 문으로 완료 될 때까지 트랜잭션에 의해 잠 깁니다.

그리고 INSERT 문서에 명시된 바와 같이 테이블에 대한 독점 잠금을 획득합니다. 테이블에 대해 SELECT를 수행 할 수있는 유일한 방법 NOLOCK은 트랜잭션의 격리 수준 을 사용 하거나 설정하는 것입니다.


4
이전에 BOL에서 다소 나쁜 말로 된 진술을 보지 못했습니다. 리소스 계층 구조 내에서 무언가에 대한 독점 잠금이 필요하지만 가장 확실하게 항상 테이블은 아닙니다.
Mark Storey-Smith

6
문서의 경우 -1 (오류가 아님)-스냅 샷 격리에서 이것이 사실이 아님을 쉽게 증명할 수 있으므로 담요 "항상 배타적 (X) 잠금 획득"이 잘못되었습니다. 다른 격리 수준에 대해 확실하지 않습니다.
잭 topanswers.xyz 시도라고
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.