数据库锁机制与并发控制
字数 1876 2025-11-02 09:29:26

数据库锁机制与并发控制

描述
数据库锁机制是并发控制的核心技术,用于协调多个事务同时访问共享数据时的冲突问题。当多个事务并发执行时,可能会引发脏读、不可重复读、幻读等问题。锁机制通过为数据资源加锁,限制其他事务的访问方式,从而确保数据的一致性和隔离性。理解锁的类型、兼容性以及死锁是掌握该知识点的关键。

解题过程
我们将从并发问题入手,逐步讲解锁如何解决这些问题。

第一步:理解并发操作可能引发的问题
在多个事务未经控制地并发执行时,会出现以下典型问题:

  1. 脏读:事务A读取了事务B已修改但未提交的数据。如果事务B后来回滚,那么事务A读到的就是无效的“脏”数据。
  2. 不可重复读:事务A内多次读取同一数据。在两次读取的间隔,事务B修改并提交了该数据,导致事务A两次读取的结果不一致。
  3. 幻读:事务A根据条件查询一批数据。在查询过程中,事务B插入或删除了符合该条件的记录并提交,导致事务A再次查询时,结果集的行数发生了变化,如同出现了“幻觉”。

第二步:认识锁的基本类型——共享锁与排他锁
锁是解决上述问题的基本工具。最核心的两种锁是:

  1. 共享锁(S锁,读锁)
    • 作用:用于读取操作。一个事务对数据对象加了S锁后,其他事务可以继续加S锁进行读取,但不能加X锁进行修改。
    • 类比:就像多人同时打开同一份文档阅读,大家都能看,但不能有任何人去修改它。
  2. 排他锁(X锁,写锁)
    • 作用:用于写入操作(增、删、改)。一个事务对数据对象加了X锁后,其他事务既不能对其加S锁(读取),也不能加X锁(修改)
    • 类比:就像一个人锁上门独自在房间里修改文档,在此期间,其他人既不能进来看,也不能进来改。

锁的兼容性矩阵如下:

当前已有的锁 请求共享锁(S) 请求排他锁(X)
无锁 允许 允许
共享锁(S) 允许 拒绝
排他锁(X) 拒绝 拒绝

第三步:锁是如何解决并发问题的
现在,我们看锁如何应用于事务来解决第一步中的问题。

  • 解决脏读:事务B在修改数据前必须先获得X锁。在事务B提交之前,这个X锁一直持有。此时事务A尝试读取该数据(请求S锁),但由于X锁与S锁不兼容,请求会被阻塞,直到事务B提交或回滚后释放X锁。这样,事务A就读不到未提交的数据。
  • 解决不可重复读:事务A在第一次读取数据后,并不立即释放S锁,而是持续持有直到事务结束。这样,在事务A执行期间,事务B尝试修改该数据(请求X锁)会被阻塞,因为S锁和X锁不兼容。这就保证了事务A在期间多次读取该数据时,结果是一致的。
  • 解决幻读:解决幻读需要一种更“宽”的锁,称为范围锁。事务A在查询时(例如WHERE age > 20),不仅会为表中已有的、满足age > 20的记录加锁,还会为整个age > 20这个“范围”加锁。这个范围锁会阻止其他事务在这个范围内插入新的记录(例如INSERT ... age=25),从而防止了幻读的发生。

第四步:理解锁的粒度
锁可以施加在不同大小的数据单元上,这就是锁的粒度。

  • 行级锁:锁住一行记录。粒度最小,并发度最高,但管理开销最大。
  • 页级锁:锁住一页数据(数据库存储的基本单位,通常包含多行)。粒度与开销介于行锁和表锁之间。
  • 表级锁:锁住整张表。粒度最大,实现简单,开销小,但并发度非常低。

数据库系统会根据操作需要自动选择最合适的锁粒度。

第五步:认识死锁及其解决策略
当多个事务互相等待对方释放锁时,就会进入死锁状态。

  • 示例

    • 事务A锁住了行1,请求锁行2。
    • 事务B锁住了行2,请求锁行1。
    • 此时,事务A等待事务B释放行2,事务B等待事务A释放行1。双方无限期等待,形成死锁。
  • 解决策略

    1. 预防:规定所有事务必须按统一的顺序申请锁,破坏“循环等待”条件。
    2. 检测与恢复(数据库常用):系统维护一个“等待图”来检测是否存在循环等待(死锁)。一旦检测到死锁,会选择一个“牺牲者”事务(通常选择撤销代价最小的那个),将其回滚并释放其持有的所有锁,从而让其他事务得以继续执行。

总结
数据库锁机制是一个通过共享锁(S)排他锁(X)来控制并发访问的系统。它通过锁的兼容性规则和不同的加锁策略,在不同隔离级别下解决了脏读、不可重复读和幻读等问题。同时,需要注意锁的粒度权衡和死锁的应对策略。理解这套机制,是设计和优化高并发数据库应用的基础。

