「Insert Query」の前に「Begin Transaction」がテーブル全体をロックするのはなぜですか?


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のテーブルをロックしません。
スコットマーロウ2011

もっと@RPKが必要です。テーブルDDLと挿入のサンプルを使用して、発生していることを正確に説明できます。それがなければ、私たちは推測しています。
Mark Storey-Smith

この質問は曖昧すぎます。他のDBMSへの参照を削除し、SqlServerへの応答を制限しています。OPまたは他の読者がこのコアコンセプトのメリットを他のプラットフォームで議論したい場合は、プラットフォームごとに1回議論する必要があります。これをデカルト結合にすると有害です。1つのページに異なるスレッドが多すぎます。
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

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. キーの排他ロック

その後、ロックは逆の順序で解放されます。テーブルで排他ロックが取得されていることはありません。

ただし、これは1つのバッチ挿入にすぎません。これは、2つ、3つ、または数十の並列実行とは異なります。

はい、そうです。SQL Server(およびおそらくすべてのリレーショナルデータベースエンジン)は、ステートメントやバッチを処理するときに実行されている他のバッチについて予測できないため、ロック取得のシーケンスは変わりません。

シリアライザブルなど、より高い分離レベルはどうですか?

この特定の例では、まったく同じロックが使用されます。私を信用しないで、試してください!


2
非常に有益です。@Markさん、よくできました!
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.