为什么“插入查询”之前的“开始交易”会锁定整个表?


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

Answers:


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上的Intent-Exclusive锁
  2. 页面1:211上的Intent-Exclusive锁定
  3. 聚集索引条目上的RangeInsert-NullResource用于插入的值
  4. 排他锁

然后以相反的顺序释放锁。决不会在表上获得排他锁。

但这只是一批插入!这与并行运行的两个,三个或几十个不同。

是的。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)锁”是错误的。不确定其他隔离级别。
杰克说,请在2011年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.