数据库锁机制与并发控制
字数 2070 2025-11-02 17:10:18

数据库锁机制与并发控制

描述:数据库锁机制是保证数据一致性和事务隔离性的核心技术。当多个事务同时访问数据库时,通过锁可以防止并发操作导致的数据不一致问题,如丢失更新、脏读、不可重复读等。理解锁的类型、粒度以及死锁的产生与解决,是数据库领域的核心知识点。

知识讲解

第一步:为什么需要锁?—— 并发问题

想象一下,你和朋友同时编辑一个在线共享文档的同一行文字。如果没有控制机制,一个人刚输入的内容可能瞬间被另一个人的操作覆盖。数据库中也存在类似问题,主要体现在以下三个方面:

  1. 脏读:事务A读取了事务B尚未提交的修改。如果事务B后来回滚了,那么事务A读到的就是无效的“脏”数据。
  2. 不可重复读:事务A内多次读取同一数据。在两次读取之间,事务B修改并提交了该数据,导致事务A两次读取的结果不一致。
  3. 幻读:事务A根据条件查询出一批数据。此时事务B插入或删除了一些符合该条件的记录并提交。当事务A再次以相同条件查询时,发现多出了一些“幽灵”行或少了些行。

锁就是为了解决这些问题而存在的。

第二步:锁的基本类型

锁可以按照不同的维度分类,最基础的是按“权限”划分:

  1. 共享锁(S锁,读锁)

    • 行为:当一个事务对数据加共享锁后,其他事务可以继续加共享锁来读取该数据,但不能加排他锁来修改它。
    • 类比:就像很多人可以同时打开同一个PDF文件阅读,但只要有人在读,谁也不能去修改这个文件的内容。
    • 目的:保证在读取过程中,数据不会被其他事务修改,从而解决“脏读”问题。
  2. 排他锁(X锁,写锁)

    • 行为:当一个事务对数据加排他锁后,其他事务既不能再加共享锁读取,也不能加排他锁修改。
    • 类比:就像你独占了文档的编辑权限,在你编辑保存之前,其他人既不能看也不能改。
    • 目的:保证在修改数据时,不会有其他事务来读取或修改同一数据,从而保证修改的原子性和一致性。

兼容性规则:可以简单记为“读读兼容,读写/写写互斥”。

第三步:锁的粒度

锁可以应用在不同大小的数据单元上,这就是锁的粒度。粒度越小,并发性越好,但管理锁的开销越大。

  • 行级锁:锁住表中的一行记录。粒度最小,并发性最高,是主流关系型数据库(如MySQL的InnoDB、PostgreSQL)的默认或常用级别。
  • 页级锁:锁住一页数据(数据库存储的基本单位,通常包含多行)。粒度介于行锁和表锁之间。
  • 表级锁:锁住整张表。粒度最大,实现简单,开销小,但并发性能最差。例如,MySQL的MyISAM引擎就使用表级锁。

选择策略:数据库系统通常会自动选择锁粒度,并在开销和并发性之间做权衡。

第四步:封锁协议与隔离级别

仅仅有锁还不够,还需要规定事务何时加锁、何时释放锁。这套规则就是“封锁协议”。SQL标准通过定义不同的事务隔离级别,来对应不同的封锁协议严格程度,从而解决不同的并发问题。

隔离级别 脏读 不可重复读 幻读 锁策略简述
读未提交 ❌ 可能 ❌ 可能 ❌ 可能 写数据时加短暂的排他锁(事务结束释放),读不加锁。
读已提交 ✅ 避免 ❌ 可能 ❌ 可能 写数据加排他锁(事务结束释放);读数据时加共享锁,读完立即释放
可重复读 ✅ 避免 ✅ 避免 ❌ 可能 写数据加排他锁(事务结束释放);读数据时加共享锁,直到事务结束才释放
可串行化 ✅ 避免 ✅ 避免 ✅ 避免 最严格。可能在范围查询时加“间隙锁”来防止幻读。

关键点:锁的持有时间至关重要。“读已提交”级别下,共享锁读完就放,所以其他事务可以在你两次读取之间修改数据,导致“不可重复读”。而“可重复读”级别下,共享锁会一直持有到事务结束,从而保证了在事务内多次读取结果一致。

第五步:死锁与解决

当多个事务循环等待对方持有的锁时,就会发生死锁。

  • 场景模拟

    • 事务A持有锁1,并请求锁2。
    • 事务B持有锁2,并请求锁1。
    • 此时,事务A在等B,事务B在等A,双方都无法继续,形成死锁。
  • 数据库的解决方案

    1. 预防:在事务开始时,一次性申请所有可能需要的锁,或者规定一个统一的加锁顺序。但这种方法会降低系统吞吐量。
    2. 检测与解除(主流方案):数据库允许死锁发生,但会定期检测。一旦检测到死锁,它会选择一个“牺牲者”事务(通常是根据回滚代价最小等策略),将其回滚并释放其持有的所有锁,从而让其他事务可以继续执行。被回滚的事务会收到错误信息,需要应用程序重新发起。

总结
数据库锁机制是一个精巧的系统,它通过共享锁排他锁这两种基本工具,作用在行、表等不同粒度的数据上,并遵循不同的封锁协议(对应不同隔离级别) 来在数据一致性和系统并发性能之间取得平衡。同时,通过死锁检测和回滚机制来处理不可避免的资源竞争问题,确保了数据库在高并发场景下的稳定运行。

