锁 颗粒度(行锁、页锁、表锁)
sql中会有各种 增删改查的操作 为了确保数据安全性,并发情况的处理 所以也会有数据库锁。
SQL Server 的锁机制是数据库并发控制的核心,用于协调多个事务对资源的访问。锁的粒度(行锁、表锁)和锁模式(S、X、U)共同决定了事务之间的兼容性与冲突
锁 类型
锁模式定义事务对资源的访问权限,不同模式之间的兼容性决定了事务能否并发执行:
-
共享锁(Shared Lock, S)
作用:用于读取操作(如 SELECT),允许多事务并发读取数据。
兼容性:
✅ 与 其他 S 锁 兼容。
❌ 与 X 锁 不兼容。
释放时机:默认在事务结束(隔离级别为 REPEATABLE READ 或更高时)或读取完成后释放(如 READ COMMITTED)。 -
排他锁(Exclusive Lock, X)
作用:用于写操作(如 INSERT, UPDATE, DELETE),确保事务独占资源。
兼容性:
❌ 与 所有其他锁 不兼容(包括 S、U、X)。
释放时机:事务结束时释放。 -
更新锁(Update Lock, U)
作用:用于 UPDATE 操作,防止多个事务同时更新同一资源导致的死锁。
行为:
事务在修改数据前先获取 U 锁,实际修改时升级为 X 锁。
U 锁与 S 锁兼容,但与其他 U 锁或 X 锁不兼容。
释放时机:升级为 X 锁后释放
U 锁的设计主要是为了减少单资源更新时的锁竞争,但它无法完全解决跨资源交叉访问导致的循环等待问题
-
意向锁(Intent Locks):用于协调不同粒度的锁(如行锁与表锁):
IS(意向共享锁):表明事务将在某些行上获取 S 锁。
IX(意向排他锁):表明事务将在某些行上获取 X 锁。
SIX(共享+意向排他锁):表级锁,表示事务持有 S 锁并将在某些行上获取 X 锁 -
行锁与表锁的联系
意向锁的桥梁作用:
当某事务对行加 S/X 锁时,会先在表级加 IS/IX 锁。
其他事务尝试加表级锁时,通过检查意向锁判断是否存在冲突。
锁升级:
当行锁数量超过阈值(默认 5000)或内存压力大时,SQL Server 自动将行锁升级为表锁。
可通过 ALTER TABLE … SET (LOCK_ESCALATION = DISABLE) 禁用锁升级。
死锁
解释一下 就是 因为锁 之间的阻塞 导致互相等待资源 极易在事务这种复杂多资源处理中出现
更新锁虽然防止了多个事务同时更新同一资源导致的死锁,如果两个事务以不同顺序更新互相依赖的资源,依然可能发生死锁
总结
死锁本质:循环等待资源 + 锁不兼容。
关键预防:统一访问顺序、降低事务粒度、优化索引(减少锁粒度 避免行锁》表锁)。
事后处理:重试机制 + 死锁日志分析
不加锁 出现的 脏读 幻读
一、WITH (NOLOCK) 的作用机制
- 避免申请共享锁(S 锁)
默认行为:在 SQL Server 的 READ COMMITTED 隔离级别下,普通 SELECT 查询会为读取的数据申请共享锁(S 锁),这会阻塞其他事务对相同数据的修改(X 锁)。
NOLOCK 的作用:
使用 WITH (NOLOCK) 时,查询会在 READ UNCOMMITTED 隔离级别下运行,直接跳过共享锁(S 锁)的申请,因此不会阻塞其他事务的写操作,也不会被写操作阻塞。 - 脏读风险的本质 1-2-1 读出来2
脏读(Dirty Read):由于不申请 S 锁,查询可能读取到其他事务未提交的数据。例如:
事务 A 修改了某行但未提交;
事务 B 使用 WITH (NOLOCK) 读取该行,会直接获取未提交的数据;
若事务 A 回滚,事务 B 读取的数据在逻辑上从未存在过。
典型场景:在长时间运行的写事务中,其他会话的 NOLOCK 查询可能读取到中间状态的无效数据
二、性能与风险的权衡
- 性能优势
减少锁争用:避免 S 锁的申请可减少事务间的阻塞,提升并发性能,尤其在高写入负载的场景中效果显著。
适用场景:
非关键业务报表或历史数据分析(允许数据轻微不一致);
高频查询但对数据实时性要求不高的场景(如监控大盘)。 - 潜在风险
数据不一致性:
除了脏读,还可能导致 不可重复读(同一事务内多次查询结果不同)和 幻读(其他事务插入或删除数据导致结果集变化)。
例如:财务系统中若使用 NOLOCK,可能因读取到未提交的金额导致严重错误。
锁的局限性:
NOLOCK 仍会申请 架构稳定性锁(Sch-S),防止其他会话修改表结构(如 ALTER TABLE)
行版本控制(RCSI) 提升性能不加锁还能 避免 脏读
-
RCSI 的核心机制
行版本控制(Read Committed Snapshot Isolation, RCSI)通过在 tempdb 中维护数据行的历史版本,使读操作无需申请共享锁(S锁),从而避免与写操作的锁争用。在 RCSI 模式下:
读操作:基于事务开始时的 已提交数据快照 执行,仅读取其他事务已提交的版本。
写操作:仍需要排他锁(X锁),但不会阻塞读操作,因为读操作通过版本快照访问数据。 -
RCSI 如何避免脏读
脏读的本质是读取到未提交的数据,而 RCSI 的版本控制机制 完全规避了这一问题:
快照的生成规则:
当数据库启用 RCSI 后,读操作会基于 当前语句开始时的最新已提交版本 生成快照。即使其他事务正在修改数据,这些未提交的修改对当前读操作不可见。
示例场景:
事务 A 更新某行但未提交;
事务 B 使用 RCSI 读取该行,会获取事务 A 开始前该行的已提交版本,而非未提交的新值;
事务 A 回滚后,事务 B 的读取结果仍与原始快照一致。 -
RCSI 与隔离级别的兼容性
默认行为:
在 SQL Server 中,启用 RCSI 后,READ COMMITTED 隔离级别自动使用行版本控制,而非传统的锁机制。
数据一致性保障:
RCSI 满足 ANSI SQL 的 READ COMMITTED 标准,即保证 不读取未提交数据,因此不会引发脏读。 -
潜在问题与注意事项
版本存储开销:
频繁的更新操作会导致 tempdb 中存储大量行版本,可能增加存储和 I/O 压力。