知其然要知其所以然,探索每一个知识点背后的意义,你知道的越多,你不知道的越多,一起学习,一起进步,如果文章感觉对您有用的话,关注、收藏、点赞,有困惑的地方请评论,我们一起交流!
一、FIND_IN_SET基础用法
1. 函数语法
sql
FIND_IN_SET(str, strlist)
- str:待查找的字符串。
- strlist:逗号分隔的字符串列表。
- 返回值 :
- 找到:返回位置索引(从1开始)。
- 未找到:返回
0
。 - 任一参数为
NULL
:返回NULL
。
2. 简单示例
sql
SELECT FIND_IN_SET('apple', 'apple,banana,orange'); -- 返回 1
SELECT FIND_IN_SET('banana', 'apple,banana,orange'); -- 返回 2
SELECT FIND_IN_SET('grape', 'apple,banana,orange'); -- 返回 0
二、典型使用场景(及风险)
1. 简单标签匹配
假设有一个 articles
表,其中 tags
字段存储逗号分隔的标签:
sql
SELECT * FROM articles
WHERE FIND_IN_SET('tech', tags) > 0;
问题:全表扫描、无法利用索引、数据冗余。
2. 权限校验
用户表 users
包含 permissions
字段(如 'read,write,delete'
):
sql
SELECT * FROM users
WHERE FIND_IN_SET('write', permissions) > 0;
隐患:权限管理耦合在字符串中,难以维护。
三、深度解析
1. 底层实现机制
- 字符串分割 :将
strlist
按逗号拆分为临时数组。 - 遍历匹配:逐个对比元素,直到找到匹配项。
- 时间复杂度:O(n),对大字符串或大数据集性能急剧下降。
2. 性能问题实测
sql
EXPLAIN SELECT * FROM large_table WHERE FIND_IN_SET('value', csv_column);
- 执行计划 :通常显示
ALL
(全表扫描),即使csv_column
有索引。
3. 与替代方案的对比
方法 | 性能 | 可维护性 | 索引支持 | 符合范式 |
---|---|---|---|---|
FIND_IN_SET | 低 | 差 | 不支持 | 否 |
关联表 (JOIN) | 高 | 优 | 支持 | 是 |
JSON_CONTAINS | 中 | 中 | 部分支持 | 否 |
四、更优替代方案
1. 关联表设计(规范化)
sql
-- 原始表
CREATE TABLE articles (
id INT PRIMARY KEY,
title VARCHAR(255)
);
-- 标签关联表
CREATE TABLE article_tags (
article_id INT,
tag VARCHAR(50),
PRIMARY KEY (article_id, tag),
FOREIGN KEY (article_id) REFERENCES articles(id)
);
-- 查询示例
SELECT a.* FROM articles a
JOIN article_tags t ON a.id = t.article_id
WHERE t.tag = 'tech';
优势:索引支持、易于统计、避免数据冗余。
2. 使用JSON类型(MySQL 5.7+)
sql
ALTER TABLE articles ADD COLUMN tags JSON;
-- 查询示例
SELECT * FROM articles
WHERE JSON_CONTAINS(tags, '"tech"', '$');
优势:支持JSON索引、查询效率较高。
3. 使用ES(ES主要是帮助业务检索负责数据)
五、何时可以考虑使用FIND_IN_SET?
- 临时数据分析:快速查询非规范化数据。
- 小型静态表:数据量小且不频繁查询。
- 遗留系统适配:暂时无法修改表结构时。
六、总结
- 慎用场景 :
FIND_IN_SET
在大多数生产环境中应避免使用,尤其是在高并发或大数据表上。 - 设计原则 :
- 优先遵循数据库第一范式(1NF)。
- 使用关联表或JSON类型替代逗号分隔字符串。
- 迁移建议:逐步将现有逗号分隔字段重构为关联表,结合业务需求评估代价。