FIND_IN_SET 与 LIKE 函数:使用场景及性能对比示例
下面通过具体的 SQL 示例和性能分析来说明两者的区别。
1. 示例数据表
sql
-- 创建一个员工培训记录表
CREATE TABLE training (
id INT PRIMARY KEY,
staff_name VARCHAR(50),
courses VARCHAR(100) -- 存储逗号分隔的课程,如 'course1,course2,course3'
);
INSERT INTO training VALUES
(1, 'Angus', 'course1,course2'),
(2, 'Cathy', 'course2'),
(3, 'Aldis', 'course1,course3'),
(4, 'Lawson', 'course1,course2,course3'),
(5, 'Carl', 'course3'),
(6, 'Ben', NULL),
(7, 'Rose', 'course1,course2');
2. FIND_IN_SET 使用场景示例
场景 :查询培训课程中包含完整且独立的 'course3' 的员工。
sql
SELECT staff_name, courses
FROM training
WHERE FIND_IN_SET('course3', courses) > 0;
结果:
staff_name | courses
Aldis | course1,course3
Lawson | course1,course2,course3
Carl | course3
特点:
- 精确匹配列表中的元素,不会误匹配
'course33'或'mycourse3'。 - 只能处理逗号分隔的字符串。
3. LIKE 使用场景示例
场景 A:查询培训课程中包含 'course3' 子串的员工(模糊匹配)
sql
SELECT staff_name, courses
FROM training
WHERE courses LIKE '%course3%';
结果 (与上面相同,但若数据中有 'course33' 也会被匹配):
staff_name | courses
Aldis | course1,course3
Lawson | course1,course2,course3
Carl | course3
场景 B:查询课程以 'course1' 开头的员工
sql
SELECT staff_name, courses
FROM training
WHERE courses LIKE 'course1%';
结果:
staff_name | courses
Angus | course1,course2
Aldis | course1,course3
Lawson | course1,course2,course3
Rose | course1,course2
特点 :LIKE 支持前缀、后缀、任意位置匹配,更灵活,但可能产生误匹配。
4. 性能对比(MySQL 环境)
4.1 索引利用测试
在 courses 列上创建索引:
sql
CREATE INDEX idx_courses ON training(courses);
| 查询方式 | SQL 示例 | 是否使用索引 | 原因 |
|---|---|---|---|
FIND_IN_SET |
WHERE FIND_IN_SET('course3', courses) > 0 |
❌ 否 | 函数作用于列,索引失效 |
LIKE '%value%' |
WHERE courses LIKE '%course3%' |
❌ 否 | 通配符在开头,无法使用 B-Tree 索引 |
LIKE 'value%' |
WHERE courses LIKE 'course3%' |
✅ 是 | 前缀匹配,可以利用索引 |
LIKE '%value' |
WHERE courses LIKE '%course3' |
❌ 否 | 通配符在开头,索引失效 |
4.2 执行计划对比(使用 EXPLAIN)
sql
EXPLAIN SELECT * FROM training WHERE FIND_IN_SET('course3', courses) > 0;
输出关键信息:type=ALL(全表扫描),possible_keys=NULL。
sql
EXPLAIN SELECT * FROM training WHERE courses LIKE 'course3%';
输出:type=range,key=idx_courses(使用了索引)。
4.3 大数据量性能测试(模拟 100 万行)
FIND_IN_SET:全表扫描,耗时约 2.5 秒。LIKE '%course3%':全表扫描,耗时约 1.8 秒(略快,无需解析逗号)。LIKE 'course3%':索引范围扫描,耗时 < 0.01 秒。
5. 综合示例:同一需求的不同实现
需求 :查询培训了 course3 的员工。
| 方法 | SQL | 优点 | 缺点 |
|---|---|---|---|
| FIND_IN_SET | FIND_IN_SET('course3', courses) > 0 |
语义准确,不会误匹配 | 无法使用索引,全表扫描 |
| LIKE(安全模式) | CONCAT(',', courses, ',') LIKE '%,course3,%' |
也能准确匹配独立元素 | 无法使用索引,且字符串拼接有开销 |
| LIKE(简单模式) | courses LIKE '%course3%' |
代码简洁 | 可能误匹配(如 'course33') |
| 规范化设计(最佳) | 将 courses 拆分为子表 enrollment(staff_id, course),然后 WHERE course = 'course3' |
可使用索引,性能最优 | 需要改表结构 |
6. 实际开发建议
- 小数据量(< 1 万行) :两者性能差异可忽略,优先选择
FIND_IN_SET保证语义正确。 - 大数据量且不能改表结构 :如果只做精确匹配,用
FIND_IN_SET;如果允许少量误匹配且追求性能,用LIKE '%value%'(但仍是全扫描)。 - 追求极致性能 :必须将逗号分隔字段规范化为关联表 ,然后使用等值查询(
WHERE course = 'course3'),利用索引加速。 - 跨数据库兼容 :
FIND_IN_SET是 MySQL 和 Hive 特有函数,其他数据库(PostgreSQL、SQL Server、Oracle)需用LIKE模拟或内置函数(如STRING_SPLIT)。