find_in_set、IN、LIKE 谁更坑?90% 开发者都用错了!

find_in_set、IN、LIKE 谁更坑?90% 开发者都用错了!

编码文章call10242025-09-12 16:23:143A+A-

在 MySQL 开发中,“判断某个值是否在集合里” 是高频需求。但面对 find_in_set、IN、LIKE 这三个常用工具,很多人凭感觉乱用,结果要么查不出数据,要么把数据库查崩!今天就用真实案例拆解它们的性能差异、优缺点和适用场景,帮你避开 90% 的坑,最后还邀请大家分享实战 SQL 技巧。

一、先看一个血案:用错函数,查询从 0.1 秒变 10 秒

前几天帮朋友排查项目故障:一个用户列表查询,原本 0.1 秒出结果,加了个 “筛选用户角色” 的条件后,直接卡到 10 秒!原来他写了这样的 SQL:

-- 想筛选出拥有"管理员"角色(角色编码1)的用户

SELECT * FROM user WHERE roles LIKE '%,1,%';

而用户表的 roles 字段存的是 “1,2,3” 这种逗号分隔字符串,表数据才 5 万行,却触发了全表扫描。后来把 SQL 改成FIND_IN_SET('1', roles),查询时间降到 2 秒;再重构表结构用 IN 查询,直接回到 0.1 秒。

这个案例暴露了很多人对这三个工具的认知误区:以为能实现 “包含判断” 就可以随便用,却忽略了性能天差地别。

二、3 分钟搞懂:三者核心差异对比

先上一张对比表,把关键区别讲透,后面再逐个拆解:

对比维度

IN 运算符

FIND_IN_SET 函数

LIKE 运算符

操作对象

固定值列表 / 子查询结果

逗号分隔的字符串字段

任意字符串字段

匹配逻辑

直接匹配列表值

拆分字符串后精确匹配

通配符模糊匹配

索引支持

支持(字段有索引时)

不支持(必全表扫描)

仅前缀匹配支持(如 '% a' 不支持)

性能(大数据量)

优秀(毫秒级)

较差(秒级)

极差(分钟级,全表扫描)

典型错误场景

用 IN 判断字符串字段集合

大数据表用它做筛选

用 '% 值 %' 判断包含关系

1. IN 运算符:性能王者,但只认 “固定列表”

什么是 IN?

IN 是 MySQL 里的 “多值匹配神器”,专门判断 “某个值是否在固定列表里”,语法超简单:

-- 例子1:判断id是否在[1,3,5]列表中(用索引,0.1秒出结果)

SELECT * FROM user WHERE id IN (1,3,5);

-- 例子2:结合子查询(动态获取列表,同样支持索引)

SELECT * FROM user WHERE dept_id IN (SELECT dept_id FROM dept WHERE status=1);

优点:

  • 性能天花板:只要查询的字段有索引(比如 id、dept_id),MySQL 会直接走索引查找,大数据量下也能秒出结果。
  • 语法直观:一看就懂,不用记复杂函数参数。
  • 支持子查询:可以动态生成匹配列表,灵活度高。

缺点:

  • 不能直接匹配字符串集合:比如字段 roles 存 “1,2,3”,写WHERE 1 IN (roles)会报错!因为 IN 只认 “(值 1, 值 2)” 这种列表,不认字符串。
  • 列表长度有限制:如果 IN 后面跟几千个值,SQL 解析会变慢,建议用子查询替代。

适用场景:

  • 匹配固定值列表(如筛选指定 ID 的用户)。
  • 匹配子查询返回的结果(如筛选属于活跃部门的用户)。

2. FIND_IN_SET:字符串集合专用,但性能拉胯

什么是 FIND_IN_SET?

专门处理 “逗号分隔的字符串字段”,比如 roles 字段存 “1,2,3”,判断 “1” 是否在里面,语法:

-- 正确用法:判断1是否在roles字段的字符串里

SELECT * FROM user WHERE FIND_IN_SET('1', roles);

-- 错误用法:不要用它匹配固定列表(性能不如IN)

SELECT * FROM user WHERE FIND_IN_SET(id, '1,3,5');

