MySQL中的锁详解

在 MySQL 中,锁机制是保证数据一致性和事务隔离性的基石。特别是对于默认的 InnoDB 存储引擎,理解各种锁的运作原理对于构建高并发应用至关重要。以下是关于 MySQL 中各类锁的详细教程:

一、按锁粒度划分

锁粒度决定了锁的影响范围。粒度越小,并发度越高,但锁管理的开销也就越大。

1. 全局锁(Global Lock)

  • 定义:锁定整个 MySQL 实例,是粒度最大的锁。
  • 作用:保证整个实例的数据一致性,防止数据被修改。
  • 实现与场景 :通过 FLUSH TABLES WITH READ LOCK (FTWRL) 命令加锁。主要用于全库逻辑备份,避免备份过程中数据被修改。加锁后,所有库的所有表都只能读,写操作(增删改、DDL)全部被阻塞。解锁使用 UNLOCK TABLES

2. 表级锁(Table-Level Lock)

  • 定义:锁定整张表,开销小、加锁快,但并发度低。
  • 常见类型
    • 表读锁(READ / 共享锁 S 锁):加锁后,自己和其他会话都能读表,但所有会话都不能写表。适用于只读操作(如批量查询)。
    • 表写锁(WRITE / 排他锁 X 锁):加锁后,只有自己能读写表,其他会话既不能读也不能写。适用于批量修改表数据。
    • 元数据锁(MDL):隐式加锁,无需手动干预。保护表结构(元数据)不被修改。DDL 操作会加 MDL 写锁,DML / 查询加 MDL 读锁。
    • 意向锁(Intention Locks):表级的"标记锁",由 InnoDB 自动管理。分为意向共享锁(IS)和意向排他锁(IX),用于表示"某个事务准备给表中的某些行加锁",避免行锁和表锁冲突。

3. 行级锁(Row-Level Lock)

  • 定义:锁定表中的单行数据,粒度最小,并发度最高,是 InnoDB 引擎特有的优势(MyISAM 不支持)。
  • 核心类型
    • 行共享锁(S 锁) :事务对行加 S 锁后,其他事务可加 S 锁(共享读),但不能加 X 锁(排他写)。通过 SELECT ... LOCK IN SHARE MODE 加锁。
    • 行排他锁(X 锁) :事务对行加 X 锁后,其他事务既不能加 S 锁也不能加 X 锁,保证修改/删除时数据独占。通过 SELECT ... FOR UPDATE 或普通的 UPDATE/DELETE/INSERT 语句自动加锁。
    • 间隙锁(Gap Lock):锁定索引记录之间的"间隙"(不包含记录本身),防止插入新数据,主要用来解决"幻读"问题。
    • 临键锁(Next-Key Lock):行锁 + 间隙锁的组合,锁定"记录 + 记录前的间隙"(左开右闭区间)。这是 InnoDB 在可重复读(RR)隔离级别下默认的行锁方式。
    • 插入意向锁(Insert Intention Lock):多个事务插入同一间隙时的"意向锁",互不阻塞,用于提升并发插入效率。
    • 自增锁(AUTO-INC Lock):针对自增列的表级锁,保证自增 ID 连续唯一。

二、按锁功能划分

1. 共享锁(Shared Lock, S 锁)

又称为读锁。当事务对数据加了 S 锁,其他事务可以再加 S 锁读数据,但不能加 X 锁修改数据。实现了"读读不阻塞,读写阻塞"。

2. 排他锁(Exclusive Lock, X 锁)

又称为写锁。当事务对数据加了 X 锁,其他事务不能加任何锁,既不能读也不能写。实现了"写写阻塞,读写阻塞"。

三、行级锁的算法实现与死锁

InnoDB 的行级锁是通过索引来实现的。如果查询语句没有使用索引,InnoDB 会退化为表级锁,锁住整个表。其核心算法包括:

  • 记录锁(Record Lock) :精准锁住某一条索引记录。例如 WHERE id = 1,只会锁住 id=1 的这一行。
  • 间隙锁(Gap Lock):锁住两个索引记录之间的间隙,不锁住记录本身,只防止其他事务在这个间隙里插入新数据。
  • 临键锁(Next-Key Lock) :InnoDB RR 级别下的默认行锁算法,临键锁 = 记录锁 + 间隙锁。例如表中有 id=1、5、10 三条记录,临键锁会把索引分成 (-∞,1]、(1,5]、(5,10]、(10,+∞] 四个区间,当你查询 WHERE id <= 5 时,InnoDB 会锁住 (-∞,1]、(1,5] 两个区间,彻底杜绝了其他事务插入符合条件的数据,从根本上解决了幻读问题。

死锁(Deadlock)与最佳实践

行级锁虽然提升了并发度,但也带来了死锁的风险。当两个或多个事务互相等待对方释放锁时,就会形成死锁。

  • 死锁示例:事务 A 先更新 id=1 再更新 id=2;事务 B 先更新 id=2 再更新 id=1。如果两者同时进行,就会互相等待对方释放锁,导致死锁。
  • 解决方案:按固定顺序访问资源。例如,在业务代码中,都按照 id 从小到大的顺序去更新数据。
  • 两阶段锁协议(2PL) :InnoDB 遵循此协议,锁的操作分为加锁阶段和解锁阶段。直到事务提交(COMMIT)或回滚(ROLLBACK)时,才会一次性释放所有在该事务中获取的锁。因此,为了减少锁冲突,应尽量将最可能引起冲突的写操作(如 SELECT ... FOR UPDATE)放在事务的后面执行,以缩短排他锁的持有时间。
相关推荐
一条泥憨鱼8 小时前
苍穹外卖【day7| 对购物车进行操作】
java·后端·mysql·苍穹外卖
小码农 - 初12 小时前
MySQL 增删改查
mysql
五阿哥永琪19 小时前
MySQL中操作json的函数!
数据库·mysql·json
来者皆善19 小时前
了解Mysql优化吗?如何优化索引?
数据库·mysql
Ai拆代码的曹操1 天前
MySQL GROUP BY 性能优化:执行计划分析 + 索引重构实战
mysql·性能优化·重构
安_1 天前
MySQL SQL 调优完整指南
数据库·sql·mysql
艾莉丝努力练剑1 天前
【MYSQL】MYSQL学习的一大重点:事务(上)- 原子性与持久性
android·数据库·学习·mysql·面试
全栈派森2 天前
告别大表膨胀!MySQL 冷热分离落地全过程(含 CDC 与单行锁抢占实战)
大数据·mysql
篮框坏了2 天前
大事务 DELETE 锁表排查:删 3344 万行,一个清理任务变成周期炸弹
mysql
LinuxGeek10242 天前
Kylin-Server-V11、openEuler-22.03和openEuler-24.03适配原生rpm的MySQL 8.4.11版本正式发布
大数据·mysql·kylin