引言
在数据分析 或业务查询中,我们经常需要 “按某个字段分组,并取每组中时间最新的记录”。例如:
按 data_time 分组,取每组 insert_time 最大的记录。按用户 ID 分组,取每个用户的最新订单。但 MySQL 的 GROUP BY 默认行为可能无法直接满足需求,尤其是当查询依赖 ORDER BY 时。本文将深入探讨 一种常见的 hack 方案(依赖 LIMIT),并分析其问题,最终给出 更高效、更标准的解决方案。
1. 常见问题:如何获取每个分组的最新记录?
假设我们有一个表 spider.nbs_data,结构如下:
CREATE TABLE spider.nbs_data ( id BIGINT PRIMARY KEY, index_id VARCHAR(20), data_time DATE, insert_time DATETIME, -- 其他字段... );
需求:查询 index_id = '0000120460' 且 data_time >= '2020-12-31' 的数据,按 data_time 分组,并取每组 insert_time 最大的记录。
2. 一种常见的 hack 方案:依赖 LIMIT 的 GROUP BY
2.1 原始 SQL
SELECT * FROM ( SELECT * FROM spider.nbs_data WHERE index_id = '0000120460' AND data_time >= '2020-12-31' ORDER BY `insert_time` DESC LIMIT 10000000000 -- 超大 LIMIT 值 ) t GROUP BY t.data_time -- 按 data_time 分组 ORDER BY `data_time`;
2.2 为什么它能工作?
内层查询:
先筛选符合条件的数据。按 insert_time DESC 排序,确保最新记录排在前面。LIMIT 10000000000 强制 MySQL 先执行 ORDER BY 再 GROUP BY,避免优化器打乱顺序。外层 GROUP BY:
MySQL 默认取每组的第一条记录(即 insert_time 最大的那条)。最终按 data_time 排序输出。2.3 问题与风险
依赖 MySQL 的 GROUP BY 行为:
如果 SELECT * 包含非分组列,可能触发 ONLY_FULL_GROUP_BY 错误(MySQL 5.7+ 默认启用)。不同 MySQL 版本或 SQL 模式可能导致结果不一致。性能问题:
LIMIT 10000000000 相当于 全表扫描,数据量大时性能极差。如果数据量超过 LIMIT 值,可能丢失数据。代码可读性差:
LIMIT 10000000000 是一个 hack 行为,容易让后续维护者困惑。
3. 更优方案:标准 SQL 实现
方案 1:JOIN + 子查询(兼容所有 MySQL 版本)
SELECT t1.* FROM spider.nbs_data t1 JOIN ( SELECT data_time, MAX(insert_time) AS max_insert_time FROM spider.nbs_data WHERE index_id = '0000120460' AND data_time >= '2020-12-31' GROUP BY data_time ) t2 ON t1.data_time = t2.data_time AND t1.insert_time = t2.max_insert_time WHERE t1.index_id = '0000120460' ORDER BY t1.data_time;
优点:
清晰、标准,不依赖 GROUP BY 行为。性能较好(子查询先聚合,再 JOIN)。兼容所有 MySQL 版本。方案 2:ROW_NUMBER()(MySQL 8.0+)
WITH ranked_data AS ( SELECT *, ROW_NUMBER() OVER (PARTITION BY data_time ORDER BY insert_time DESC) AS rn FROM spider.nbs_data WHERE index_id = '0000120460' AND data_time >= '2020-12-31' ) SELECT * FROM ranked_data WHERE rn = 1 ORDER BY data_time;
优点:
语法简洁,逻辑清晰。性能优秀(窗口函数优化较好)。适用于复杂分组排序场景。缺点:
仅 MySQL 8.0+ 支持。
4. 性能对比
LIMIT hack快速验证、临时查询差(全表扫描)所有版本JOIN + 子查询所有 MySQL 版本优所有版本ROW_NUMBER()MySQL 8.0+优8.0+
5. 总结
避免依赖LIMIT 的 hack 方案:虽然能工作,但性能差、可读性低、存在风险。推荐 JOIN + 子查询:兼容性好,性能稳定,适合大多数场景。MySQL 8.0+ 优先用 ROW_NUMBER():语法简洁,性能更优。
最终推荐 SQL:
-- MySQL 5.7 及以下版本 SELECT t1.* FROM spider.nbs_data t1 JOIN ( SELECT data_time, MAX(insert_time) AS max_insert_time FROM spider.nbs_data WHERE index_id = '0000120460' AND data_time >= '2020-12-31' GROUP BY data_time ) t2 ON t1.data_time = t2.data_time AND t1.insert_time = t2.max_insert_time WHERE t1.index_id = '0000120460' ORDER BY t1.data_time; -- MySQL 8.0+ WITH ranked_data AS ( SELECT *, ROW_NUMBER() OVER (PARTITION BY data_time ORDER BY insert_time DESC) AS rn FROM spider.nbs_data WHERE index_id = '0000120460' AND data_time >= '2020-12-31' ) SELECT * FROM ranked_data WHERE rn = 1 ORDER BY data_time;
结语
在 SQL 查询中,清晰、标准、高效 是最重要的原则。依赖 LIMIT 的 hack 方案虽然能解决一时之需,但长期来看,使用 JOIN 或窗口函数才是更可靠的选择。希望本文能帮助你优化查询,写出更健壮的 SQL
到此这篇关于MySQL查询优化之高效获取每个分组的最新记录的文章就介绍到这了,
