• SQL Server 锁机制


    锁兼容性图:

    一、锁的粒度:

    比较需要注意的是RID/KEY、HoBT/PAGE这两对儿的区别,RID和HoBT是针对堆表的,即没有聚集索引的表。

    二、锁的模式:

    1.关于其中的S、U、X锁:

    共享锁

    共享锁(S 锁)允许并发事务在封闭式并发控制下读取 (SELECT) 资源。 资源上存在共享锁(S 锁)时,任何其他事务都不能修改数据。 读取操作一完成,就立即释放资源上的共享锁(S 锁),除非将事务隔离级别设置为可重复读或更高级别,或者在事务持续时间内用锁定提示保留共享锁(S 锁)。

    更新锁

    更新锁(U 锁)可以防止常见的死锁。 在可重复读或可序列化事务中,此事务读取数据 [获取资源(页或行)的共享锁(S 锁)],然后修改数据 [此操作要求锁转换为排他锁(X 锁)]。 如果两个事务获得了资源上的共享模式锁,然后试图同时更新数据,则一个事务尝试将锁转换为排他锁(X 锁)。 共享模式到排他锁的转换必须等待一段时间,因为一个事务的排他锁与其他事务的共享模式锁不兼容;发生锁等待。 第二个事务试图获取排他锁(X 锁)以进行更新。 由于两个事务都要转换为排他锁(X 锁),并且每个事务都等待另一个事务释放共享模式锁,因此发生死锁。

    若要避免这种潜在的死锁问题,请使用更新锁(U 锁)。 一次只有一个事务可以获得资源的更新锁(U 锁)。 如果事务修改资源,则更新锁(U 锁)转换为排他锁(X 锁)。

    同oracle及mysql不同,SQL Server的update语句在查找阶段对资源加的是S锁,只有在找到数据后才会尝试将S锁转化为X锁,由于S锁转化为X的过程中可能遭遇如上所示的死锁,因此设计了U锁,这种U锁与S锁兼容与X、U锁不兼容,这样解决了可能的死锁隐患。那么为什么不直接使用X锁呢?mysql就是这样的,对索引记录和行记录这些资源直接加独占锁,SQL Server也可以这样,只不过如果直接使用独占锁,那么在update查找的过程中会直接导致select语句阻塞,由于SQL Server默认的读机制是一致性锁定读,所以我认为采用U锁是折中的办法。

    排他锁

    排他锁(X 锁)可以防止并发事务对资源进行访问。 使用排他锁(X 锁)时,任何其他事务都无法修改数据;仅在使用 NOLOCK 提示或未提交读隔离级别时才会进行读取操作。

    数据修改语句(如 INSERT、UPDATE 和 DELETE)合并了修改和读取操作。 语句在执行所需的修改操作之前首先执行读取操作以获取数据。 因此,数据修改语句通常请求共享锁和排他锁。 例如,UPDATE 语句可能根据与一个表的联接修改另一个表中的行。 在此情况下,除了请求更新行上的排他锁之外,UPDATE 语句还将请求在联接表中读取的行上的共享锁。

    Ps:在这里官网的解释可能会有歧义,DML操作虽然是读取和修改的合并,但是只有update才会先加IU、U锁来读取数据,然后修改时转化为IX、X。而删除和插入我们可以认为从一开始就是加的IX、X锁。

    2.关于其中的意向锁:
    意向锁有两种用途:
    • 防止其他事务以会使较低级别的锁无效的方式修改较高级别资源。
    • 提高数据库引擎在较高的粒度级别检测锁冲突的效率。

    3.关于其中的架构锁:

    数据库引擎在表数据定义语言 (DDL) 操作(例如添加列或删除表)的过程中使用架构修改 (Sch-M) 锁。 保持该锁期间,Sch-M 锁将阻止对表进行并发访问。 这意味着 Sch-M 锁在释放前将阻止所有外围操作。

    某些数据操作语言 (DML) 操作(例如表截断)使用 Sch-M 锁阻止并发操作访问受影响的表。

    数据库引擎在编译和执行查询时使用架构稳定性 (Sch-S) 锁。 Sch-S 锁不会阻止某些事务锁,其中包括排他 (X) 锁。 因此,在编译查询的过程中,其他事务(包括那些针对表使用 X 锁的事务)将继续运行。 但是,无法针对表执行获取 Sch-M 锁的并发 DDL 操作和并发 DML 操作。

    4.关于其中的大容量更新锁:
    大容量更新锁(BU 锁)允许多个线程将数据并发地大容量加载到同一表,同时防止其他不进行大容量加载数据的进程访问该表。 在满足以下两个条件时,数据库引擎使用大容量更新 (BU) 锁。
    • 使用 Transact-SQL BULK INSERT 语句或 OPENROWSET(BULK) 函数,或者您使用某个大容量插入 API 命令(如 .NET SqlBulkCopy)、OLEDB 快速加载 API 或 ODBC 大容量复制 API 来将数据大容量复制到表。
    • TABLOCK指定提示或表大容量加载上的锁表选项设置使用sp_tableoption。
    5.关于其中的键范围锁:
    在使用可序列化事务隔离级别时,对于 Transact-SQL 语句读取的记录集,键范围锁可以隐式保护该记录集中包含的行范围。 键范围锁可防止虚拟读取。 通过保护行之间键的范围,它还防止对事务访问的记录集进行虚拟插入或删除。

    关于键范围锁可以参考官网,或者另一篇博客SQL Server事务隔离级别中对于可序列化读隔离级别的加锁说明。

    三、锁升级

    SQL Server数据库会发生锁升级,官网说明的锁升级触发条件为,如果没有使用 ALTER TABLE SET LOCK_ESCALATION 选项来禁用表的锁升级并且满足以下任一条件时,触发锁升级:
    • 单个 Transact-SQL 语句在单个无分区表或索引上获得至少 5,000 个锁。
    • 单个 Transact-SQL 语句在已分区表的单个分区上获得至少 5,000 个锁,并且 ALTER TABLE SET LOCK_ESCALATION 选项设为 AUTO。
    • 数据库引擎实例中的锁的数量超出了内存或配置阈值。
    • 如果由于锁冲突导致无法升级锁,则数据库引擎每当获取 1,250 个新锁时便会触发锁升级。
    对于锁升级的优化官网提供如下建议:
    1. 使用READ_COMMITTED_SNAPSHOT事务隔离级别。
    2. 使用SNAPSHOT事务隔离级别。
    3. 使用READ UNCOMMITTED事务隔离级别。
    一般情况下我们只需要把READ_COMMITTED_SNAPSHOT选项打开即可,可以避免select加锁,从而避免阻塞和锁升级。
    此外还可以打开1211和1224来避免锁升级,但是极度不推荐,锁升级本身就是为加快锁获取的效率而设计的,根本解决办法还是优化SQL。
     
    参考文档
    SQL Server 事务锁定和行版本控制指南:https://msdn.microsoft.com/zh-cn/library/jj856598(v=sql.120).aspx
  • 相关阅读:
    独角戏
    开源引擎
    如何实现一个UI系统
    VC编程规范—程序员应该这样写代码
    夕阳下的熊猫香[转]
    在桌面上显示屏保
    在WinSock上使用IOCP
    结构体对齐的具体含义(#pragma pack)
    一个程序员的奋斗
    让汇编揭开死循环的神秘面纱
  • 原文地址:https://www.cnblogs.com/leohahah/p/8465062.html
Copyright © 2020-2023  润新知