mysql 去重(长文解析)
💡一则或许对你有用的小广告
欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论
- 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于
Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...
,点击查看项目介绍 ;演示链接: http://116.62.199.48:7070 ;- 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;
截止目前, 星球 内专栏累计输出 90w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 3100+ 小伙伴加入学习 ,欢迎点击围观
前言
在数据库开发中,"mysql 去重"是一个高频需求。想象你正在管理一个图书馆的借阅记录表,当发现多本同名书籍被重复登记时,如何快速筛选出唯一名称的书籍?类似地,开发者常需要从数据库中删除冗余数据或统计唯一值。本文将通过DISTINCT、GROUP BY等核心工具,结合实际案例,逐步拆解 MySQL 去重的实现方法与优化策略。
基础语法:DISTINCT 的核心用法
什么是去重?
"去重"即从数据集合中筛选出唯一值。在 MySQL 中,最基础的去重工具是 DISTINCT 关键字。它像一个"过滤器",能自动忽略重复的字段值。
示例:查询唯一用户名称
假设有一个用户表 users
,包含以下字段:
CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(50),
email VARCHAR(100)
);
插入测试数据:
INSERT INTO users (id, name, email)
VALUES
(1, 'Alice', 'alice@example.com'),
(2, 'Bob', 'bob@example.com'),
(3, 'Alice', 'alice2@example.com'),
(4, 'Charlie', 'charlie@example.com');
若想查询所有不同名称的用户,使用以下语句:
SELECT DISTINCT name FROM users;
执行结果:
+--------+
| name |
+--------+
| Alice |
| Bob |
| Charlie|
+--------+
DISTINCT 的局限性:
- 只能针对单个字段去重。若需多字段组合去重(例如同时筛选唯一
name
和email
),需将字段写入同一DISTINCT
子句中:SELECT DISTINCT name, email FROM users;
- 无法直接删除重复数据,仅能返回去重后的结果集。
进阶技巧:GROUP BY 与多条件去重
当需要统计重复数据或处理多字段去重时,GROUP BY 是更灵活的工具。
案例:统计重复名称的用户数量
SELECT name, COUNT(*) AS count
FROM users
GROUP BY name
HAVING count > 1;
执行结果:
+--------+-------+
| name | count |
+--------+-------+
| Alice | 2 |
+--------+-------+
GROUP BY 的优势:
- 可结合聚合函数(如
COUNT
、SUM
)实现复杂统计。 - 通过
HAVING
筛选满足条件的分组,例如找出重复超过三次的记录。
子查询与临时表:处理复杂去重场景
当需要删除重复数据时,DISTINCT
和 GROUP BY
无法直接操作。此时可通过子查询或临时表定位重复项。
案例:删除重复的用户记录
假设 users
表中存在完全重复的 name
和 email
记录:
INSERT INTO users (id, name, email)
VALUES (5, 'Bob', 'bob@example.com');
删除重复数据的步骤:
- 通过
GROUP BY
找到重复的组合:SELECT name, email, MIN(id) AS keep_id FROM users GROUP BY name, email HAVING COUNT(*) > 1;
结果:
+------+---------------------+----------+ | name | email | keep_id | +------+---------------------+----------+ | Bob | bob@example.com | 2 | +------+---------------------+----------+
- 删除非最小
id
的重复记录:DELETE FROM users WHERE (name, email) IN ( SELECT name, email FROM users GROUP BY name, email HAVING COUNT(*) > 1 ) AND id NOT IN ( SELECT MIN(id) FROM users GROUP BY name, email HAVING COUNT(*) > 1 );
窗口函数:高效去重的现代方案
MySQL 8.0 引入的窗口函数(如 ROW_NUMBER()
)为去重提供了更简洁的方法。
案例:标记重复行并删除
WITH ranked_users AS (
SELECT
id,
name,
email,
ROW_NUMBER() OVER (PARTITION BY name, email ORDER BY id) AS rn
FROM users
)
DELETE FROM users
WHERE id IN (SELECT id FROM ranked_users WHERE rn > 1);
解释:
ROW_NUMBER()
为每组重复记录分配序号。- 删除序号大于
1
的记录,保留第一条。
性能优化:让去重查询更高效
1. 索引的作用
在去重字段上添加索引可显著提升查询速度。例如:
ALTER TABLE users ADD INDEX idx_name_email (name, email);
2. 避免使用 SELECT *
去重时仅选择必要字段,减少数据传输量。
-- 低效写法
SELECT * FROM users GROUP BY name;
-- 高效写法
SELECT name FROM users GROUP BY name;
3. 使用 EXPLAIN
分析执行计划
通过 EXPLAIN
检查查询是否使用索引:
EXPLAIN SELECT DISTINCT name FROM users;
理想情况下,type
列应显示 index
,表示直接使用索引扫描。
常见问题与解决方案
问题 1:去重后如何保留其他字段?
若需保留非去重字段(如 id
),可结合 GROUP BY
和聚合函数:
SELECT
name,
MIN(id) AS first_id,
MAX(email) AS latest_email
FROM users
GROUP BY name;
问题 2:如何处理部分字段重复?
假设需保留 name
唯一,但允许 email
不同:
SELECT * FROM users
WHERE name IN (
SELECT name FROM users GROUP BY name HAVING COUNT(*) = 1
);
结论
"mysql 去重"是开发者必须掌握的核心技能。从基础的 DISTINCT
到进阶的窗口函数,每种方法都有其适用场景。通过合理使用索引、避免全表扫描,甚至结合事务确保数据一致性,开发者可以高效处理去重需求。建议读者在实际项目中结合案例练习,并通过 EXPLAIN
持续优化查询性能。掌握这些技巧后,你将能从容应对数据冗余带来的挑战。