FIND_IN_SET()仅适用于无法修改表结构的遗留系统中临时查询逗号分隔字段,如查含某角色的用户;它不走索引、逗号硬分隔、大小写敏感、NULL/空字符串处理复杂,应优先用关联表、JSON字段或SET类型替代。

FIND_IN_SET() 是 MySQL 里一个「看着简单、用着危险」的字符串查找函数——它只适合小数据量、低频查询、临时补救场景,绝不该出现在核心业务表结构设计中。
什么时候必须用 FIND_IN_SET()?
只有当你面对的是已上线、无法改表结构的遗留系统,且字段存的是逗号分隔值(如 roles 字段值为 'admin,user,editor'),又急需快速查出含某个角色的用户时,才考虑它。
- ✅ 合理场景:后台运营临时导出“带 vip 标签的用户”,表没建关联表、也没 JSON 字段
- ✅ 合理场景:测试环境快速验证逻辑,不追求性能
- ❌ 绝对避免:高并发接口、日活百万级系统的 WHERE 条件、订单/用户主表查询
- ❌ 绝对避免:把
FIND_IN_SET()套在索引字段上幻想能走索引(它完全不走索引)
SELECT * FROM users WHERE FIND_IN_SET('vip', user_tags) > 0;
FIND_IN_SET() 的三个致命限制
不是语法写错才出问题,而是设计层面就埋了雷:
-
逗号是硬分隔符,不能处理含逗号的值:比如查
FIND_IN_SET('a,b', 'a,b,c')→ 返回0(因为函数把整个'a,b'当作一个待匹配项,而列表里只有'a'、'b'、'c'三个独立项) -
大小写敏感:
FIND_IN_SET('Admin', 'admin,user')→ 返回0 -
NULL 和空字符串全返回 NULL 或 0,但含义不同:
FIND_IN_SET(NULL, 'a,b')→NULL;FIND_IN_SET('', 'a,b')→0;FIND_IN_SET('a', '')→0—— 判断时必须写成> 0,不能只写IS NOT NULL
比 FIND_IN_SET() 更靠谱的替代方案
如果还能动表结构,立刻停用它。以下方案按推荐优先级排序:
-
关联表(最优):新建
user_roles(user_id, role_id),用 JOIN + 索引,支持任意复杂查询 -
JSON 字段(MySQL 5.7+):把
roles改成JSON类型,用JSON_CONTAINS(roles, '"admin"'),可配合生成列+索引加速 -
SET 类型(仅限固定枚举值):定义
roles SET('admin','user','vip'),此时FIND_IN_SET()会被优化为位运算,但灵活性极差
别信“加个函数索引就能提速”——FIND_IN_SET() 本身无法被索引覆盖,所谓“优化”只在 strlist 是 SET 列时才生效,普通字符串字段无效。
真要用,至少守住这三条底线
- WHERE 条件里永远写
FIND_IN_SET('xxx', field) > 0,别漏掉> 0 - 确保
str参数不含逗号、单引号、反斜杠等特殊字符(否则需预处理或转义) - 在 WHERE 中避免嵌套调用,例如不要写
FIND_IN_SET(UPPER('a'), LOWER(tags))—— 性能雪崩
说到底,FIND_IN_SET() 是数据库设计欠债后的止痛片,不是维生素。用它一时爽,查慢报警火葬场。










