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)放在事务的后面执行,以缩短排他锁的持有时间。
相关推荐
Irene19911 小时前
触发器(Trigger) 是数据库中一种特殊的存储程序,它会在指定的表上发生特定事件(如 INSERT、UPDATE、DELETE)时自动执行
mysql
唐青枫2 小时前
别再误会 SELECT 1:MySQL 常量查询与存在性判断实战
sql·mysql
野生技术架构师2 小时前
MySQL / PostgreSQL DDL 审核自动化:从人工 review 到 CI 拦截
mysql·postgresql·自动化
24白菜头2 小时前
MySQL学习笔记
数据库·笔记·学习·mysql
shizhan_cloud2 小时前
MySQL 备份与恢复
数据库·mysql
思麟呀2 小时前
MySQL的内置函数
数据库·mysql
熏鱼的小迷弟Liu2 小时前
【MySQL】MySQL中的MVCC是什么?它与隔离级别有什么关系?
mysql·mvcc
NineData2 小时前
还在轮询 MySQL 吗?用 NineData 把业务变更直接送进 Kafka
数据库·mysql·kafka·ninedata·数据复制·玖章算术·数据迁移工具
重生之小比特3 小时前
【MySQL 数据库】索引特性
数据库·mysql