更新时间:2026-07-31 GMT+08:00
分享

锁机制

MySQL的锁机制是数据库在并发访问时,为了保证数据一致性、完整性和隔离性而采取的一系列控制手段。它通过限制多个事务对同一数据资源的访问方式,来防止丢失更新、脏读、不可重复读、幻读等问题。

为什么需要锁

数据库中的“锁”本质上是一种并发控制机制。MySQL之所以需要锁,根本原因在于:当多个事务或会话同时访问同一数据时,必须通过某种方式协调它们的操作,以防止数据损坏、逻辑错误或违背业务规则。

锁的优势是什么

  • 防止数据冲突:在多用户并发访问数据库时,多个事务可能会同时尝试修改同一数据。锁通过让写操作互斥(比如行级排他锁),确保只有一个事务能修改数据,直到它提交或回滚,从而避免覆盖。
  • 保证事务的隔离性:锁机制帮助实现不同事务之间的隔离,确保每个事务在执行过程中不会受到其他事务的干扰。
  • 保证数据的一致性:锁机制确保在并发操作中,数据的一致性得到维护。例如,通过使用行级锁,可以确保在多个事务同时修改同一行数据时,只有一个事务能够成功提交,其他事务需要等待或回滚。
  • 提高并发性能:虽然锁机制会引入一定的开销,但合理的锁机制可以显著提高数据库的并发性能。例如,行级锁相比表级锁,可以允许多个事务同时访问表中的不同行,从而提高并发性能。
  • 防止死锁:锁机制还包括死锁检测和预防机制。死锁是指两个或多个事务在等待对方释放锁,从而导致所有事务都无法继续执行的情况。MySQL通过超时机制和死锁检测算法来防止和解决死锁问题。

锁的使用场景

  • 数据库备份与恢复:使用 mysqldump完整备份数据库,要求备份期间数据视图一致且不受写操作干扰,可以使用全局读锁FLUSH TABLES WITH READ LOCK,将所有表变为只读,使整个数据库快照冻结。
  • 高并发在线抢购:例如电商秒杀,多个请求同时扣减同一商品库存,需避免超卖的场景,可以对商品行加 X 锁,其他并发扣减会被阻塞,直到事务提交,保证“读取-判断-写入”的原子性。
    SELECT stock FROM product WHERE id=100 FOR UPDATE;
    UPDATE product SET stock = stock -1 WHERE id=100;
  • 数据迁移或全表更新:如需把一张大表的数据全部替换或迁移到新表,并且期间不希望有其他会话读写。可以使用表写锁LOCK TABLES old_table WRITE,阻塞所有其他会话的读写,确保迁移时数据完全静态。
  • 自增主键生成:针对高并发插入,要求主键唯一且连续(或单调)的场景,可以使用自增锁并发插入时确保每条记录获得唯一且递增的ID,避免主键冲突。

