TABLOCK和TABLOCKX的区别

86

8
TABLOCK是一个共享锁,允许多个客户端同时将数据加载到表中。而排它锁则不行。 - Lieven Keersmaekers
@Lieven,使用tablocks可以将互斥性排除在锁定之外,在此锁定中,数据表在保留页内存周期中被构思。 - Roel
3个回答

118

这两种锁的主要区别在于,TABLOCK 会尝试获取“共享”锁,而 TABLOCKX 则是独占锁。

如果您正在事务中并在表上获取独占锁,例如:

SELECT 1 FROM TABLE WITH (TABLOCKX)

那么其他进程将无法在该表上获取任何锁,这意味着所有试图访问该表的查询都将被阻止,直到事务提交。

TABLOCK 只会获取一个共享锁,如果您的事务隔离级别是READ COMMITTED(默认设置),则共享锁会在语句执行后释放。如果您的隔离级别更高,例如:SERIALIZABLE,则共享锁将保留直到事务结束。


共享锁是可共享的。这意味着,如果两个事务都通过 TABLOCK 在表上持有S或IS锁,则它们都可以同时从表中读取数据。但是,如果事务A在表上持有共享锁,则事务B在所有共享锁释放之前将无法获取独占锁。了解哪些锁与哪些锁兼容


这两个提示都会让数据库绕过获取更精细的锁(例如行或页级别的锁)。原则上,更精细的锁允许您获得更好的并发性。因此,例如,一个事务可以在您的表中同时更新第100行和第1000行,并且另一个事务也可以在同时执行(这在页面锁定时会变得棘手,但让我们跳过这个)。

通常情况下,您需要使用颗粒锁,但有时您可能希望减少数据库并发以提高特定操作的性能并消除死锁的可能性。

通常情况下,您不会使用TABLOCKTABLOCKX,除非您绝对需要它来处理某些边缘情况。


9
其中一个边缘情况是,例如,当您最初创建表并使用“INSERT INTO”填充数据时。在这种情况下,独占锁可以为您带来很大的性能提升。 - Abel
1
意思是所有试图与表对话的查询都将被阻止,直到事务提交。但这并不是正确的。您仍然可以使用 NOLOCK 或 TRANSACTION ISOLATION LEVEL READ UNCOMMITTED 进行读取。 - Po-ta-toe
这似乎在 SQL Server 2016 上无法正常工作? - Frederik Gheysels

6
这篇mssqlcity上的旧文试图解释锁的类型:

共享锁用于不对数据进行更改或更新的操作,例如SELECT语句。

当SQL Server打算修改页面时,会使用更新锁,并在实际进行更改之前将更新页面锁提升为独占页面锁。

排他锁用于数据修改操作,例如UPDATE、INSERT或DELETE。

它没有涉及“意图”(这基本上是这些锁类型的修饰符)。意图(共享/独占)锁是在真正的锁定级别高于实际锁定级别的级别保持的锁。例如,如果您的事务对一行具有X锁定,则它还将在表级别上具有IX锁定(这样就阻止了其他事务在您的事务完成或回滚之前尝试在表上的更高级别上获取不兼容锁定,例如模式修改锁定)。
“共享”锁的概念非常简单-多个事务可以共享相同资源的共享锁,而只有一个事务可以拥有独占锁,并且独占锁会阻止任何事务获取或持有共享锁。

持有锁的时间是否有区别?TABLOCK 会一直持有到语句结束。如果同时指定了 HOLDLOCK,则共享表锁将一直持有到事务结束。但是 TABLOCKX 呢? - Carlo V. Dango
@Carlo - 独占锁总是持有到事务结束。 - Damien_The_Unbeliever
1
@Carlo,如果你想在读取提交的事务中保持锁定,则所有共享锁都会在语句结束时释放,否则你需要使用提示或更高的隔离级别。tablockx将获取X锁。 - Sam Saffron

5
这是一个例子,其中TABLOCK没有起作用,但TABLOCKX起了作用。
我有两个会话,都使用默认(READ COMMITTED)隔离级别:
会话1是一个显式事务,将从链接服务器复制数据到数据库中的一组表,并需要几秒钟才能运行。[例如,它删除问题] 会话2是一个插入语句,只是将行插入到会话1不更改的表中。[例如,它插入答案]。
(实际上,当会话1运行其事务时,有多个会话同时向表中插入多个记录)。
会话1必须查询会话2插入的表,因为它无法删除依赖于会话2添加的条目的记录。[例如:删除未回答的问题]。
因此,在会话1执行并且会话2尝试插入时,会话2每次都会在死锁中失败。
因此,会话1中的删除语句可能如下所示:DELETE tblA FROM tblQ LEFT JOIN tblX on ... LEFT JOIN tblA a ON tblQ.Qid = tblA.Qid WHERE ... a.QId IS NULL and ...
死锁似乎是由于在会话2 [3、4、5、...、n] 尝试插入到tblA时查询tblA之间的争用引起的。
在我的情况下,我可以将会话1的事务的隔离级别更改为SERIALIZABLE。当我这样做时:事务管理器已禁用其对远程/网络事务的支持。
因此,我可以按照此处接受的答案中的说明来解决它:The transaction manager has disabled its support for remote/network transactions 但是,a) 我不舒服首先将隔离级别更改为SERIALIZABLE - 据说会降低性能并可能产生其他后果,b) 不明白为什么这样做突然导致事务无法跨链接服务器工作,并且c) 不知道启用网络访问可能开放了哪些可能的漏洞。
似乎有仅在一个非常大的事务中引起麻烦的6个查询。
因此,我了解了TABLOCK和TabLOCKX。
我对差异不是非常清楚,也不知道哪个会起作用。但是看起来会。首先我尝试了TABLOCK,但似乎没有任何区别。竞争会话生成相同的死锁。然后我尝试了TABLOCKX,不再有死锁。
因此,在六个地方,我所需要做的就是添加一个WITH(TABLOCKX)。
因此,在 Session 1 中,删除语句可能如下所示: DELETE tblA FROM tblQ q LEFT JOIN tblX x on ... LEFT JOIN tblA a WITH (TABLOCKX) ON tblQ.Qid = tblA.Qid WHERE ... a.QId IS NULL and ...

3
欢迎来到stackoverflow!感谢您发布了第一个答案。我认为如果您去掉个人参考并只保留基本事实,这个答案将更有用于其他人。继续保持好工作! - Caltor

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接