searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

详细分析Mysql去重的几种方法(附Demo)-天翼云

2026-07-08 14:58:44
1
0

一、为什么去重如此重要?

在深入技术细节之前,先明确去重的价值:

维度 影响
数据一致性 重复数据会导致统计结果失真,比如用户数虚高、订单金额重复计算
存储优化 冗余记录占用磁盘空间,拖慢查询速度
分析准确性 BI报表、数据看板若基于重复数据,结论必然偏离事实
业务逻辑 唯一性约束被破坏,可能引发业务异常(如重复扣款)

所以,去重不是"锦上添花",而是数据治理的基本功


二、MySQL去重的六大核心方法

方法一:DISTINCT —— 最简洁的去重利器

DISTINCT是MySQL中最直观的去重关键字,直接作用于查询结果集,剔除所有重复行。

基础语法

sql
SELECT DISTINCT column_name FROM table_name;

多列组合去重

sql
SELECT DISTINCT user_name, email, address FROM t_user;

核心要点

  • DISTINCT必须放在SELECT关键字之后、所有字段之前
  • 它作用于所有选中字段的组合,只有当所有字段值完全相同时才会被去重
  • 适用于单列或少量字段的简单去重场景

性能表现: 在有索引的情况下,当结果集远小于表总量时,DISTINCT性能最优;但在大表无索引场景下,可能触发全表扫描,效率急剧下降。


方法二:GROUP BY —— 去重与聚合的双刃剑

GROUP BY通过分组实现去重,同时天然支持聚合函数,是分析型查询的首选。

基础语法

sql
SELECT user_name, email, address 
FROM t_user 
GROUP BY user_name, email, address;

配合聚合函数的进阶用法

sql
-- 查询每个部门的最高薪资
SELECT dept, MAX(salary) AS maximum 
FROM staff 
GROUP BY dept;

-- 查询雇员数超过2人的部门
SELECT dept, MAX(salary) AS maximum, MIN(salary) AS minimum 
FROM staff 
GROUP BY dept 
HAVING COUNT(*) > 2;

GROUP BY vs DISTINCT 的关键区别

对比项 DISTINCT GROUP BY
语法复杂度 极简 中等
聚合能力 不支持 天然支持
返回非分组字段 有限制 需配合聚合函数
执行计划 某些场景与GROUP BY相同 某些场景与DISTINCT相同

性能结论: 有索引时,GROUP BY在结果集较大的场景下比DISTINCT性能更好;无索引时两者表现接近。


方法三:ROW_NUMBER() 窗口函数 —— 去重保留指定记录的终极方案

这是MySQL 8.0+版本引入的杀手级特性。当你需要"去重并保留最新/最早/最大/最小的那条记录"时,窗口函数是最佳选择。

核心Demo:保留每个用户的最新登录记录

sql
WITH ranked AS (
    SELECT 
        user_name, 
        email, 
        last_login,
        ROW_NUMBER() OVER (PARTITION BY user_name ORDER BY last_login DESC) AS rn
    FROM t_user
)
SELECT user_name, email, last_login
FROM ranked
WHERE rn = 1;

语法拆解

  • PARTITION BY user_name:按用户名分组
  • ORDER BY last_login DESC:组内按登录时间降序排列
  • ROW_NUMBER():为每组内的行分配唯一序号
  • WHERE rn = 1:只取每组的第一条(即最新记录)

低版本MySQL的替代方案(MySQL 5.7及以下)

sql
SELECT user_name, email, address
FROM (
    SELECT b.*, 
           @rownum := @rownum + 1,
           IF(@pdept = b.user_name, @rank := @rank + 1, @rank := 1) AS rank,
           @pdept := b.user_name
    FROM (SELECT * FROM t_user ORDER BY user_name, last_login DESC) b,
         (SELECT @rownum := 0, @pdept := NULL, @rank := 0) c
) t
WHERE rank = 1;

这种方式利用用户变量模拟ROW_NUMBER(),虽然能用,但可读性和可维护性远不如窗口函数。


方法四:临时表法 —— 大数据量去重的安全之选

当需要真正删除表中的重复数据(而非仅查询去重)时,临时表法是最稳妥的方案。

完整操作流程

sql
-- 步骤1:创建新表,插入去重数据
CREATE TABLE t_user_new LIKE t_user;

INSERT INTO t_user_new 
SELECT * FROM t_user GROUP BY user_name, email;

-- 步骤2:验证数据
SELECT COUNT(*) FROM t_user_new;

-- 步骤3:替换原表
RENAME TABLE t_user TO t_user_backup, t_user_new TO t_user;

-- 步骤4:确认无误后删除备份
DROP TABLE t_user_backup;

优势: 不锁原表、不影响线上业务、可回滚,是生产环境大批量去重的首选方案。


