特色栏目

ASP源码

PHP源码

.NET源码

JSP源码

游戏频道
专题合集
关闭菜单
首页> 其他> 为什么你的 SELECT 有时会阻塞?

为什么你的 SELECT 有时会阻塞?

时间:2026-03-20 18:11:01 作者:互联网

一、基本概念:MySQL 的两种“读”法

在 Mysql 中,并非所有 SELECT 都一样。根据是否加锁、是否读最新数据,分为两类:

快照读(Snapshot Read)

  SELECT * FROM orders WHERE user_id = 1001;

当前读(Current Read)

  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;


二、使用场景:什么时候该用哪种读?

场景 1:只读查询 → 用快照读

场景 2:先查后改(Check-Then-Act)→ 必须用当前读!

// 伪代码:错误示范(快照读)
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;
-- 再判断并更新

场景 3:防止幻读(RR 级别下)


三、避坑指南

问题 1:FOR UPDATE 导致大量阻塞甚至死锁

  SELECT * FROM orders WHERE create_time > '2024-01-01' FOR UPDATE; -- create_time 无索引

→ 锁住整个表,所有 INSERT 被阻塞! 解决方案


问题 2:快照读 + 当前读混合,误判“幻读”

  -- 事务内
  SELECT COUNT(*) FROM t WHERE id > 10;               -- 快照读,返回 0
  SELECT COUNT(*) FROM t WHERE id > 10 FOR UPDATE;   -- 当前读,返回 1!

解决方案


问题 3:RC 级别下 FOR UPDATE 无法防止幻读

解决方案


快照读和当前读,是 InnoDB 实现高性能与一致性平衡的双翼。

但在实际开发中,最大的风险不是技术本身,而是“不知道自己在用哪种读”

下次当你写下 SELECT 时,不妨多想一步 “我需要的是历史快照,还是此刻的真实?”



相关文章

热门文章

猜你喜欢

返回顶部