数据库锁机制与并发控制 描述 数据库锁机制是并发控制的核心技术,用于协调多个事务同时访问共享数据时的冲突问题。当多个事务并发执行时,可能会引发脏读、不可重复读、幻读等问题。锁机制通过为数据资源加锁,限制其他事务的访问方式,从而确保数据的一致性和隔离性。理解锁的类型、兼容性以及死锁是掌握该知识点的关键。 解题过程 我们将从并发问题入手,逐步讲解锁如何解决这些问题。 第一步:理解并发操作可能引发的问题 在多个事务未经控制地并发执行时,会出现以下典型问题: 脏读 :事务A读取了事务B 已修改但未提交 的数据。如果事务B后来回滚,那么事务A读到的就是无效的“脏”数据。 不可重复读 :事务A内多次读取同一数据。在两次读取的间隔,事务B 修改并提交 了该数据,导致事务A两次读取的结果不一致。 幻读 :事务A根据条件查询一批数据。在查询过程中,事务B 插入或删除 了符合该条件的记录并提交,导致事务A再次查询时,结果集的行数发生了变化,如同出现了“幻觉”。 第二步:认识锁的基本类型——共享锁与排他锁 锁是解决上述问题的基本工具。最核心的两种锁是: 共享锁(S锁,读锁) : 作用 :用于读取操作。一个事务对数据对象加了S锁后, 其他事务可以继续加S锁 进行读取,但 不能加X锁 进行修改。 类比 :就像多人同时打开同一份文档阅读,大家都能看,但不能有任何人去修改它。 排他锁(X锁,写锁) : 作用 :用于写入操作(增、删、改)。一个事务对数据对象加了X锁后, 其他事务既不能对其加S锁(读取),也不能加X锁(修改) 。 类比 :就像一个人锁上门独自在房间里修改文档,在此期间,其他人既不能进来看,也不能进来改。 锁的兼容性矩阵 如下: | 当前已有的锁 | 请求共享锁(S) | 请求排他锁(X) | | :--- | :---: | :---: | | 无锁 | 允许 | 允许 | | 共享锁(S) | 允许 | 拒绝 | | 排他锁(X) | 拒绝 | 拒绝 | 第三步:锁是如何解决并发问题的 现在,我们看锁如何应用于事务来解决第一步中的问题。 解决脏读 :事务B在修改数据前必须先获得X锁。在事务B提交之前,这个X锁一直持有。此时事务A尝试读取该数据(请求S锁),但由于X锁与S锁不兼容,请求会被阻塞,直到事务B提交或回滚后释放X锁。这样,事务A就读不到未提交的数据。 解决不可重复读 :事务A在第一次读取数据后,并不立即释放S锁,而是持续持有直到事务结束。这样,在事务A执行期间,事务B尝试修改该数据(请求X锁)会被阻塞,因为S锁和X锁不兼容。这就保证了事务A在期间多次读取该数据时,结果是一致的。 解决幻读 :解决幻读需要一种更“宽”的锁,称为 范围锁 。事务A在查询时(例如 WHERE age > 20 ),不仅会为表中已有的、满足 age > 20 的记录加锁,还会为整个 age > 20 这个“范围”加锁。这个范围锁会阻止其他事务在这个范围内插入新的记录(例如 INSERT ... age=25 ),从而防止了幻读的发生。 第四步:理解锁的粒度 锁可以施加在不同大小的数据单元上,这就是锁的粒度。 行级锁 :锁住一行记录。粒度最小,并发度最高,但管理开销最大。 页级锁 :锁住一页数据(数据库存储的基本单位,通常包含多行)。粒度与开销介于行锁和表锁之间。 表级锁 :锁住整张表。粒度最大,实现简单,开销小,但并发度非常低。 数据库系统会根据操作需要自动选择最合适的锁粒度。 第五步:认识死锁及其解决策略 当多个事务互相等待对方释放锁时,就会进入死锁状态。 示例 : 事务A锁住了行1,请求锁行2。 事务B锁住了行2,请求锁行1。 此时,事务A等待事务B释放行2,事务B等待事务A释放行1。双方无限期等待,形成死锁。 解决策略 : 预防 :规定所有事务必须按统一的顺序申请锁,破坏“循环等待”条件。 检测与恢复(数据库常用) :系统维护一个“等待图”来检测是否存在循环等待(死锁)。一旦检测到死锁,会选择一个“牺牲者”事务(通常选择撤销代价最小的那个),将其 回滚 并释放其持有的所有锁,从而让其他事务得以继续执行。 总结 数据库锁机制是一个通过 共享锁(S) 和 排他锁(X) 来控制并发访问的系统。它通过锁的兼容性规则和不同的加锁策略,在不同隔离级别下解决了脏读、不可重复读和幻读等问题。同时,需要注意锁的粒度权衡和死锁的应对策略。理解这套机制,是设计和优化高并发数据库应用的基础。