这个答案可能对原始问题很有帮助,但主要是解决其他帖子中不准确的信息。它还突出了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
打开跟踪文件,您将找到以下内容:
采取的锁定顺序为:
- MyTable上的Intent-Exclusive锁
- 页面1:211上的Intent-Exclusive锁定
- 聚集索引条目上的RangeInsert-NullResource用于插入的值
- 排他锁
然后以相反的顺序释放锁。决不会在表上获得排他锁。
但这只是一批插入!这与并行运行的两个,三个或几十个不同。
是的。SQL Server(以及可能是任何关系数据库引擎)对于处理语句和/或批处理时可能正在运行的其他批处理没有预见性,因此锁获取的顺序不会改变。
更高的隔离级别(例如可序列化)怎么样?
对于此特定示例,将采用完全相同的锁。不相信我,试试看!