MySQL 到底有哪些锁?

前言

应用卡顿?数据库死锁?这些问题的背后,往往藏着 MySQL"锁"这个神秘角色。它既是维持数据秩序的交通警察,也可能是造成拥堵的固执保镖。让我来带你一文看懂全局锁、表级锁与行级锁,让你彻底告别并发烦恼。

全局锁

想象一下,你们公司要年底盘点,老板大手一挥:"都别动!所有货物都不准动,我要看看今年到底赚了多少钱!"

这时候,MySQL 里的 "全局锁" 就登场了。它最霸道,一旦你开启了 "全局锁",整个数据库就变成了"只读"模式

  • 作用场景: 主要就是为了"全库逻辑备份"。你想想,如果不把整个 "仓库" 都锁住,你这边刚数完A货架的货,那边销售又卖出去两个,这账不就乱了吗?所以,必须锁上 "仓库大门",暂停所有"写"操作(增、删、改),让你安安心心地把所有数据复制一份带走。
  • 如何使用: 开启全局锁非常简单,只需要一条命令就可以让 MySQL 变成只读模式。
    • 开启全局锁:FLUSH TABLES WITH READ LOCK
    • 释放锁:UNLOCK TABLES
      • Tips:当你开启全局锁的会话关闭之后也会自动将锁释放。

表级锁

在 MySQL 中表级锁分为:表锁元数据锁意向锁AUTO-INC 锁

表锁

MySQL 的表锁有两种,分别叫读锁 / 共享锁(Table Read Lock)、写锁 / 独占锁(Table Write Lock)。

  • 读锁 (Table Read Lock)
    • 想象一下:在一个餐厅中,服务员对店里的客人说:"大家可以进来参观和阅读菜单(Read ),但不许任何人点菜(Write )!"这个时候很多歌想要"参观" / "阅读"(Read )菜单都可以进入这个包厢;这个时候服务员自己也只能看(Read )不能点菜(Write ),任何想要"点菜"(Write )的客人都会被拦在门外,必须等门锁解除后才能"点菜"(Write)。
    • 这把锁也叫共享锁,当一个会话(Session)给一张表加上了读锁,这个会话自己只能读不能写其他会话也只能读不能写 ,大家都可以共享读取的权限,任何写入操作(INSERT, UPDATE, DELETE)都会被阻塞,直到锁被释放。
    • 如何使用
      • 开启读锁 / 共享锁:LOCK TABLES table_name READ
      • 释放锁:UNLOCK TABLES
  • 写锁(Table Write Lock)
    • 想象一下:在一个餐厅中,厨师对店里的所有人说:"我要修改菜单(Write )了,任何人都不许点菜(Write )!看一眼菜单(Read )都不行!"这个时候,只有这位厨师自己可以在包厢里随意查看修改菜单(Write、Read )。其他任何人,无论是想要看菜单(Read ),还是想要点菜(Write)的人统统都被拦在门外,只能在门口排队等着。
    • 这把锁也叫独占锁,当一个会话给一张表加上了写锁,这个会话自己既可以读也可以写其他任何会话的所有操作(读和写)都会被完全阻塞,直到锁被释放。
    • 如何使用
      • 开启写锁 / 独占锁:LOCK TABLES table_name WRITE
      • 释放锁:UNLOCK TABLES

元数据锁

MySQL 的元数据锁,是 MySQL 非常重要的一个锁机制,元数据指的是"数据的数据",在 MySQL 中指的是数据库对象的定义,比如:表结构、视图、触发器、索引、存储过程......等等。而元数据锁就是用来保护这些元数据的,确保当前线程在数据被访问的时候,不会被其他线程修改。

想象一下:当您在A餐桌点菜吃饭时(执行查询等事务 ),系统会自动给这张桌子加上一个"使用中"的标记,这就是 共享元数据锁 。这个标记不影响其他客人来A桌吃饭(多个查询可并行 ),但它唯一的作用就是告诉老板:桌子有人用,您暂时不能改造或丢掉它(执行ALTER/DROP TABLE等操作)。

但问题也随之而来:如果您这顿饭吃得特别久(长事务未结束),那么想改造桌子的老板就只能一直等待。更糟的是,所有想来A桌的新客人(新的查询请求),看到老板在等,也必须排队,最终导致整个餐厅门口大排长龙,谁也进不来。

MySQL 元数据锁住要目的是为了解决 DML(数据操作语言)和 DDL(数据定义语言)操作之间的冲突问题。当一个事务在使用某个表的元数据时,元数据锁会阻止其他会话对这个表的结构进行修改。MySQL 元数据锁主要分为两种类型:

  • 共享元数据库锁
    • 当执行 DML 操作时(如 SELECT, INSERT, UPDATE, DELETE)会对表加上共享锁。共享锁之间是兼容的,多个事务可以同时持有同一张表的共享 MDL,互不影响,保证了 DML 操作的并发性。
  • 排他元数据锁
    • 当执行 DDL 操作时(如 ALTER TABLE, DROP TABLE, TRUNCATE TABLE)会对表加上排他锁。排他锁与任何其他元数据锁(包括共享锁和排他锁)都是互斥的,这意味着,如果要对一个表执行 DDL,必须等到所有正在使用该表的事务(即使是只读查询)全部结束。反之,如果一个表上有 DDL 操作正在等待或执行,那么其他所有想访问该表的事务(包括 SELECT)都必须等待。

