当前位置:首页 > 问答 > 正文

SQL Server 锁机制:擦亮自己的眼睛去看SQL Server之浅谈锁管理

SQL Server | 锁机制:擦亮自己的眼睛去看SQL Server之浅谈锁管理

场景引入:那个让我加班的订单系统

上周五下午5点,我正准备收拾东西下班,突然接到运营同事的电话:"王哥,咱们订单系统又卡死了!用户投诉说提交订单一直转圈圈,最后报错!"我叹了口气,放下背包,打开SSMS连上生产环境——果然,又是锁的问题。

这不是第一次了,上个月促销活动时,系统就出现过类似情况,当时临时解决了,但看来根本问题还在,咱们就好好聊聊SQL Server这个让人又爱又恨的锁机制。

锁是什么?为什么需要它?

想象一下图书馆的自习室,如果所有人都能随便拿书、随便坐,那肯定会乱套——可能两个人同时看同一本书,或者有人占着座位却不用,SQL Server的锁机制就像图书管理员,它确保:

  1. 你读数据时,别人不能随便改(就像看书时别人不能在你书上乱画)
  2. 你改数据时,别人不能同时改(就像只能有一个人修改图书目录)
  3. 你改数据时,别人读到的要么是改前的完整数据,要么是改后的完整数据(不会读到半成品)

没有锁的话,数据库就会陷入混乱,出现"脏读"(读到别人没提交的修改)、"不可重复读"(两次读取结果不一样)、"幻读"(突然多出或少了记录)这些问题。

SQL Server的锁类型详解

SQL Server的锁就像一套组合工具,不同场景用不同的锁:

按锁的"严格程度"分

  • 共享锁(S锁):就像"我只看看不拿走",多个查询可以同时持有S锁读数据,但只要有S锁在,就不能加X锁。

    -- 这个查询会加S锁
    SELECT * FROM Orders WHERE OrderID = 10086
  • 排他锁(X锁):就像"这本书我借走了,谁都不给",有X锁时,其他任何锁都不能加。

    -- 这个语句会加X锁
    UPDATE Orders SET Status = '已支付' WHERE OrderID = 10086
  • 更新锁(U锁):特殊的存在,准备要升级为X锁前的"预备动作",防止多个事务同时尝试升级锁导致的死锁。

按锁的"范围"分

  • 行锁:最细粒度,只锁一行
  • 页锁:锁住整个数据页(约8KB)
  • 表锁:直接锁整张表
  • 数据库锁:整个数据库的锁,比如恢复操作时

特殊锁类型

  • 意向锁:像"预告"——"我可能要锁这表的某些行",避免其他事务直接加表锁
  • 架构锁:改表结构时用的锁
  • 大容量更新锁:批量导入数据时的特殊锁

锁的兼容性:谁能和谁共存

锁之间能否共存是个重要问题,就像有些人能共处一室,有些人会打架:

请求的锁 \ 现有的锁 S 锁 X 锁 U 锁
S 锁
X 锁
U 锁

锁升级:SQL Server的"自动挡"

SQL Server会尽量用细粒度锁(如行锁),但当锁太多时会自动升级为更粗的锁(如表锁),这就像:

SQL Server 锁机制:擦亮自己的眼睛去看SQL Server之浅谈锁管理

  • 行锁:给书里某个段落贴便签
  • 页锁:给整页贴便签
  • 表锁:直接把书借走

锁升级阈值默认是5000个锁,可以通过跟踪标志1211禁用(但不推荐)。

如何查看锁信息?

当系统出现锁问题时,这几个工具特别有用:

系统视图查询

-- 查看当前锁情况
SELECT 
    request_session_id AS spid,
    resource_type AS type,
    resource_description AS description,
    request_mode AS mode,
    request_status AS status
FROM sys.dm_tran_locks
WHERE resource_database_id = DB_ID('你的数据库名');

活动监视器

SSMS里右键服务器 -> "活动监视器" -> "进程"页签,能看到阻塞情况。

扩展事件(XEvent)

更强大的监控方式,可以捕获锁获取、释放等详细事件。

常见锁问题及解决方案

阻塞:最常见的锁问题

现象:一个事务长时间运行不提交,导致其他查询排队。

解决

  • 优化长时间事务,拆分为小事务
  • 添加适当的索引减少锁范围
  • 使用NOLOCK提示(但要明白脏读风险)
-- 应急时可以用,但要注意副作用
SELECT * FROM Orders WITH(NOLOCK) WHERE OrderID = 10086

死锁:互相等待的僵局

现象:事务A锁了资源1等资源2,事务B锁了资源2等资源1。

解决

  • 使用死锁优先级
  • 按固定顺序访问资源
  • 减少事务持续时间
-- 设置死锁优先级
SET DEADLOCK_PRIORITY HIGH;  -- 这个事务更重要

锁升级导致的并发下降

现象:本来好好的行锁突然变成表锁,并发急剧下降。

解决

SQL Server 锁机制:擦亮自己的眼睛去看SQL Server之浅谈锁管理

  • 分批处理大数据量操作
  • 使用TABLOCKX提示明确需要表锁
  • 调整锁升级阈值(谨慎)

最佳实践:写出"锁友好"的SQL

  1. 事务要短小精悍:就像在超市排队,买完赶紧结账走人

    -- 不好的写法
    BEGIN TRAN
    -- 这里有很多业务逻辑...
    COMMIT TRAN
    -- 好的写法
    BEGIN TRAN
    UPDATE Orders SET Status = '完成' WHERE OrderID = @OrderID
    COMMIT TRAN
  2. 访问顺序要一致:多个事务都按A->B->C顺序访问表,避免死锁

  3. 合理使用隔离级别:默认的READ COMMITTED能满足大部分场景

    -- 不是所有场景都需要最高隔离级别
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; -- 慎重使用
  4. 索引是锁的好朋友:良好的索引能让锁住更少的数据

真实案例:我们的订单系统优化

回到开头的订单系统问题,我们最终发现:

  1. 有一个统计报表查询没加索引,导致全表扫描长时间持有S锁
  2. 支付完成后的日志记录和订单更新在一个大事务中
  3. 高峰期并发高时,锁升级为表锁

解决方案

  1. 为报表查询添加覆盖索引
  2. 将支付流程拆分为:
    • 短事务更新订单状态
    • 异步记录日志
  3. 对大表操作采用分页处理

优化后,即使在促销期间,系统也能平稳运行。

SQL Server的锁机制就像交通信号灯,确保数据"车辆"有序通行,理解锁的工作原理,才能:

  • 快速定位性能问题
  • 设计出高并发的数据库应用
  • 在出现问题时知道如何应对

锁不是敌人,用不好的锁才是,下次当你遇到查询卡顿时,不妨先看看锁的情况,也许答案就在那里。 基于SQL Server 2022版本,信息参考日期2025年8月)

发表评论