锁的工作原理

  • 行锁的工作原理

    行锁不是锁“数据行”,而是锁索引记录。如果一条UPDATE通过主键定位,就在主键索引上加锁;如果通过辅助索引,则同时锁住辅助索引记录及其对应的主键索引记录(通过回表)。

    1. 锁对象结构(lock_t)

      每个锁请求都会在内存中生成一个 锁对象,核心结构包含:

      • 锁的页面 / 行标识:(space_id, page_no) 定位到数据页,再通过 Heap No 指定行(记录在页内的物理顺序号)。
      • 锁类型:共享锁 (S) 或排他锁 (X)。
      • 锁模式:记录锁、间隙锁、临键锁等(由位掩码组合表示)。
      • 事务 ID:归属哪个事务。
      • 等待/持有链表:每个事务有一个trx_lock_t结构,管理其持有和等待的锁。
    2. 所有锁对象以哈希表形式挂载在lock_sys中:
      • 行锁哈希表:按 (space_id, page_no) 哈希,找到页上的锁列表。
      • 表锁哈希表:按 (space_id, table_id) 哈希。
    3. 加锁过程(以SELECT ... FOR UPDATE为例)
      1. 定位到目标记录所在的页面,获取页面锁(Page Lock,短暂保护 B-tree 操作)。
      2. 在 lock_sys->rec_hash 中找到该页的锁队列。
      3. 检查队列中是否有其他事务持有冲突锁:
        • 若无冲突,创建 lock_t 对象挂在队列尾部,标记为 LOCK_REC | LOCK_X | LOCK_ORDINARY(临键锁)。
        • 若有冲突,创建等待锁对象,插入等待队列,事务进入 等待状态,并放入等待图用于死锁检测。
      4. 释放页面锁。
  • 间隙锁的工作原理

    间隙锁不是锁记录,而是锁 两条索引记录之间的范围。实现上,InnoDB 使用“前一条记录的锁”来表示对该记录之前间隙的锁。

    • 对记录 R 加一个间隙锁,实际上标记在该记录的锁对象上,但设置一个 LOCK_GAP 标志,表示锁住的是 R 之前的间隙。
    • 对记录 R 加临键锁,就是锁住 R 本身 + R 之前的间隙(LOCK_ORDINARY)。

    这样,当另一个事务试图在该间隙插入新记录时,会检查插入位置前后记录上的锁,若发现有间隙锁,则产生冲突。

  • 意向锁的工作原理

    意向锁是表级锁,存储在表对象上,目的是让行锁与表锁可以高效共存。

    • 当事务需要给某行加 X 锁,会先在表上加 意向排他锁 (IX)。
    • 当事务要锁整张表(例如 LOCK TABLES t WRITE),会请求表级 X 锁。此时只需检查表上是否有其他事务的 IS/IX 锁(意向锁互斥),而不必遍历所有行。
  • 元数据锁 (MDL) 的工作原理

    MDL 不是 InnoDB 特有,而是 MySQL Server 层实现的一种锁,保护表结构(DDL)不被并发 DML 破坏。

    • MDL 对象:每个表在打开时有 MDL_ticket。
    • 锁类型:MDL_SHARED(DML)、MDL_SHARED_READ、MDL_SHARED_WRITE、MDL_EXCLUSIVE(DDL)。
    • 加锁逻辑:当执行SELECT时,获取MDL_SHARED;当执行ALTER TABLE时,尝试获取MDL_EXCLUSIVE。由于MDL通过等待队列 + 死锁检测管理,如果DDL无法立即获得EXCLUSIVE,会阻塞后续所有DML。

锁的分类

按照锁的作用范围分为以下几类:

  • 全局锁

    使用SQL语句FLUSH TABLES WITH READ LOCK,锁定整个实例,所有库所有表只读,阻塞所有DDL和DML操作。

  • 表级锁(Table-Level Locking)
    • 元数据锁(Metadata Lock,简称MDL):锁定表的元数据(表结构定义),防止在查询或写入时结构被更改(如DDL操作)。
    • 意向锁(Intention Locks):意向锁用于表示事务对表中的某些行有锁定的意图,主要作用是防止其他事务对表进行不兼容的锁定操作。
    • 自增锁(Auto-Increment Locks):自增锁用于管理自增列的值分配,确保在并发插入时自增列的值不会冲突。
  • 页级锁(Page-Level Locking)

    页级锁是介于表级锁和行级锁之间的一种锁机制,它锁定的是表中的一个页(通常包含多行数据)。页级锁的优点是开销比行级锁小,但并发性能比行级锁差。

  • 行级锁(Row-Level Locking)
    • 记录锁(Record Lock):锁定索引中的某一条具体记录,防止其他事务修改或删除该行。
    • 间隙锁(Gap Locks):间隙锁锁定的是索引记录之间的间隙,而不是具体的行。间隙锁用于防止其他事务在这些间隙中插入新的记录。间隙锁主要用于解决幻读问题。
    • 临键锁(Next-Key Locks):临键锁是行锁和间隙锁的组合,锁定的是一个索引记录及其前一个间隙。临键锁主要用于InnoDB存储引擎,用于实现可重复读(Repeatable Read)隔离级别。
    • 插入意向锁(Insert Intention Lock):特殊间隙锁,插入时设置,与其他间隙锁兼容。

与锁相关的特性和操作

在全面了解锁的核心概念与应用场景后,本小节将为您介绍锁的常用基础操作与高级应用特性。如果您想要深入了解并在实际业务中配置使用,可以点击相关超链接跳转查看详细的华为云官方文档。

锁相关操作:通过智能DBA功能查看数据库实例是否有元数据锁和InnoDB锁等待,以及查看最近死锁分析和全量死锁分析的数据,详情参考管理锁&事务

相关文档