我正在使用 SQL Server 2005 Express。
在一个场景中,我在存储过程中Begin Transaction的INSERT语句之前添加了命令。当我执行这个存储过程时,它锁定了整个表,所有并发连接都显示挂起,直到INSERT完成。
为什么整个表都被锁定了,我如何在 SQL Server 2005 Express 中解决这个问题?
已编辑
查询如下:
INSERT INTO <table2> SELECT * FROM <table1> WHERE table1.workCompleted = 'NO'
Run Code Online (Sandbox Code Playgroud)
Mar*_*ith 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
Run Code Online (Sandbox Code Playgroud)
设置一个探查器跟踪来跟踪 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
Run Code Online (Sandbox Code Playgroud)
插入一行并停止跟踪:
USE LockDemo
GO
INSERT MyTable DEFAULT VALUES
GO
EXEC sp_trace_setstatus 3, 0
EXEC sp_trace_setstatus 3, 2
GO
Run Code Online (Sandbox Code Playgroud)
打开跟踪文件,您应该找到以下内容:

采取的锁的顺序是:
然后以相反的顺序释放锁。在任何时候都没有在表上获得排他锁。
但这只是一批插入!这与并行运行的两个、三个或数十个不同。
是的。SQL Server(以及可以说是任何关系数据库引擎)在处理语句和/或批处理时没有预见到其他批处理可能正在运行,因此锁定获取的顺序不会改变。
更高的隔离级别(例如可序列化)怎么样?
对于这个特定的例子,采用了完全相同的锁。不要相信我,试试吧!
小智 0
我不做太多 T-SQL 工作,但通过阅读文档...
这是设计使然,如BEGIN TRANSACTION中所述:
根据当前事务隔离级别设置,为支持连接发出的 Transact-SQL 语句而获取的许多资源将被事务锁定,直到使用 COMMIT TRANSACTION 或 ROLLBACK TRANSACTION 语句完成为止。
正如INSERT文档所述,它将获取表上的独占锁。对表进行 SELECT 的唯一方法是使用NOLOCK或设置事务的隔离级别。
| 归档时间: |
|
| 查看次数: |
31295 次 |
| 最近记录: |