MySQL查询行无锁实现及表锁定/解锁方法咨询
Hey there! Let's break down your questions one by one since you're getting up to speed with MySQL locking behavior.
一、表级锁定与解锁的基本操作
MySQL支持手动对表加锁,不过不同存储引擎的锁机制差异很大,先讲通用的表锁操作:
1. 加表锁
- 共享读锁(READ):多个会话可以同时加读锁,允许其他会话读,但禁止写操作:
LOCK TABLES your_table_name READ; - 排他写锁(WRITE):只有当前会话能读写该表,其他会话的读写都会阻塞:
LOCK TABLES your_table_name WRITE;
2. 解锁
执行以下语句手动释放所有当前会话持有的表锁:
UNLOCK TABLES;
另外,当你的MySQL会话断开时,所有持有的表锁会自动释放。
注意:InnoDB引擎默认是行级锁为主,只有在特定场景下才会升级为表锁;而MyISAM引擎则完全依赖表级锁,这一点需要根据你使用的存储引擎区分对待。
二、关于MySQL中没有NOLOCK关键字的问题
你说得没错——MySQL没有NOLOCK这个官方关键字,这是SQL Server特有的查询提示。为什么你执行带NOLOCK的查询没报错?因为MySQL会把它当作无效的语法忽略(如果是放在注释里则直接被跳过),不会触发报错,但也不会产生任何无锁的效果。
比如你写SELECT * FROM your_table WITH (NOLOCK);,MySQL会识别为语法错误吗?其实要看具体写法,如果你把NOLOCK放在注释里(比如SELECT * FROM your_table /* NOLOCK */),那自然不会报错,但完全没作用。总之,别指望NOLOCK在MySQL里实现无锁读。
三、实现查询行无锁的标准方式
在InnoDB引擎(现在MySQL的默认引擎)中,无锁读的核心是MVCC(多版本并发控制),这也是MySQL实现类似SQL Server NOLOCK效果的标准方式,具体分几种场景:
1. 普通SELECT的默认行为(快照读)
在默认的READ COMMITTED或REPEATABLE READ隔离级别下,不加任何锁提示的SELECT语句都是快照读:
- 它不会对目标行加任何锁,也不会被其他会话的排他锁阻塞
- 读取的是数据的一致性快照(不是最新的实时数据,但保证事务一致性)
- 完全不影响其他会话的写操作
这就是最常用的无锁读方式,比如:
SELECT * FROM your_table WHERE id = 1;
2. 强制无锁读(针对特殊隔离级别)
如果你的会话使用的是SERIALIZABLE隔离级别(最高隔离级别),普通SELECT会变成“当前读”,自动加共享行锁。这时候要实现无锁读,可以临时切换隔离级别:
-- 临时设置当前会话的隔离级别为读提交 SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; -- 执行无锁查询 SELECT * FROM your_table WHERE ...; -- 可以再切换回原来的隔离级别(如果需要) SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
3. 跳过被锁行(可选场景)
如果你希望查询时直接跳过被其他会话锁定的行(而不是等待锁释放),可以使用MySQL 8.0+支持的SKIP LOCKED提示:
SELECT * FROM your_table WHERE id IN (1,2,3) FOR UPDATE SKIP LOCKED;
注意:这个是跳过锁行,不是无锁读,它本身是加排他锁的,只是跳过已被锁的行,适合特定的并发场景(比如任务队列)。
4. MyISAM引擎的无锁读(不推荐)
如果你的表用的是MyISAM引擎(现在很少用了),它默认是表级读锁,所有SELECT都会阻塞写操作。要实现无锁读,只能切换到READ UNCOMMITTED隔离级别:
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SELECT * FROM your_table WHERE ...;
但这种方式会读到脏数据(未提交的修改),所以一般不推荐,建议换成InnoDB引擎。
内容的提问来源于stack exchange,提问作者Francesco Rizzi




