如何锁定预订数据表?锁表后仍可读取的问题排查
为什么
LOCK TABLE booking READ后其他用户仍能读取表? 你遇到的问题核心是对MySQL表级锁中**READ锁(共享读锁)**的特性理解有误,下面一步步拆解清楚:
1. READ锁的本质是「共享读,阻塞写」
当你执行LOCK TABLE booking READ时,这个锁的作用并不是禁止其他用户读取表,而是:
- 持有该锁的会话只能对表执行读操作(如果尝试写会直接报错,你提到的插入操作其实应该无法执行,除非你中途解锁了?可能实际操作中存在误操作,但先回到锁的核心特性)
- 其他所有会话依然可以正常读取该表,完全不受阻塞
- 只有当其他会话尝试对该表执行**写操作(INSERT/UPDATE/DELETE)**时,才会被卡住,直到你释放READ锁
这就是为什么终端2的user2能顺利执行SELECT * from booking——共享读锁本来就允许并发读,它的设计目的是阻止写操作干扰读,而不是限制读本身。
2. 解决重复预订的正确锁方案
你的需求是避免用户重复预订同一时段,本质是要在自己校验、插入的过程中,阻止其他用户写入重叠的预订数据,这时候得用排他锁而非共享读锁:
方案1:表级WRITE锁(不推荐,并发极差)
如果一定要用表级锁,应该执行:
LOCK TABLE booking WRITE;
此时:
- 持有锁的会话可以对表执行任意读写操作
- 其他所有会话的读和写操作都会被阻塞,直到你执行
UNLOCK TABLES;释放锁
但这种方式会彻底卡死其他用户的所有操作,并发性能拉胯,完全不适合实际业务场景。
方案2:行级排他锁(推荐,并发友好)
更合理的做法是用行级锁,通过带FOR UPDATE的查询锁定可能重叠的预订时段,流程如下:
-- 开启事务 START TRANSACTION; -- 查询是否存在重叠的预订,同时给匹配行加排他锁 SELECT * FROM booking WHERE from_datetime < '2019-01-01 14:00:00' AND to_datetime > '2019-01-01 12:00:00' FOR UPDATE; -- 如果上面的查询无结果,说明没有重叠,插入新预订 INSERT INTO booking (from_datetime, to_datetime) VALUES ('2019-01-01 12:00:00', '2019-01-01 14:00:00'); -- 提交事务,自动释放锁 COMMIT;
这种方式只会锁定与当前预订时段重叠的行(如果有的话),其他不相关的预订操作依然能正常执行,并发性能好很多。另外,给from_datetime和to_datetime创建联合索引,还能让FOR UPDATE的查询更快锁定目标行,避免意外的全表锁。
内容的提问来源于stack exchange,提问作者ruyawo
相关产品推荐
相关产品推荐

