前言
MySQL锁体系里面,表级锁分为表读锁(S,共享表锁) 和表写锁(X,排他表锁) 。
很多人会混淆:行级S共享锁 LOCK IN SHARE MODE 和表级S读锁。二者完全不是一个东西:
- 行S锁:InnoDB行锁,锁住某几行数据;
- 表S读锁:
LOCK TABLES xxx READ,锁住整张数据表,属于表粒度锁,MyISAM原生大量使用,InnoDB也支持。
注意:
LOCK TABLES是MySQL原生表锁语法,InnoDB虽然兼容,但是会破坏InnoDB事务机制,生产尽量避免手动使用。
一、什么是表读锁 S(Shared Table Read Lock)
表读锁,共享锁(Shared Lock),简称S表锁。
语法:
sql
LOCK TABLES `t_user` READ;
给整张 t_user 表加上表级共享读锁。
核心特性:共享,多个会话可以同时加S读锁。
加锁成功之后权限规则
- 当前持有锁的会话
- ✅可以执行普通SELECT查询本表;
- ❌不能对本表做 INSERT / UPDATE / DELETE 写入;
- ❌不能执行 DDL(alter、drop、truncate);
- ✅可以访问其他没有被锁定的表。
- 其他会话(别的连接)
- ✅可以正常SELECT读取这张表;
- ❌所有写入操作 insert/update/delete 会被阻塞排队等待;
- ❌DDL语句同样被阻塞;
- ✅其他未锁定的表不受任何影响。
释放锁两种方式:
sql
-- 手动释放当前会话所有表锁
UNLOCK TABLES;
-- 会话断开,自动释放所有表锁
重点:
LOCK TABLES READ,锁只作用于指定的这一张(或多张)表,不会影响数据库中其余表,这点和全局锁FTWRL有本质区别。
二、锁兼容性矩阵(表级别)
表级S读锁、X写锁之间的兼容关系:
| 已经持有 | 请求S表读锁 | 请求X表写锁 |
|---|---|---|
| S表读锁(读锁) | ✅兼容 | ❌互斥阻塞 |
| X表写锁(写锁) | ❌互斥阻塞 | ❌互斥阻塞 |
解读:
- 多个会话可以同时给同一张表加 S读锁,读和读互相兼容;
- 只要存在任意一个S读锁,所有会话想要加X写锁全部阻塞;
- 只要存在X写锁,任何人不能再加S读锁。
通俗理解:很多人可以一起"读上锁",只要有一个读锁在,任何人都不能写。
三、MyISAM下的表S锁行为
MyISAM引擎不支持事务,自动使用表锁。
当MyISAM执行查询SELECT,会自动拿S表读锁;执行DML写入自动拿X表写锁。
- MyISAM:select自动S表锁,多个select并发没问题;
- 一旦有写入,X写锁到来,所有后续select、写入全部等待。
所以MyISAM读多写少还可以;一旦写请求多,全部阻塞,并发性能很差。
现在业务基本淘汰MyISAM。
四、InnoDB 使用 LOCK TABLES READ 巨大坑点
⚠️InnoDB是支持
LOCK TABLES语法,但是会绕过InnoDB行锁、破坏事务,很多人踩大坑。
坑1:LOCK TABLES 会提交当前事务
执行 LOCK TABLES t READ; 的瞬间,会隐式提交当前正在运行的事务。事务还没做完就被强制commit,数据一致性直接破坏。
sql
BEGIN;
UPDATE t_user SET name='A' WHERE id=1;
-- 执行这一句,上面的事务直接被隐式提交!!
LOCK TABLES t_user READ;
不要在InnoDB事务中途使用LOCK TABLES,事务直接被意外提交。
坑2:LOCK TABLES之后,不能感知MVCC,也不会触发行锁
当执行 LOCK TABLES t READ,InnoDB不再使用行锁,直接整张表锁住。
即使你只想要锁定部分行,也会锁全表,失去InnoDB行锁优势。
坑3:区分【表S读锁】和【行S共享锁 LOCK IN SHARE MODE】
这两个非常容易混淆,完全两码事。
1)表级S读锁
sql
LOCK TABLES t_user READ;
锁整张表,禁止所有人写入,破坏InnoDB事务。
2)行级S共享锁(InnoDB行锁)
sql
SELECT * FROM t_user WHERE id=1 LOCK IN SHARE MODE;
只锁住id=1这一行记录,其他行不受影响,属于行锁,事务内生效,不会提交事务。
| 对比项 | 表S读锁 LOCK TABLES READ | 行S锁 LOCK IN SHARE MODE |
|---|---|---|
| 锁粒度 | 整张数据表 | 匹配索引的行记录 |
| 存储引擎 | MyISAM、InnoDB都支持 | 仅InnoDB |
| 事务影响 | 执行即隐式提交事务 | 事务内使用,不自动提交 |
| 锁范围 | 全表,所有行无法写入 | 仅命中行,其余行正常读写 |
绝大多数并发业务,我们需要的是行S锁,而不是表S读锁。
五、实操演示,复现表读锁阻塞现象
准备两个会话 Session A、Session B。
步骤1:Session A 加表读锁
sql
LOCK TABLES t_user READ;
A会话可以正常select本表;A会话不能写本表。
步骤2:Session B 查询本表
sql
SELECT * FROM t_user;
✅正常执行,S读锁之间兼容。
步骤3:Session B 执行写入
sql
UPDATE t_user SET name='test' WHERE id=1;
❌语句被阻塞,处于等待锁状态。
show processlist看到状态:Waiting for table lock。
步骤4:Session A释放锁
sql
UNLOCK TABLES;
释放之后,Session B的update立刻执行。
现象总结:只要表存在任意S读锁,全部DML写入阻塞。
六、表读锁S 和 全局锁 FTWRL 的区别
很多人把二者混淆:
LOCK TABLES t READ:单张表加S读锁,其他库其他表完全不受影响。FLUSH TABLES WITH READ LOCKFTWRL全局锁:实例所有数据库全部表统一加上读锁,范围是整个MySQL实例。
七、什么场景才适合手动使用 LOCK TABLES READ
- 遗留MyISAM表,做简单的只读导出,临时锁住单张表防止变更;
- 极少数运维场景,需要冻结某一张表写入,其他表继续业务;
❗InnoDB业务系统,尽量不要手动写
LOCK TABLES READ。InnoDB想要"读的时候阻止别人修改",优先使用事务 +
LOCK IN SHARE MODE行锁,而不是表S锁。
八、排查表锁等待
sql
show processlist;
状态出现 Waiting for table lock,代表线程正在等待表级锁(S/X)。
注意区分:
Waiting for table lock:表锁;Waiting for X lock:InnoDB行锁等待。
找到持有锁的会话,执行 kill 会话id,会话断开,表锁自动释放。
九、开发避坑规范
- InnoDB业务,禁止手动使用
LOCK TABLES xxx READ;会隐式提交事务,锁全表,抛弃行锁能力。 - 如果想要"读取同时防止别人修改该行",使用行共享锁:
select ... lock in share mode,放在事务中。 - MyISAM引擎才会大量出现表S锁;新项目不要使用MyISAM。
LOCK TABLES READ只会锁定指定表,不等于全局锁FTWRL。- 用完表锁必须执行
UNLOCK TABLES,或者关闭会话,否则写入一直阻塞。 - 不要混淆表S读锁和行S共享锁,二者粒度、行为完全不一样。
十、常见问题问答
Q1:加了表READ锁之后,加锁的会话自己能不能写这张表?
A:不能。哪怕是自己,也不能insert/update,只能select。
Q2:多个会话同时执行 LOCK TABLES t READ,会阻塞吗?
A:不会,S读锁共享,多个会话可以同时持有。只要有一个S锁,所有写操作就卡住。
Q3:BEGIN事务里面执行LOCK TABLES READ,事务还生效吗?
A:不生效。LOCK TABLES会隐式commit当前事务。
Q4:InnoDB不手动执行LOCK TABLES,会不会自动产生表S读锁?
A:正常DML、SELECT不会自动产生表S锁;只有DDL、部分特殊操作会触发表级锁逻辑。
总结
LOCK TABLES table READ是表级S共享读锁,粒度整张表,多个会话可同时加读锁;读与读兼容,读与写互斥。- 持有S表读锁期间:所有会话都无法执行本表DML/DDL写入;仅SELECT查询放行。
- 表S读锁主要服务MyISAM;InnoDB兼容该语法,但会隐式提交事务,破坏行锁与MVCC,业务尽量避开。
- 区分开表S读锁 和 行S锁 LOCK IN SHARE MODE,前者锁整张表,后者只锁行。
- 释放:
UNLOCK TABLES或者断开会话。 - 等待状态
Waiting for table lock代表在等待表锁,kill持有锁的会话快速恢复业务。
核心一句话:InnoDB环境,尽量不要碰手动表锁,优先用事务+行锁解决并发问题。