方法五:DELETE + 子查询 —— 精准删除重复项

如果只想删除重复数据、保留一条(比如保留ID最小的那条):

sql
DELETE FROM t_user 
WHERE id NOT IN (
    SELECT min_id FROM (
        SELECT MIN(id) AS min_id 
        FROM t_user 
        GROUP BY user_name, email
    ) AS temp
);

注意: MySQL不允许在子查询中直接修改同一张表,所以必须再套一层SELECT,这是一个经典的"防坑"写法。


方法六:UNIQUE约束 —— 从源头杜绝重复

去重的最高境界不是"删",而是"防"。

sql
ALTER TABLE t_user 
ADD CONSTRAINT uk_user_email UNIQUE (user_name, email);

一旦添加唯一约束,任何插入重复数据的操作都会被MySQL拒绝并抛出错误。这是数据库层面的最后一道防线


三、方法选型决策树

你的需求 推荐方法 理由
简单查询去重,不需要其他列 DISTINCT 语法最简,性能在小结果集时最优
需要聚合统计(count/max/sum等) GROUP BY + HAVING 天然支持聚合,功能最灵活
去重并保留最新/最大/最小的一条 ROW_NUMBER() 精确控制保留哪条,MySQL 8.0+首选
真正删除表中重复数据 临时表法 / DELETE+子查询 安全可控,支持回滚
防止未来产生重复 UNIQUE约束 从根源解决问题

四、性能优化建议

  1. 建索引:对去重字段建立索引,能将全表扫描降为索引扫描,性能提升可达数倍
  2. 批量处理:大数据量去重时,分批次执行,避免长事务锁表
  3. 先查后删:执行任何删除操作前,务必先用SELECT验证重复数据
  4. 定期维护:建议将去重纳入数据库定期维护计划,而非等问题爆发再处理

五、总结

MySQL的去重查询绝非"一招鲜吃遍天"。DISTINCT胜在简洁,GROUP BY赢在灵活,ROW_NUMBER()强在精准,临时表法贵在安全,UNIQUE约束赢在防患于未然。

作为开发工程师,真正的能力不在于记住所有语法,而在于面对具体场景时,能迅速判断哪种方法最合适、最安全、最高效

0条评论
作者已关闭评论
窝补药上班啊
1449文章数
7粉丝数
窝补药上班啊
1449 文章 | 7 粉丝
原创

详细分析Mysql去重的几种方法(附Demo)-天翼云

2026-07-08 14:58:44
1
0

一、为什么去重如此重要?

在深入技术细节之前,先明确去重的价值:

维度 影响
数据一致性 重复数据会导致统计结果失真,比如用户数虚高、订单金额重复计算
存储优化 冗余记录占用磁盘空间,拖慢查询速度
分析准确性 BI报表、数据看板若基于重复数据,结论必然偏离事实
业务逻辑 唯一性约束被破坏,可能引发业务异常(如重复扣款)

所以,去重不是"锦上添花",而是数据治理的基本功


二、MySQL去重的六大核心方法

方法一:DISTINCT —— 最简洁的去重利器

DISTINCT是MySQL中最直观的去重关键字,直接作用于查询结果集,剔除所有重复行。

基础语法

sql
SELECT DISTINCT column_name FROM table_name;

多列组合去重

sql
SELECT DISTINCT user_name, email, address FROM t_user;

核心要点

  • DISTINCT必须放在SELECT关键字之后、所有字段之前
  • 它作用于所有选中字段的组合,只有当所有字段值完全相同时才会被去重
  • 适用于单列或少量字段的简单去重场景

性能表现: 在有索引的情况下,当结果集远小于表总量时,DISTINCT性能最优;但在大表无索引场景下,可能触发全表扫描,效率急剧下降。


方法二:GROUP BY —— 去重与聚合的双刃剑

GROUP BY通过分组实现去重,同时天然支持聚合函数,是分析型查询的首选。

基础语法

sql
SELECT user_name, email, address 
FROM t_user 
GROUP BY user_name, email, address;

配合聚合函数的进阶用法

sql
-- 查询每个部门的最高薪资
SELECT dept, MAX(salary) AS maximum 
FROM staff 
GROUP BY dept;

-- 查询雇员数超过2人的部门
SELECT dept, MAX(salary) AS maximum, MIN(salary) AS minimum 
FROM staff 
GROUP BY dept 
HAVING COUNT(*) > 2;

GROUP BY vs DISTINCT 的关键区别

对比项 DISTINCT GROUP BY
语法复杂度 极简 中等
聚合能力 不支持 天然支持
返回非分组字段 有限制 需配合聚合函数
执行计划 某些场景与GROUP BY相同 某些场景与DISTINCT相同

