この回答は元の質問には役立つかもしれませんが、主に他の投稿の不正確な情報に対処するためのものです。また、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
トレースファイルを開くと、以下が見つかります。
取得されるロックのシーケンスは次のとおりです。
- MyTableの意図的排他ロック
- 1:211ページの意図的排他ロック
- 挿入される値のクラスター化インデックスエントリのRangeInsert-NullResource
- キーの排他ロック
その後、ロックは逆の順序で解放されます。テーブルで排他ロックが取得されていることはありません。
ただし、これは1つのバッチ挿入にすぎません。これは、2つ、3つ、または数十の並列実行とは異なります。
はい、そうです。SQL Server(およびおそらくすべてのリレーショナルデータベースエンジン)は、ステートメントやバッチを処理するときに実行されている他のバッチについて予測できないため、ロック取得のシーケンスは変わりません。
シリアライザブルなど、より高い分離レベルはどうですか?
この特定の例では、まったく同じロックが使用されます。私を信用しないで、試してください!