不能在ON中加LIMIT,因SQL标准不支持该语法且会报错;正确做法是用ROW_NUMBER()在JOIN前对右表分区排序并编号,再通过ON r.rn=1筛选,且PARTITION BY字段须与JOIN外键一致、ORDER BY决定“第一条”含义、rn=1必须写在ON而非WHERE中。

LEFT JOIN 后只取右表一条记录,为什么不能在 ON 里加 LIMIT
因为 LIMIT 不是 SQL 标准允许出现在 ON 子句里的语法。写成 LEFT JOIN b ON a.id = b.a_id LIMIT 1 会直接报错(如 MySQL 报 ERROR 1064)。更隐蔽的错误是把右表先 GROUP BY 再连——这会丢失左表无匹配时应保留的 NULL 行,实质变成带过滤的内连接。
用 ROW_NUMBER() 在 JOIN 前给右表编号
核心思路:别让 JOIN 自己“爆炸”,而是提前把右表按业务规则排好序、标好号,再精确挑第 1 条。
-
PARTITION BY字段必须和 JOIN 条件中的外键一致,比如order_id,否则分组错位导致取错行 -
ORDER BY created_at DESC决定哪条算“第一条”——要最新就DESC,要最早就ASC -
AND r.rn = 1必须写在ON子句里,写进WHERE会把右表无匹配的左表行过滤掉
示例:
SELECT l.*, r.sku, r.price FROM orders l LEFT JOIN ( SELECT *, ROW_NUMBER() OVER (PARTITION BY order_id ORDER BY updated_at DESC) AS rn FROM order_items ) r ON l.id = r.order_id AND r.rn = 1;
MySQL 5.7 或 SQLite 怎么办
这些引擎不支持窗口函数,只能退回到相关子查询或自连接,但代价明确:
- 每个左表行触发一次独立子查询,N 行左表 → N 次索引扫描,IOPS 压力陡增
- 无法在一个子查询里同时取多个字段(MySQL 5.7 不支持
SELECT (col1, col2)形式) - 自连接写法虽能避免重复扫描,但大表上易触发笛卡尔积,
(group_key, sort_col)联合索引必不可少
兼容写法(MySQL 5.7):
SELECT l.*, (SELECT sku FROM order_items r2 WHERE r2.order_id = l.id ORDER BY updated_at DESC LIMIT 1) AS sku, (SELECT price FROM order_items r2 WHERE r2.order_id = l.id ORDER BY updated_at DESC LIMIT 1) AS price FROM orders l;
最容易被忽略的索引陷阱
无论用哪种方案,性能瓶颈几乎总落在排序字段上。没索引时,ROW_NUMBER() 的 ORDER BY 或子查询的 ORDER BY ... LIMIT 1 都会全表扫描。
- 联合索引必须包含
PARTITION BY字段 +ORDER BY字段,顺序不能颠倒(如(order_id, updated_at)) - 如果业务要求“取创建时间最早的一条”,但
created_at有大量重复值,仅靠它排序可能无法稳定选中唯一行——建议补上主键(如id)做二级排序 - MySQL 中若用变量模拟
ROW_NUMBER(),在复杂查询或并行执行下行为不可靠,线上环境应避免

















