千万级数据下join必然卡死,需双向索引、避免on中函数、拆分多表连接并验证执行计划。

JOIN 操作在千万级数据下触发全表扫描,不是“可能慢”,而是“必然卡死”——只要关联字段没索引、ON 里用了函数、或驱动表选错,查询就会退化成逐行比对。
JOIN字段必须双向建索引,不能只建一边
很多人给左表加了索引就以为万事大吉,结果右表没索引,数据库照样对右表全表扫描。MySQL/PostgreSQL 的 JOIN 是双向匹配过程,两边都得能快速定位。
-
user_id是orders表和users表的关联字段?那orders.user_id和users.id都要建索引 - 优先用主键或唯一索引;复合索引中,等值条件字段(如
status = 'paid')必须放最左,范围条件(如created_at > '2025-01-01')放后面 - 别信“类型一样就能走索引”——
INT列和传入的VARCHAR字符串比较,会触发隐式转换,索引直接失效
EXPLAIN 看懂 type 和 rows,别只盯 key
EXPLAIN 输出里,type 列是第一道生死线:ALL 或 index 就等于全表扫描;ref 或 eq_ref 才算走对了索引。但光看 key 是否非空没用,得结合 rows 判断实际扫描量。
一款AI视频创作工具,主要用于蛙蛙写作辅助AI写文,帮助获取创意灵感,提供拆书、小说转剧本、视频生成等功能,是一款功能全面的AI智能写作工具,适合需要提升相关任务效率的用户。
-
rows值不是物理行数,而是优化器估算的“需要检查的行数”,它严重依赖统计信息是否新鲜 - MySQL 要定期跑
ANALYZE TABLE orders,PostgreSQL 要VACUUM ANALYZE orders,否则rows失真,优化器会误判驱动表 - 如果
rows显示驱动表有 80 万,被驱动表才 5000,说明顺序反了——该把过滤后的orders当驱动表,而不是硬塞users在左边
ON 子句里别写函数,宁可冗余字段
ON DATE(o.created_at) = u.join_date 这种写法,哪怕 created_at 有索引也白搭。B+ 树索引存的是原始值,函数运算后无法利用有序结构做二分查找。
- 改成
ON o.created_date = u.join_date,其中created_date是从created_at提取的DATE值,单独存并建索引 - MySQL 8.0+ 支持函数索引,如
CREATE INDEX idx_created_date ON orders ((DATE(created_at))),但稳定性不如冗余字段,上线前务必压测 - 同理,
UPPER(name)、SUBSTRING(email, 1, 5)、o.amount * 100全部禁止出现在ON或WHERE左侧
三张以上JOIN必须拆,别信优化器自动优化
超过 3 张表连查,优化器大概率选错执行顺序,尤其当某张表带高选择性 WHERE 条件时。它不会主动把“先过滤再连接”当成默认策略。
- 把
SELECT * FROM a JOIN b ON ... JOIN c ON ... JOIN d ON ... WHERE a.status = 'done' AND a.ts > '2025-06-01'拆成:先SELECT id, user_id FROM a WHERE status = 'done' AND ts > '2025-06-01'插临时表,再跟b、c、d依次JOIN - 临时表建议加主键或唯一索引(比如
id),避免后续连接又扫一遍 -
STRAIGHT_JOIN可强制顺序,但仅限调试确认问题时用;生产环境长期依赖它,等于放弃优化器演进能力
真正卡住人的,从来不是“要不要建索引”,而是建在哪、怎么验证、以及什么时候该放弃单条 SQL 改用应用层分步处理——千万级数据下,一次 JOIN 承担太多责任,本身就是设计风险点。