性能结论: 有索引时,GROUP BY在结果集较大的场景下比DISTINCT性能更好;无索引时两者表现接近。


方法三:ROW_NUMBER() 窗口函数 —— 去重保留指定记录的终极方案

这是MySQL 8.0+版本引入的杀手级特性。当你需要"去重并保留最新/最早/最大/最小的那条记录"时,窗口函数是最佳选择。

核心Demo:保留每个用户的最新登录记录

sql
WITH ranked AS (
    SELECT 
        user_name, 
        email, 
        last_login,
        ROW_NUMBER() OVER (PARTITION BY user_name ORDER BY last_login DESC) AS rn
    FROM t_user
)
SELECT user_name, email, last_login
FROM ranked
WHERE rn = 1;

语法拆解

  • PARTITION BY user_name:按用户名分组
  • ORDER BY last_login DESC:组内按登录时间降序排列
  • ROW_NUMBER():为每组内的行分配唯一序号
  • WHERE rn = 1:只取每组的第一条(即最新记录)

低版本MySQL的替代方案(MySQL 5.7及以下)

sql
SELECT user_name, email, address
FROM (
    SELECT b.*, 
           @rownum := @rownum + 1,
           IF(@pdept = b.user_name, @rank := @rank + 1, @rank := 1) AS rank,
           @pdept := b.user_name
    FROM (SELECT * FROM t_user ORDER BY user_name, last_login DESC) b,
         (SELECT @rownum := 0, @pdept := NULL, @rank := 0) c
) t
WHERE rank = 1;

这种方式利用用户变量模拟ROW_NUMBER(),虽然能用,但可读性和可维护性远不如窗口函数。


方法四:临时表法 —— 大数据量去重的安全之选

当需要真正删除表中的重复数据(而非仅查询去重)时,临时表法是最稳妥的方案。

完整操作流程

sql
-- 步骤1:创建新表,插入去重数据
CREATE TABLE t_user_new LIKE t_user;

INSERT INTO t_user_new 
SELECT * FROM t_user GROUP BY user_name, email;

-- 步骤2:验证数据
SELECT COUNT(*) FROM t_user_new;

-- 步骤3:替换原表
RENAME TABLE t_user TO t_user_backup, t_user_new TO t_user;

-- 步骤4:确认无误后删除备份
DROP TABLE t_user_backup;

优势: 不锁原表、不影响线上业务、可回滚,是生产环境大批量去重的首选方案。


方法五:DELETE + 子查询 —— 精准删除重复项

如果只想删除重复数据、保留一条(比如保留ID最小的那条):

sql
DELETE FROM t_user 
WHERE id NOT IN (
    SELECT min_id FROM (
        SELECT MIN(id) AS min_id 
        FROM t_user 
        GROUP BY user_name, email
    ) AS temp
);

注意: MySQL不允许在子查询中直接修改同一张表,所以必须再套一层SELECT,这是一个经典的"防坑"写法。


方法六:UNIQUE约束 —— 从源头杜绝重复

去重的最高境界不是"删",而是"防"。

sql
ALTER TABLE t_user 
ADD CONSTRAINT uk_user_email UNIQUE (user_name, email);

一旦添加唯一约束,任何插入重复数据的操作都会被MySQL拒绝并抛出错误。这是数据库层面的最后一道防线


三、方法选型决策树

你的需求 推荐方法 理由
简单查询去重,不需要其他列 DISTINCT 语法最简,性能在小结果集时最优
需要聚合统计(count/max/sum等) GROUP BY + HAVING 天然支持聚合,功能最灵活
去重并保留最新/最大/最小的一条 ROW_NUMBER() 精确控制保留哪条,MySQL 8.0+首选
真正删除表中重复数据 临时表法 / DELETE+子查询 安全可控,支持回滚
防止未来产生重复 UNIQUE约束 从根源解决问题

四、性能优化建议

  1. 建索引:对去重字段建立索引,能将全表扫描降为索引扫描,性能提升可达数倍
  2. 批量处理:大数据量去重时,分批次执行,避免长事务锁表
  3. 先查后删:执行任何删除操作前,务必先用SELECT验证重复数据
  4. 定期维护:建议将去重纳入数据库定期维护计划,而非等问题爆发再处理

五、总结

MySQL的去重查询绝非"一招鲜吃遍天"。DISTINCT胜在简洁,GROUP BY赢在灵活,ROW_NUMBER()强在精准,临时表法贵在安全,UNIQUE约束赢在防患于未然。

作为开发工程师,真正的能力不在于记住所有语法,而在于面对具体场景时,能迅速判断哪种方法最合适、最安全、最高效

文章来自个人专栏
文章 | 订阅
0条评论
作者已关闭评论
作者已关闭评论
0
0