先发制人2026
72.36M · 2026-03-22
在 Mysql 中,并非所有 SELECT 都一样。根据是否加锁、是否读最新数据,分为两类:
SELECT * FROM orders WHERE user_id = 1001;
SELECT * FROM orders WHERE user_id = 1001 FOR UPDATE; -- 排他锁
SELECT * FROM orders WHERE user_id = 1001 LOCK IN SHARE MODE; -- 共享锁(MySQL 8.0+ 可用 FOR SHARE)
UPDATE orders SET status = 'paid' WHERE id = 123;
DELETE FROM orders WHERE id = 123;
// 伪代码:错误示范(快照读)
Order order = select("SELECT * FROM orders WHERE id = 123"); // 快照读
if (order.status == "unpaid") {
update("UPDATE orders SET status = 'paid' WHERE id = 123");
}
→ 问题:两个线程同时执行,都看到 status=unpaid,导致重复支付!
正确做法(当前读):
-- 加锁读取最新状态(仅用来体现当前读的作用,高并发场景下不建议使用 FOR UPDATE)
SELECT * FROM orders WHERE id = 123 FOR UPDATE;
-- 再判断并更新
SELECT ... FOR UPDATE 触发 Next-Key Lock(记录 + 间隙锁);FOR UPDATE 导致大量阻塞甚至死锁 SELECT * FROM orders WHERE create_time > '2024-01-01' FOR UPDATE; -- create_time 无索引
→ 锁住整个表,所有 INSERT 被阻塞! 解决方案:
WHERE 条件命中索引; -- 事务内
SELECT COUNT(*) FROM t WHERE id > 10; -- 快照读,返回 0
SELECT COUNT(*) FROM t WHERE id > 10 FOR UPDATE; -- 当前读,返回 1!
解决方案:
FOR UPDATE 无法防止幻读FOR UPDATE,别人仍可插入新数据;解决方案:
Duplicate entry);快照读和当前读,是 InnoDB 实现高性能与一致性平衡的双翼。
但在实际开发中,最大的风险不是技术本身,而是“不知道自己在用哪种读”。
下次当你写下 SELECT 时,不妨多想一步 “我需要的是历史快照,还是此刻的真实?”