意向锁

MySQL的意向锁(Intention Lock)是一种由 InnoDB 引擎自动管理的 表级锁,但它本身不锁定任何具体的数据行。它的核心作用是一个"信号"或"意向声明",用来协调不同粒度的锁(表锁与行锁)之间可能发生的冲突。

意向锁的出现主要是为了解决性能问题。想象一下,当一个事务想锁定整张表时(如LOCK TABLES),如果没有意向锁,数据库就必须遍历表中的每一行去检查是否存在行锁,这对于大表来说是灾难性的。

有了意向锁,过程就变得高效:当一个事务要给某几行加行锁(如SELECT ... FOR UPDATE)时,它会先在表上加上一个意向排他锁。之后,当另一个事务想锁整张表时,只需检查表上是否存在意向锁,就能立刻知道内部有行锁,从而避免了全表扫描。

AUTO-INC 锁

MySQL的自增锁(AUTO-INC Lock)是一种特殊的表级锁,专用于保护AUTO_INCREMENT自增列,确保在多事务并发INSERT时,自增ID能够唯一、有序地分配,避免冲突。

它的具体行为由innodb_autoinc_lock_mode参数控制,直接影响数据库的并发性能。主要有三种模式:

  1. 传统模式(innodb_autoinc_lock_mode = 0) :在整个INSERT语句执行期间都持有表级的AUTO-INC锁,直到语句结束。这种方式最安全但并发性能最差。
  2. 连续模式(innodb_autoinc_lock_mode = 1) :这是一种混合模式。对于可以预知插入行数的简单INSERT,它使用一个轻量级的锁在内存中快速分配ID后就释放,不阻塞其他事务。但对于INSERT ... SELECT等无法预知行数的批量插入,仍会使用传统的表锁,以保证分配的ID块是连续的。
  3. 交错模式(innodb_autoinc_lock_mode = 2):完全放弃表级锁,全部使用轻量级锁。这种模式下并发性能最好,但代价是单条批量插入语句产生的自增ID可能不再连续。

行锁

在 MySQL 中行锁分为:记录锁(Record Lock)、间隙锁(Gap Lock)、临键锁 (Next-Key Lock)

记录锁

  • 记录锁(Record Lock)顾名思义,就是会锁定单条记录的锁
  • 记录锁有两种实现:共享锁(S 锁)和排他锁(X 锁)
  • 如何加锁
    • 共享锁:
      • 需要手动添加。
        • MySQL 8.0+ 推荐:SELECT ... FOR SHARE;
        • 旧版语法(仍可用):SELECT ... LOCK IN SHARE MODE;
    • 排他锁:
      • 自动加锁 :执行 INSERT, UPDATE, DELETE 操作时,InnoDB 会自动为涉及的行加上X锁。
      • 手动加锁 :执行 SELECT ... FOR UPDATE;

间隙锁

  • 间隙锁(Gap Lock )不锁定任何记录本身,它锁定的是索引记录之间的"间隙 ",其设计的核心目的是为了在 可重复读(Repeatable Read)隔离级别下,防止幻读 的发生。
  • 如何加锁
    • 使用范围查询 (如 BETWEEN, >, <)或者查询一个不存在的记录时,会触发间隙锁。

临键锁

  • 临键锁 (Next-Key Lock)记录锁 + 间隙锁 的组合,它会锁定一个索引记录本身,以及该记录之前的那个间隙。
  • 临键锁是 InnoDB 在可重复读(Repeatable Read)隔离级别下的默认锁定算法。它既能防止数据被修改,又能防止幻读。
  • 如何加锁
    • 表中有 id 为 10 和 20 的记录,SELECT * FROM users WHERE id < 15 FOR UPDATE; 会锁定 id=10 的记录(记录锁)以及 id=10 之前的间隙 (-∞, 10](间隙锁),组合成一个临键锁。这样,其他事务既不能修改 id=10 的记录,也不能在 (-∞, 10) 这个区间插入新数据。
相关推荐
爷_2 小时前
字节跳动震撼开源Coze平台!手把手教你本地搭建AI智能体开发环境
前端·人工智能·后端
不过普通话一乙不改名5 小时前
第一章:Go语言基础入门之函数
开发语言·后端·golang
豌豆花下猫6 小时前
Python 潮流周刊#112:欢迎 AI 时代的编程新人
后端·python·ai
Electrolux6 小时前
你敢信,不会点算法没准你赛尔号都玩不明白
前端·后端·算法
whhhhhhhhhw7 小时前
Go语言-fmt包中Print、Println与Printf的区别
开发语言·后端·golang
ん贤7 小时前
Zap日志库指南
后端·go
Spliceㅤ7 小时前
Spring框架
java·服务器·后端·spring·servlet·java-ee·tomcat
IguoChan8 小时前
10. Redis Operator (3) —— 监控配置
后端
Micro麦可乐9 小时前
前端与 Spring Boot 后端无感 Token 刷新 - 从原理到全栈实践
前端·spring boot·后端·jwt·refresh token·无感token刷新
方块海绵9 小时前
浅析 MongoDB
后端