消失的共享锁


消失的共享锁

原文地址:

http://sqlblog.com/blogs/paul_white/archive/2010/10/31/read-committed-shared-locks-and-rollbacks.aspx

 译者:

在SQL SERVER中,不同事务隔离级别下,获取共享锁的情况亦不同:

1)  Read Uncommitted下不申请共享锁。

2)  Read Committed下需申请共享锁,语句执行过程中,共享锁使用完立即释放。

3)  Repeatable Read下需申请共享锁,共享锁需等到事务结束才释放。

4)  Serialization下申请键范围锁。

SQL SERVER的缺省事务隔离级别是Read Committed,在事务执行的过程中会不停地申请和释放共享锁,代价非常大。因此SQL SERVER对共享锁进行了某种优化:在某些情况下,会跳过共享锁的申请和释放。注意:SQL SERVER至今没有在任何文档(至少我没有看见)中描述过这种共享锁优化,一切都是推测和总结。

以前翻译的文章《疯狂的“独占”行锁》其实就应该属于这种共享锁优化。本文的作者试图更加全面的描述这种共享锁优化行为。

译文:

这本文中,我将描述一种目前知之甚少的锁优化行为,它会给下面的问题一个令人意外的答案:

“如果我在一个数据行中拥有一个排他锁,其他运行在缺省的readcommitted隔离级别的事务还能读该行吗?”

基于以往的知识:读操作在试图申请一个共享锁时会受阻,所以大多数人的答案是:“不能”。还有一些人会说:这得依赖于READ_COMMITTED_SNAPSHOT数据库选项是否打开,本文中我们假设不用考虑这种情况,我们就是简单的缺省read committed(锁)隔离级别下的情况。

一个令人意外的答案

为了进行调查,我们创建一个测试表,并添加一行数据。

CREATE TABLE
        dbo.Demo
        (
        some_key    INTEGER PRIMARY KEY,
        some_value  INTEGER NOT NULL,
        );
GO
INSERT  INTO
        dbo.Demo (some_key, some_value)
VALUES  (1, 100);
GO
IF      DB_NAME() = N'tempdb'
        CHECKPOINT;

现在,启动一个事务,获取一个排他锁,并列出该事务拥有的锁的情况。

BEGIN   TRANSACTION;
SELECT  D.some_key,
        D.some_value
FROM    dbo.Demo D WITH (XLOCK);
SELECT  L.resource_type,
        L.request_mode,
        L.request_status,
        L.resource_description,
        L.resource_associated_entity_id
FROM    sys.dm_tran_current_transaction T
JOIN    sys.dm_tran_locks L
        ON  L.request_owner_id = T.transaction_id;

就像所期望的一样:行上拥有一个排他锁,表和页上各有一个意向排他锁。

在另一个独立的连接中,我们试图来读取这个表的内容:

SELECT  D.some_key,
        D.some_value
FROM    dbo.Demo D WITH (ROWLOCK, READCOMMITTEDLOCK);
可能和你期望的不同,这个查询并没有受阻——尽管已经有排他锁,但还是立即就返回了。

(注:READCOMMITTEDLOCK表提示是确保不管READ_COMMITTED_SNAPSHOT选项的情况,读操作都使用READCOMMITTED(锁)隔离级别的规则)

锁优化

SQL SERVER包含一种优化:允许在某些情况下避免申请行级别上的共享锁。特别是若没有这些锁也不会有读到非提交事务数据的风险时,它就会跳过共享锁的申请。

上面的SELECT查询没有受阻的原因非常简单:没有申请共享锁。我们可以通过profiler来检查锁的申请情况:

在表和页级别上分别有一个意向共享锁(IS),这和第一个查询的意向排他锁是兼容的。因为并没有和已存在的排他锁相冲突的行级别的共享锁,所以并没有受阻。

仅仅是行级别共享锁

这种优化仅仅是针对行级别的共享锁的,所以如果我们特别申请一个其他粒度的锁,查询就要受阻了:

SELECT  D.some_key,
        D.some_value
FROM    dbo.Demo D WITH (PAGLOCK, READCOMMITTEDLOCK);
看下面的锁,我们看到一个表级别的意向共享锁(IS)已被成功申请。企图获取页级别的共享锁时受阻了,因为共享锁和已经存在的页级别的意向排他锁是相冲突的。

没有未提交的变化

如果页中含有未提交的变化,SQL SERVER也不会使用这种锁优化。这是有道理的:如果页中有一个未提交的变化,此时再不使用共享锁,就可能会发生脏读。在read committed隔离级别上是不允许发生脏读的。

SQL SERVER是基于页来使用这种锁优化的,所以一个读操作可能在一些页中会申请行级别共享锁(比如,这些页中有未提交的变化)而另外的页中却不需要申请(因为足够安全所以跳过了这些锁)。

为了演示,我们使用上面还开着的事务连接,并在表中插入第二条数据:

INSERT  INTO
        dbo.Demo (some_key, some_value)
VALUES  (2, 200);
现在两行都是排他锁,而且在页中有一个未提交的变化:

如果再次运行SELECT查询,我们会发现:正如我们所期望的,它在试图获取一个排他锁是被阻塞了。如果我们加上一个READPAST提示(跳过已锁定行),我们发现会立即返回查询结果——没有任何行。

