MySQL 5.7及更早版本禁止子查询中直接使用LIMIT,属解析器硬限制;正确做法是用相关子查询配合MAX()获取每组最大时间戳,再通过主键去重并建立复合索引(user_id, created_at)提升性能。

为什么直接用 ORDER BY ... LIMIT 1 在子查询里会报错
MySQL 5.7 及更早版本不支持在子查询中使用 LIMIT,错误信息通常是 This version of MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery'。即使在 MySQL 8.0+ 或 PostgreSQL 中允许,LIMIT 也无法表达“每组最新”这个语义——它只返回全局第一条,不是按 user_id 分组后的各自最新。
用相关子查询找每组最新记录的正确写法
核心思路是:对外层每一行,用子查询查出该组(如 user_id)中最大的时间戳,再让外层行的时间戳等于这个最大值。
- 必须用相关子查询(即子查询里引用外层表的列,如
t1.user_id = t2.user_id) - 子查询需用
MAX()或ORDER BY ... LIMIT 1(仅限支持版本),但前者兼容性更好 - 若存在多条同为最大时间的记录,会全部返回;如需唯一一条,得加额外去重逻辑(如取最小
id)
SELECT t1.* FROM orders t1 WHERE t1.created_at = ( SELECT MAX(t2.created_at) FROM orders t2 WHERE t2.user_id = t1.user_id );
当时间字段有重复时,如何确保只取一条
上面的写法在 created_at 重复时可能返回多行。要强制单条,可在子查询中嵌套比较主键:
- 先查出每个
user_id对应的最大created_at - 再查出该
created_at下最小(或最大)的id - 外层匹配这个
id
SELECT t1.*
FROM orders t1
WHERE t1.id = (
SELECT MIN(t2.id)
FROM orders t2
WHERE t2.user_id = t1.user_id
AND t2.created_at = (
SELECT MAX(t3.created_at)
FROM orders t3
WHERE t3.user_id = t1.user_id
)
);性能关键点:索引怎么建才不慢
相关子查询天然容易慢,因为对主表每行都执行一次子查询。没有索引时是全表扫描嵌套,数据量稍大就卡住。
- 复合索引必须包含
WHERE和MAX()涉及的字段,顺序很重要:先等值字段(如user_id),再范围/聚合字段(如created_at) - 推荐建:
INDEX (user_id, created_at)—— 这样子查询能走索引快速定位到某user_id的所有记录,并直接拿到最大值 - 如果还用到
id去重,可考虑INDEX (user_id, created_at, id)覆盖查询
没加对索引时,EXPLAIN 会显示 type: ALL 或 Using temporary; Using filesort;加上后应看到 type: ref 且 Extra 里没有临时表。

