优点:

  • 精确匹配字符串集合:不会像 LIKE 那样 “误伤”,比如FIND_IN_SET('2', '1,23')会返回 0(不匹配),而 LIKE '%,2,%' 会误判为匹配。
  • 解决 “字符串存集合” 的痛点:如果表结构没设计好,字段里存了逗号分隔值,用它能准确筛选。

缺点:

  • 性能黑洞:无论字段有没有索引,FIND_IN_SET 都不会走索引!因为它要先把字符串拆分成集合,再逐个匹配,5 万行数据可能要 2-3 秒,100 万行直接卡崩。
  • 依赖字符串格式:如果字段里有空格(比如 “1, 2,3”),会匹配失败,必须先清理格式。
  • 不支持子查询:不能像 IN 那样写FIND_IN_SET(id, SELECT ...),只能传固定字符串。

适用场景:

  • 字段存储了逗号分隔的字符串(如标签、角色),且数据量小(比如几百行)。
  • 临时查询,不追求性能(比如后台小工具)。

3. LIKE:灵活但危险,90% 的人用错它

什么是 LIKE?

LIKE 是模糊匹配工具,用 %(任意字符)、_(单个字符)做通配符,比如:

-- 前缀匹配(支持索引,性能好)

SELECT * FROM user WHERE username LIKE '张%';

-- 包含匹配(不支持索引,性能差)

SELECT * FROM user WHERE roles LIKE '%,1,%';

优点:

  • 灵活度最高:可以匹配任意位置的字符串,比如开头、结尾、中间包含。
  • 语法简单:不用记复杂函数,新手也能上手。

缺点:

  • 性能极差(包含匹配时):如果用%值%(中间包含),MySQL 会放弃索引,全表扫描,10 万行数据可能要几十秒。
  • 匹配不精确:容易误判,比如LIKE '%,2,%'会匹配 “12,3”“22,4”,把包含 “2” 的字符串都算进去。
  • 无法处理空值:如果字段是 NULL,LIKE 匹配不到,需要用 IS NULL。

适用场景:

  • 前缀匹配(如张%):此时支持索引,适合模糊查询用户名、手机号(如查 138 开头的手机号)。
  • 简单的字符串开头 / 结尾匹配(如%@qq.com查 QQ 邮箱)。
  • 绝对不要用它做 “包含判断”(如%,1,%),除非数据量极小!

三、避坑指南:3 个实战原则

  1. 优先用 IN,避免 FIND_IN_SET 和 LIKE 的包含匹配

只要能拆成 “固定列表” 或 “子查询”,就用 IN,性能秒杀另外两个。比如原本用FIND_IN_SET('1', roles),如果能改成role_id IN (1)(把 roles 拆成关联表),性能会提升 100 倍。

  1. 拒绝 “字符串存集合” 的表设计

这是根源问题!很多人图方便,把角色、标签存成 “1,2,3” 这种字符串,后期查询全是坑。正确做法是拆成关联表,比如 user_roles 表(user_id, role_id),然后用 IN 或 JOIN 查询:

-- 拆表后用JOIN,性能优秀

SELECT u.* FROM user u

JOIN user_roles ur ON u.id = ur.user_id

WHERE ur.role_id = 1;
  1. 迫不得已用 FIND_IN_SET?加个限制条件

如果表结构改不了,必须用 FIND_IN_SET,一定要加其他筛选条件(如时间范围),减少扫描行数:

-- 不好:全表扫描5万行

SELECT * FROM user WHERE FIND_IN_SET('1', roles);

-- 好:先按create_time筛选,只扫描1000行

SELECT * FROM user

WHERE create_time > '2024-01-01'

AND FIND_IN_SET('1', roles);

各位看官老爷在项目里踩过 SQL 的坑,也总结出了自己的技巧:比如用 JOIN 替代子查询、用索引覆盖优化查询、用 EXPLAIN 分析执行计划……

欢迎在评论区分享你的实战技巧:比如你是怎么优化慢 SQL 的?有没有遇到过 FIND_IN_SET 或 LIKE 的坑?一起帮更多开发者避坑!

点击这里复制本文地址 以上内容由文彬编程网整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!
qrcode

文彬编程网 © All Rights Reserved.  蜀ICP备2024111239号-4