SELECT  D.some_key,
        D.some_value
FROM    dbo.Demo D WITH (ROWLOCK, READCOMMITTEDLOCK, READPAST);
页上的两行都被跳过了:一条是因为前面使用了XLOCK表提示;另一条是为了保护新插入行(但是还没有提交)的排他锁。Profiler跟踪显示:这两个行级别的共享锁申请都超时了:

 你可能惊讶地发现你的数据库中有很多和变化的数据无关的排他锁。这是因为SQL SERVER在处理UPDATE命令时,不管数据是否被修改都将获取排他锁,。更多细节,请参阅我以前的文章: TheImpact of Updates that Don't Change Data.
提交变化就好了

如果提交数据,我们发现又可以跳过行级别锁了:

COMMIT  TRANSACTION;
BEGIN   TRANSACTION;
SELECT  D.some_key,
        D.some_value
FROM    dbo.Demo D WITH (XLOCK);
在第二个连接中,运行SELECT查询并不会获取任何共享锁,而且两行数据都返回了。

SELECT  D.some_key,
        D.some_value
FROM    dbo.Demo D WITH (ROWLOCK, READCOMMITTEDLOCK);

事务回滚非常大不同

如果我们在页上修改了数据,然后回滚了这些修改,此时SQLSERVER也不能在该页上使用锁优化,直到该页中进行一次提交或者将该页写到磁盘中(比如,执行checkpoint)。

使用上面连接打开的事务,我们插入第三行,并立即回滚修改:

INSERT  INTO
        dbo.Demo (some_key, some_value)
VALUES  (3, 300);

我们会发现SELECT在受影响的页上回申请共享锁,即使该页上并没有排他锁。

SELECT  D.some_key,
        D.some_value
FROM    dbo.Demo D WITH (ROWLOCK, READCOMMITTEDLOCK);
Profiler跟踪显示行级别的共享锁:

现在再次插入第三行数据,并提交变化:

BEGIN   TRANSACTION;
INSERT  INTO
        dbo.Demo (some_key, some_value)
VALUES  (3, 300);
COMMIT  TRANSACTION;
我们的SELECT查询返回了所有的三行数据,并且只是在表和页级别上申请了意向共享锁:

我们也可以通过手工执行CHECKPOINT重新使能(re-enabled)锁优化。

总结

SQLSERVER在读取数据时,会决定是否在行、页或者表级别上申请锁。本文描述的锁优化有一个有趣的副作用:一个查询获取页锁可能会被阻塞,而同样的查询使用ROWLOCK却可能不会被阻塞(我说“可能不会”是因为ROWLOCK是一个提示,而不是指令,所以引擎可能根本就不睬他)。

比如,一个需要申请页级共享锁的查询会因为遇到意向排他锁(IX)而被阻塞,但是该查询若指定ROWLOCK则可能仅申请页级意向共享锁(IS,它和IX锁兼容),并且不要获取任何行级别锁。这不是说你应该立即在所有的SELECT查询中加上ROWLOCK提示——这可能是一个非常坏的主意——但是你应当知道存在这样的锁优化方法。

Readcommitted隔离级别保证两件事:不会有脏写和脏读。但是没有说这种保证是如何实现的。SQL SERVER碰巧使用共享锁作为这种缺省隔离级别的部分实现——但是这种锁并不总是存在的。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
对于计算机专业的学生而言,参加各类比赛能够带来多方面的益处,具体包括但不限于以下几点: 技能提升: 参与比赛促使学生深入学习和掌握计算机领域的专业知识与技能,如编程语言、算法设计、软件工程、网络安全等。 比赛通常涉及实际问题的解决,有助于将理论知识应用于实践中,增强问题解决能力。 实践经验: 大多数比赛都要求参赛者设计并实现解决方案,这提供了宝贵的动手操作机会,有助于积累项目经验。 实践经验对于计算机专业的学生尤为重要,因为雇主往往更青睐有实际项目背景的候选人。 团队合作: 许多比赛鼓励团队协作,这有助于培养学生的团队精神、沟通技巧和领导能力。 团队合作还能促进学生之间的知识共享和思维碰撞,有助于形成更全面的解决方案。 职业发展: 获奖经历可以显著增强简历的吸引力,为求职或继续深造提供有力支持。 某些比赛可能直接与企业合作,提供实习、工作机会或奖学金,为学生的职业生涯打开更多门路。 网络拓展: 比赛是结识同行业人才的好机会,可以帮助学生建立行业联系,这对于未来的职业发展非常重要。 奖金与荣誉: 许多比赛提供奖金或奖品,这不仅能给予学生经济上的奖励,还能增强其成就感和自信心。 荣誉证书或奖状可以证明学生的成就,对个人品牌建设有积极作用。 创新与研究: 参加比赛可以激发学生的创新思维,推动科研项目的开展,有时甚至能促成学术论文的发表。 个人成长: 在准备和参加比赛的过程中,学生将面临压力与挑战,这有助于培养良好的心理素质和抗压能力。 自我挑战和克服困难的经历对个人成长有着深远的影响。 综上所述,参加计算机领域的比赛对于学生来说是一个全面发展的平台,不仅可以提升专业技能,还能增强团队协作、沟通、解决问题的能力,并为未来的职业生涯奠定坚实的基础。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值