mysql find_in_set(建议收藏)
💡一则或许对你有用的小广告
欢迎加入小哈的星球 ,你将获得:专属的项目实战(已更新的所有项目都能学习) / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论
- 新开坑项目:《Spring AI 项目实战》 正在持续爆肝中,基于 Spring AI + Spring Boot 3.x + JDK 21..., 点击查看 ;
- 《从零手撸:仿小红书(微服务架构)》 已完结,基于
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
的FIND_IN_SET()
函数便能大显身手。它像一把精准的“字符串筛子”,帮助开发者快速定位符合条件的数据。本文将从基础语法、进阶用法、性能优化到常见问题,逐步解析这一函数的使用场景与技巧,适合编程初学者和中级开发者深入理解。
基础语法与核心原理
什么是 FIND_IN_SET?
FIND_IN_SET(str, str_list)
是 MySQL
提供的字符串函数,用于在逗号分隔的字符串列表中查找某个字符串的位置。其返回值为:
- 正整数:表示该字符串在列表中的位置(从1开始计数);
- 0:表示未找到;
- NULL:当任意参数为
NULL
时返回。
形象比喻:
可以将 FIND_IN_SET
想象为一个“字符串筛子”,将输入的字符串列表(如 "a,b,c"
)拆解为多个独立项,逐项检查目标字符串是否存在于其中。
基础语法示例
SELECT FIND_IN_SET('b', 'a,b,c,d'); -- 返回 2
SELECT FIND_IN_SET('e', 'a,b,c,d'); -- 返回 0
SELECT FIND_IN_SET('b', NULL); -- 返回 NULL
实际案例:学生选课系统
假设有一个学生表 students
,其中 courses
字段存储学生选修的课程(如 "math,physics"
),我们需要查询选修了“math”课程的学生:
CREATE TABLE students (
id INT PRIMARY KEY,
name VARCHAR(50),
courses TEXT
);
INSERT INTO students VALUES
(1, 'Alice', 'math,physics'),
(2, 'Bob', 'physics'),
(3, 'Charlie', 'math,biology');
SELECT * FROM students
WHERE FIND_IN_SET('math', courses) > 0;
-- 返回 Alice 和 Charlie 的记录
进阶用法与逻辑扩展
条件筛选的多种组合
FIND_IN_SET
可与其他查询条件结合,实现复杂筛选。例如,查找同时选修了“math”和“physics”的学生:
SELECT * FROM students
WHERE
FIND_IN_SET('math', courses) > 0
AND FIND_IN_SET('physics', courses) > 0;
-- 返回 Alice
子查询与动态参数
若目标字符串需动态生成,可结合子查询使用。例如,查询选修了其他学生选修过的所有课程的学生:
SELECT * FROM students s
WHERE
FIND_IN_SET(
(SELECT courses FROM students WHERE id = 1),
s.courses
) > 0;
-- 假设学生1的courses是 "math,physics",则筛选出包含这两个课程的学生
注意事项:参数顺序与空格
- 参数顺序不可颠倒:
FIND_IN_SET('math', 'math,physics')
正确,而FIND_IN_SET('math,physics', 'math')
会返回 0。 - 空格不影响匹配:
FIND_IN_SET('math', 'math ,physics')
仍返回 1,但建议存储时保持格式统一。
性能优化与替代方案
为何使用 FIND_IN_SET 可能影响性能?
FIND_IN_SET
的核心问题在于:无法利用索引。因为其操作依赖于字符串拆分,数据库无法通过索引快速定位数据,导致查询可能全表扫描。例如,当表数据量极大时,以下查询可能变慢:
SELECT * FROM large_table
WHERE FIND_IN_SET('rare_value', tags) > 0;
优化策略:重构数据结构
为避免性能瓶颈,可考虑以下替代方案:
1. 使用关联表(规范化设计)
将多对多关系(如“学生-课程”)拆分为三张表:
CREATE TABLE students (id INT PRIMARY KEY, name VARCHAR(50));
CREATE TABLE courses (id INT PRIMARY KEY, name VARCHAR(50));
CREATE TABLE student_courses (
student_id INT,
course_id INT,
PRIMARY KEY (student_id, course_id),
FOREIGN KEY (student_id) REFERENCES students(id),
FOREIGN KEY (course_id) REFERENCES courses(id)
);
此方案允许通过索引快速查询,例如:
SELECT s.*
FROM students s
JOIN student_courses sc ON s.id = sc.student_id
JOIN courses c ON sc.course_id = c.id
WHERE c.name = 'math';
2. 使用 JSON 类型(MySQL 5.7+)
若需保留“字符串列表”特性,可改用 JSON
类型存储,利用 JSON_CONTAINS
函数查询:
ALTER TABLE students MODIFY courses JSON;
INSERT INTO students (id, name, courses) VALUES
(1, 'Alice', '["math", "physics"]'),
(2, 'Bob', '["physics"]');
SELECT * FROM students
WHERE JSON_CONTAINS(courses, '"math"', '$');
此方法支持索引优化,但需注意 JSON
类型的存储限制。
常见问题与解决方案
问题1:为什么返回结果为空?
可能原因:
- 目标字符串的大小写或空格与存储值不一致(如查询
'Math'
而存储的是'math'
)。 - 字段值为
NULL
,导致返回NULL
而非 0。
解决方法:
- 使用
LOWER()
或TRIM()
函数统一格式:WHERE FIND_IN_SET(LOWER('Math'), LOWER(courses)) > 0
- 处理
NULL
值:WHERE COALESCE(FIND_IN_SET('math', courses), 0) > 0
问题2:如何避免全表扫描?
策略:
- 小数据量场景:若表数据量较小(如 < 10万条),
FIND_IN_SET
可接受。 - 添加虚拟列索引(MySQL 8.0+):
ALTER TABLE students ADD COLUMN has_math INT AS (FIND_IN_SET('math', courses) > 0) VIRTUAL; CREATE INDEX idx_has_math ON students (has_math);
此时查询可直接使用
WHERE has_math = 1
,利用索引加速。
结论
MySQL FIND_IN_SET
是处理逗号分隔字符串的实用工具,尤其适用于简单场景的快速开发。然而,随着数据规模增长,需谨慎评估其性能影响,并考虑通过规范化设计或 JSON
类型优化架构。开发者应根据实际需求,权衡“开发效率”与“查询性能”,合理选择实现方案。
掌握这一函数后,读者可进一步探索 LOCATE()
、SUBSTRING_INDEX()
等字符串函数,逐步构建对复杂数据操作的系统认知。