数据库锁机制与并发控制 描述 :数据库锁机制是保证数据一致性和事务隔离性的核心技术。当多个事务同时访问数据库时,通过锁可以防止并发操作导致的数据不一致问题,如丢失更新、脏读、不可重复读等。理解锁的类型、粒度以及死锁的产生与解决,是数据库领域的核心知识点。 知识讲解 : 第一步:为什么需要锁?—— 并发问题 想象一下,你和朋友同时编辑一个在线共享文档的同一行文字。如果没有控制机制,一个人刚输入的内容可能瞬间被另一个人的操作覆盖。数据库中也存在类似问题,主要体现在以下三个方面: 脏读 :事务A读取了事务B 尚未提交 的修改。如果事务B后来回滚了,那么事务A读到的就是无效的“脏”数据。 不可重复读 :事务A内多次读取同一数据。在两次读取之间,事务B修改并提交了该数据,导致事务A两次读取的结果不一致。 幻读 :事务A根据条件查询出一批数据。此时事务B插入或删除了一些符合该条件的记录并提交。当事务A再次以相同条件查询时,发现多出了一些“幽灵”行或少了些行。 锁就是为了解决这些问题而存在的。 第二步:锁的基本类型 锁可以按照不同的维度分类,最基础的是按“权限”划分: 共享锁(S锁,读锁) 行为 :当一个事务对数据加共享锁后,其他事务 可以 继续加共享锁来读取该数据,但 不能 加排他锁来修改它。 类比 :就像很多人可以同时打开同一个PDF文件阅读,但只要有人在读,谁也不能去修改这个文件的内容。 目的 :保证在读取过程中,数据不会被其他事务修改,从而解决“脏读”问题。 排他锁(X锁,写锁) 行为 :当一个事务对数据加排他锁后,其他事务 既不能 再加共享锁读取, 也不能 加排他锁修改。 类比 :就像你独占了文档的编辑权限,在你编辑保存之前,其他人既不能看也不能改。 目的 :保证在修改数据时,不会有其他事务来读取或修改同一数据,从而保证修改的原子性和一致性。 兼容性规则 :可以简单记为“读读兼容,读写/写写互斥”。 第三步:锁的粒度 锁可以应用在不同大小的数据单元上,这就是锁的粒度。粒度越小,并发性越好,但管理锁的开销越大。 行级锁 :锁住表中的一行记录。粒度最小,并发性最高,是主流关系型数据库(如MySQL的InnoDB、PostgreSQL)的默认或常用级别。 页级锁 :锁住一页数据(数据库存储的基本单位,通常包含多行)。粒度介于行锁和表锁之间。 表级锁 :锁住整张表。粒度最大,实现简单,开销小,但并发性能最差。例如,MySQL的MyISAM引擎就使用表级锁。 选择策略 :数据库系统通常会自动选择锁粒度,并在开销和并发性之间做权衡。 第四步:封锁协议与隔离级别 仅仅有锁还不够,还需要规定事务何时加锁、何时释放锁。这套规则就是“封锁协议”。SQL标准通过定义不同的事务隔离级别,来对应不同的封锁协议严格程度,从而解决不同的并发问题。 | 隔离级别 | 脏读 | 不可重复读 | 幻读 | 锁策略简述 | | :--- | :---: | :---: | :---: | :--- | | 读未提交 | ❌ 可能 | ❌ 可能 | ❌ 可能 | 写数据时加短暂的排他锁(事务结束释放),读不加锁。 | | 读已提交 | ✅ 避免 | ❌ 可能 | ❌ 可能 | 写数据加排他锁(事务结束释放); 读数据时加共享锁,读完立即释放 。 | | 可重复读 | ✅ 避免 | ✅ 避免 | ❌ 可能 | 写数据加排他锁(事务结束释放); 读数据时加共享锁,直到事务结束才释放 。 | | 可串行化 | ✅ 避免 | ✅ 避免 | ✅ 避免 | 最严格。可能在范围查询时加“间隙锁”来防止幻读。 | 关键点 :锁的持有时间至关重要。“读已提交”级别下,共享锁读完就放,所以其他事务可以在你两次读取之间修改数据,导致“不可重复读”。而“可重复读”级别下,共享锁会一直持有到事务结束,从而保证了在事务内多次读取结果一致。 第五步:死锁与解决 当多个事务循环等待对方持有的锁时,就会发生死锁。 场景模拟 : 事务A持有锁1,并请求锁2。 事务B持有锁2,并请求锁1。 此时,事务A在等B,事务B在等A,双方都无法继续,形成死锁。 数据库的解决方案 : 预防 :在事务开始时,一次性申请所有可能需要的锁,或者规定一个统一的加锁顺序。但这种方法会降低系统吞吐量。 检测与解除(主流方案) :数据库允许死锁发生,但会定期检测。一旦检测到死锁,它会选择一个“牺牲者”事务(通常是根据回滚代价最小等策略),将其 回滚 并释放其持有的所有锁,从而让其他事务可以继续执行。被回滚的事务会收到错误信息,需要应用程序重新发起。 总结 : 数据库锁机制是一个精巧的系统,它通过 共享锁 和 排他锁 这两种基本工具,作用在 行、表 等不同粒度的数据上,并遵循不同的 封锁协议(对应不同隔离级别) 来在数据一致性和系统并发性能之间取得平衡。同时,通过 死锁检测和回滚 机制来处理不可避免的资源竞争问题,确保了数据库在高并发场景下的稳定运行。