重复记录不是bug,而是left join匹配多行时的正常结果;应根据语义选distinct(仅全字段重复且无明细)、group by(需聚合统计)或子查询/窗口函数(需单条关联记录)。

直接说结论:重复记录不是 bug,是 JOIN 的正常行为;解决它不能靠“删掉重复行”,而要根据你要的数据语义,选对方法——DISTINCT、GROUP BY 或子查询去重,三者适用场景完全不同。
为什么 LEFT JOIN 一查就多出几倍行数
核心原因是:左表一行匹配右表多行时,数据库会为每组匹配生成一条结果。比如 users 表中 id=1 的用户有 5 条订单,LEFT JOIN orders 后,这个用户信息就会出现 5 次。
这不是语法写错了,也不是索引没建好,而是关系代数里“连接”的定义决定的。你看到的“重复”,其实是数据关联关系的真实投影。
常见误判现象:
- 用
COUNT(*)统计用户数,结果远大于SELECT COUNT(*) FROM users - 用
SUM(amount)算总销售额,数值翻倍甚至更高 - 应用层拿到 JSON 后发现同一用户对象被序列化了 N 次
DISTINCT 只能去“全字段重复”,别乱用
DISTINCT 是最易上手的方案,但它只对 SELECT 列完全一致的行生效。一旦你选了带订单字段(如 o.id、o.amount)的字段,DISTINCT 就失效了——因为每条订单本身就不一样。
正确用法示例(仅需用户列表,不关心具体订单):
SELECT DISTINCT u.id, u.name, u.email FROM users u LEFT JOIN orders o ON u.id = o.user_id;
错误用法(想同时展示订单又用 DISTINCT):
SELECT DISTINCT u.id, u.name, o.id, o.amount FROM users u LEFT JOIN orders o ON u.id = o.user_id;
这不会减少行数,因为 o.id 天然不同。此时 DISTINCT 形同虚设。
性能提示:DISTINCT 本质是排序去重,大数据量时可能触发临时表和 filesort,比 GROUP BY 更重。
GROUP BY 是语义最清晰的解法
当你真正想表达的是“每个用户 + 他的订单汇总信息”,那就该用 GROUP BY u.id,而不是强行拼宽表。
典型场景:
- 查每个用户的订单总数:
COUNT(o.id) - 查每个用户的总消费:
SUM(o.amount) - 查每个用户的最新订单时间:
MAX(o.created_at)
示例:
SELECT u.id, u.name, COUNT(o.id) AS order_count, COALESCE(SUM(o.amount), 0) AS total_amount FROM users u LEFT JOIN orders o ON u.id = o.user_id GROUP BY u.id, u.name;
注意:MySQL 5.7+ 默认开启 sql_mode=only_full_group_by,所以 GROUP BY 后所有非聚合字段都必须出现在 GROUP BY 子句中,否则报错 Expression #2 of SELECT list is not in GROUP BY clause。
需要单条关联记录时,用子查询或窗口函数
如果业务要求是“每个用户只显示一条最新订单”(不是汇总,也不是全部),DISTINCT 和 GROUP BY 都不合适——前者无法控制取哪条,后者只能聚合不能挑行。
推荐两种写法:
① 关联子查询(兼容 MySQL 5.6+):
SELECT u.*,
(SELECT o.amount FROM orders o WHERE o.user_id = u.id ORDER BY o.created_at DESC LIMIT 1) AS latest_amount
FROM users u;
② 窗口函数(MySQL 8.0+):
SELECT id, name, amount AS latest_amount
FROM (
SELECT u.id, u.name, o.amount,
ROW_NUMBER() OVER (PARTITION BY u.id ORDER BY o.created_at DESC) AS rn
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
) t
WHERE rn = 1;
关键点:不要在主 JOIN 中试图“限制右表只取一条”,那是逻辑错误——JOIN 是集合操作,没有“取前 N 条”的语义。必须把筛选逻辑放到子查询或窗口函数里做。
最容易被忽略的细节是:去重方式必须和业务目标严格对齐。统计用 GROUP BY,列表去重用 DISTINCT(且只用于无明细字段的场景),挑特定记录用子查询或窗口函数。混用或硬套,只会让问题更难排查。











