SQL 里的LIKE应该是接触数据库管理系统时最早遇到的查询关键字之一。很多教程会告诉你“%代表任意多个字符,_代表一个字符”,然后给两个例子就结束了。但实际在业务里写LIKE,会碰到大小写、转义、索引失效、慢查询、注入风险、通配符误匹配等一系列问题。这篇就把LIKE从语法到性能、从安全到批量任务完整拆一遍,重点看它能不能走索引、什么时候不能走、怎么在真实业务里安全地使用它。
这次我们只围绕一件事:LIKE的完整用法和工程化实践。看完你不仅能写对LIKE查询,还能理解LIKE在慢 SQL 优化和 SQL 注入防护这两个高频场景里的位置。
文章会按“核心语法 -> 适用场景 -> 环境准备 -> 功能测试 -> 性能分析 -> 接口/批量任务 -> 安全边界 -> 问题排查 -> 最佳实践”的顺序展开。每个部分都配可执行的 SQL 或代码示例,建议收藏备用。
1. LIKE 核心能力速览
| 能力项 | 说明 |
|---|---|
| 所属范畴 | 数据库管理系统中的条件查询关键字 |
| 核心功能 | 字符串模式匹配,支持通配符%和_ |
| 通配符 | %匹配任意长度字符串(含空串),_匹配单个字符 |
| 转义方式 | ESCAPE子句自定义转义字符 |
| 大小写敏感度 | 取决于数据库排序规则(collation),MySQL 默认不敏感,PostgreSQL 敏感 |
| 索引使用 | 前缀匹配(LIKE 'abc%')可走索引,前导通配符(LIKE '%abc')通常索引失效 |
| 替代方案 | LOCATE、INSTR、POSITION、全文索引、正则表达式 |
| 典型风险 | 前导通配符导致全表扫描;未转义的通配符导致 SQL 注入或误匹配 |
| 适合场景 | 模糊搜索、日志过滤、批量数据匹配、业务编码规则校验 |
LIKE功能很简单,真正的复杂度在“能不能走索引”“怎么防注入”“怎么批量执行”这些工程问题上。
2. 适用场景与使用边界
2.1 适合场景
LIKE最适合做模糊匹配,例如搜索用户名、商品名、订单号片段、文件名前缀。- 配合
ESCAPE可以做特殊字符精确包含查询,比如查询包含%或_的文本。 - 在数据清洗场景中,
LIKE常用来识别“包含某个关键字”的记录,再配合UPDATE或DELETE处理。 - 批量任务中,
LIKE常用于按模式筛选数据,例如筛选某时间段生成的文件名、某前缀的订单号。
2.2 不适合场景
- 大规模全文搜索。数据量超过百万级,
LIKE '%关键词%'几乎无法走索引,应该优先考虑全文索引或 Elasticsearch 等搜索引擎。 - 复杂模式匹配。如果需求是“匹配邮箱格式”“匹配手机号”,建议使用数据库的正则表达式函数。
- 高频核心路径查询。
LIKE的模糊匹配语义对索引不友好,核心接口应避免用前导通配符查询。
2.3 使用边界与安全提醒
- 涉及用户输入时,
LIKE的%和_需要特殊处理。如果直接拼接进 SQL,会带来 SQL 注入风险。 - 在涉及个人隐私、肖像、声音、文本等数据查询时,必须确认数据来源合法、使用范围合规。
- 使用
LIKE做数据匹配前,要确认字段的字符集和排序规则,否则可能出现中文匹配不到、大小写不敏感但预期敏感等问题。 - 禁止在未授权数据库上执行
LIKE扫描测试。所有测试应在本地测试库中进行。
3. 环境准备与 SQL 执行工具
LIKE的语法在所有主流数据库管理系统(MySQL、PostgreSQL、SQL Server、SQLite)中基本通用,以下验证方案以 MySQL 为主,兼容其他数据库。
3.1 环境检查清单
| 检查项 | 推荐要求 |
|---|---|
| 操作系统 | Windows / Linux / macOS 均可 |
| 数据库 | MySQL 5.7+ 或 MySQL 8.0+ |
| 客户端 | mysql 命令行或 DBeaver / Navicat |
| 测试库 | 单独的本地测试库,避免影响正式数据 |
| 字符集 | utf8mb4,避免中文乱码影响 LIKE 匹配结果 |
3.2 创建测试表并写入样例数据
先创建一个简单的用户表,用于后续所有 LIKE 测试:
CREATE DATABASE IF NOT EXISTS like_test DEFAULT CHARACTER SET utf8mb4; USE like_test; CREATE TABLE users ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, email VARCHAR(100), remark VARCHAR(200) ); INSERT INTO users (name, email, remark) VALUES ('Alice', 'alice@example.com', 'VIP member'), ('Bob', 'bob@example.com', 'normal user'), ('Charlie', 'charlie@test.org', 'VIP user'), ('David', 'david@example.net', 'test account'), ('Eve', 'eve@test.org', 'VIP'), ('Frank', 'frank@example.com', 'internal 100% system'), ('Grace', 'grace@test.org', 'admin_vip_user');插入完成后,先验证基础数据:
SELECT * FROM users;到这里,测试环境已经就绪。下面所有测试都基于该表。
4. LIKE 语法详解与功能测试
4.1 基础语法
LIKE的基本语法:
SELECT 字段列表 FROM 表名 WHERE 字段 LIKE 模式;模式中可以使用两个通配符:
| 通配符 | 含义 |
|---|---|
% | 匹配任意数量的字符,包括 0 个字符 |
_ | 匹配任意 1 个字符 |
测试用例 1:查询所有邮箱以example.com结尾的用户。
SELECT id, name, email FROM users WHERE email LIKE '%example.com';预期结果:Alice、Bob、Frank的邮箱都以example.com结尾,其余数据不返回。
测试用例 2:查询名字中第二个字符是a的用户。
SELECT id, name FROM users WHERE name LIKE '_a%';预期的_匹配任意一个字符,第二个字符为a,后续可以有任意字符。David符合条件,因为D是第一个字符,a是第二个字符。
测试用例 3:查询remark中同时包含VIP和user的记录。
SELECT * FROM users WHERE remark LIKE '%VIP%user%';这里隐式依赖顺序,只要文本中先出现VIP后出现user就可以匹配到。
4.2 转义通配符测试
业务中经常要查“包含百分号”或“包含下划线”的文本。因为%和_是通配符,必须使用ESCAPE指定转义字符,默认转义字符是反斜杠\,但不同数据库支持度不一样。推荐显式指定。
测试用例 4:查询remark中包含字面量100%的记录。
SELECT * FROM users WHERE remark LIKE '%100\%%' ESCAPE '\\';如果写成:
SELECT * FROM users WHERE remark LIKE '%100%%';会导致全表任意包含100的记录都匹配成功,而且行为不符合预期。
这里要注意,MySQL 默认转义字符是反斜杠,但如果sql_mode包含NO_BACKSLASH_ESCAPES,反斜杠不再作为转义符。最稳妥的方式是用ESCAPE显式指定一个字符,比如#:
SELECT * FROM users WHERE remark LIKE '%100#%%' ESCAPE '#';其中第一个#%表示字面量百分号,最后的%表示任意后续字符。
4.3 大小写敏感性测试
大小写是否敏感取决于排序规则。
在 MySQL 中:
utf8mb4_general_ci不区分大小写,LIKE 'alice%'能匹配Alice。utf8mb4_bin区分大小写,LIKE 'alice%'匹配不到Alice。
测试方法:
SELECT id, name FROM users WHERE name LIKE 'a%';如果结果包含Alice,说明当前排序规则不区分大小写。
在 PostgreSQL 中:
SELECT id, name FROM users WHERE name LIKE 'a%';默认LIKE区分大小写,Alice不会被匹配。如果要用不区分大小写的匹配,在 PostgreSQL 中使用ILIKE或转为小写。
在 SQL Server 中,大小写由数据库排序规则决定,通常安装时默认不区分。
4.4 反向匹配
NOT LIKE用于排除匹配某个模式的记录。
测试用例 5:查询邮箱不以example.com结尾的用户。
SELECT id, name, email FROM users WHERE email NOT LIKE '%example.com';注意NOT LIKE在字段为NULL时返回UNKNOWN,过滤后不会出现在结果中。如果遇到空值问题,需要补充IS NULL判断。
5. LIKE 性能分析与慢 SQL 排查
字符串匹配在数据量大的时候很容易变成慢 SQL。这一节重点讲LIKE的索引使用规则和排查方法。
5.1 什么情况下 LIKE 能走索引
结论是:只要模式以固定前缀开头,且匹配字段上有索引,LIKE就可以走索引。
用一个实验验证。先给email字段加索引:
ALTER TABLE users ADD INDEX idx_email (email);然后查看执行计划:
EXPLAIN SELECT * FROM users WHERE email LIKE 'alice%';如果执行计划中type为range,说明走了索引。前缀匹配alice%可以走索引。
再看前导通配符:
EXPLAIN SELECT * FROM users WHERE email LIKE '%alice%';此时执行计划中type大概率是ALL,也就是全表扫描。
5.2 为什么前导通配符会让索引失效
B+ 树索引是按字段值的从左到右顺序排列的。LIKE '%alice%'要求匹配任意位置出现的子串,索引无法定位起始点,只能逐行扫描。这是数据库管理系统索引结构的固有特性。
对比几种模式的索引使用情况:
| 模式 | 是否能走索引 | 原因 |
|---|---|---|
LIKE 'abc%' | 能 | 有固定前缀,索引可定位范围 |
LIKE '%abc' | 不能 | 无固定前缀 |
LIKE '%abc%' | 不能 | 无固定前缀,且中间匹配 |
LIKE 'abc_def%' | 能 | 前面部分固定,_不影响前缀定位 |
| 字段是函数表达式 | 不能 | 索引建立在原始字段上,函数包裹字段导致无法使用索引 |
5.3 慢 SQL 排查流程
当LIKE查询变慢时,按以下顺序排查:
- 查看执行计划,确认是否全表扫描。
- 确认模式是否包含前导
%。 - 确认字段上是否建立了合适的索引。
- 确认数据量和返回行数,是否只需要部分字段。
- 使用
EXPLAIN ANALYZE或EXPLAIN EXTENDED查看实际扫描行数。
MySQL 8.0 可以使用:
EXPLAIN ANALYZE SELECT * FROM users WHERE email LIKE '%alice%';5.4 索引失效的替代方案
如果必须做包含匹配,可以考虑以下方式:
- 使用覆盖索引,配合
INSTR函数。 - 使用全文索引,效果更好。
- 对经常需要模糊搜索的短文本字段,考虑引入 Elasticsearch。
- 如果数据量可控,可以使用
LOCATE('abc', email) > 0替代LIKE '%abc%',但本质上仍无法利用索引,只能减少部分解析开销。
INSTR示例:
SELECT * FROM users WHERE INSTR(email, 'alice') > 0;功能上等于LIKE '%alice%',但语义更明确。注意它依然不能走索引。
6. LIKE 在接口 API 与批量任务中的应用
LIKE单独使用价值有限,在实际工程中通常出现在接口筛选条件、存储过程和批量数据处理任务里。这一节给出可以直接套用的示例。
6.1 接口查询参数拼接
在 Java、Python、Node.js 后端中,模糊查询条件通常是接口参数。直接拼接字符串是非常危险的做法,必须使用参数化查询或 ORM 框架的查询构造器。
Python 示例:
import pymysql conn = pymysql.connect( host="127.0.0.1", user="root", password="your_password", database="like_test", charset="utf8mb4" ) keyword = "alice" with conn.cursor() as cursor: sql = "SELECT id, name, email FROM users WHERE email LIKE %s" cursor.execute(sql, (f"%{keyword}%",)) rows = cursor.fetchall() for row in rows: print(row) conn.close()注意,LIKE参数本身是%alice%,作为参数传给 SQL 语句,而不是拼进 SQL 字符串。这样即使keyword里包含%或_,也只是字符串数据,不会改变 SQL 语义。
6.2 存储过程中的 LIKE 使用
存储过程常用于批量数据处理,LIKE可以结合游标逐行匹配。
MySQL 存储过程示例:
DELIMITER // CREATE PROCEDURE find_users_by_keyword( IN p_keyword VARCHAR(100) ) BEGIN SELECT id, name, email FROM users WHERE email LIKE CONCAT('%', p_keyword, '%') OR name LIKE CONCAT('%', p_keyword, '%'); END // DELIMITER ; CALL find_users_by_keyword('alice');CONCAT('%', p_keyword, '%')在存储过程内部拼接模式字符串,不需要在调用端手动加百分号。
6.3 批量任务:按模式筛选并更新
假设要批量清洗数据,把所有remark包含 “VIP” 且邮箱为test.org的用户标记为“已通知”。
先确认筛选结果:
SELECT id, name, email, remark FROM users WHERE remark LIKE '%VIP%' AND email LIKE '%test.org';确认无误后再执行更新:
UPDATE users SET remark = CONCAT(remark, ' [notified]') WHERE remark LIKE '%VIP%' AND email LIKE '%test.org';批量任务执行前一定要先 SELECT 确认影响行数,再执行 UPDATE。
6.4 批量脚本与失败重试
在数据量较大时,建议使用脚本分批处理。Python 示例:
import pymysql import time conn = pymysql.connect( host="127.0.0.1", user="root", password="your_password", database="like_test", charset="utf8mb4" ) batch_size = 100 offset = 0 while True: with conn.cursor() as cursor: sql = """ SELECT id, name, email FROM users WHERE remark LIKE %s LIMIT %s OFFSET %s """ cursor.execute(sql, ("%VIP%", batch_size, offset)) rows = cursor.fetchall() if not rows: break for row in rows: # 在这里处理每一条记录 print(f"Processing id={row[0]}, name={row[1]}") offset += batch_size time.sleep(0.1) # 避免对数据库造成过大压力 conn.close()分页处理时,OFFSET数据量大会变慢,更优方案是使用游标或基于主键的范围分页。批量任务应加上try-except和失败重试,避免一条异常导致整个任务中断。
7. LIKE 与 SQL 注入防护
LIKE关键字最容易出现安全问题的场景有两个:一是通配符没有转义导致 SQL 注入,二是拼接字符串导致注入点扩大。
7.1 注入风险
如果业务代码这样做:
# 危险写法,不要使用 keyword = request.args.get("keyword", "") sql = f"SELECT * FROM users WHERE name LIKE '%{keyword}%'"攻击者传入%' OR 1=1 --之类的输入时,SQL 会被改写为:
SELECT * FROM users WHERE name LIKE '%%' OR 1=1 -- %'最终结果是绕过查询条件,返回全表数据。这在数据库管理系统中属于高危漏洞。
7.2 防护方案
第一,使用参数化查询,这是最基础的处理方式。
第二,如果必须转义用户输入中的特殊字符,需要在拼接模式之前处理:
def escape_like(keyword: str) -> str: return keyword.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_")使用时:
safe_keyword = escape_like(keyword) sql = "SELECT * FROM users WHERE name LIKE %s ESCAPE '\\\\'" cursor.execute(sql, (f"%{safe_keyword}%",))这样用户输入的%和_会被视为普通字符,不会被当成通配符。
7.3 安全使用清单
- 禁止字符串拼接 SQL。
- 所有用户输入必须通过参数绑定传入。
- 通配符必须按业务需求显式转义。
- 测试
LIKE查询时,至少测试包含%、_、单引号、反斜杠的输入。 - 数据库账号权限最小化,应用账号不应具备
DROP、TRUNCATE等高危权限。
8. LIKE 常见问题与排查方法
LIKE写法和查询并不复杂,但实际使用中经常遇到下面这些问题。
8.1 问题排查表
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 中文搜索不到记录 | 字符集或排序规则不一致 | 检查表和字段的 character set | 统一为 utf8mb4,重建测试数据 |
| 大小写与预期不符 | 排序规则为_ci(不区分大小写) | 查看 collation | 按需改用_bin或BINARY比较 |
%和_被当成通配符 | 没有转义 | 检查模式字符串 | 使用ESCAPE显式转义 |
| 查询很慢,全表扫描 | 前导%导致索引失效 | 使用EXPLAIN查看 type | 改为前缀匹配或引入全文索引 |
| 带空格的数据匹配不到 | 字段包含前后空格 | 使用CONCAT或TRIM检查 | 清洗数据或使用TRIM(name) |
NOT LIKE过滤掉 NULL 值 | SQL 三值逻辑 | 检查字段是否允许 NULL | 补充OR field IS NULL |
| 接口查询传入特殊字符报错 | 未转义单引号或反斜杠 | 查看数据库日志 | 使用参数化查询 |
LIKE匹配表情符号失败 | 字符集不支持 | 检查字符集 | 改为 utf8mb4 |
8.2 字符集问题
中文匹配不到,通常不是LIKE的问题,而是表或字段的字符集不是utf8mb4。检查方式:
SHOW CREATE TABLE users;如果看到latin1或gbk字符集,需要转字符集:
ALTER TABLE users CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;执行前先备份数据。
8.3 空格问题
LIKE 'alice%'无法匹配alice(带尾部空格)。排查时可以先看长度:
SELECT id, name, LENGTH(name) FROM users WHERE id = 1;如确认有空格,清洗数据:
UPDATE users SET name = TRIM(name) WHERE TRIM(name) <> name;9. LIKE 最佳实践与使用建议
9.1 SQL 编写建议
- 尽量使用前缀匹配。
LIKE 'abc%'比LIKE '%abc%'快很多,且可利用索引。 - 必须用包含匹配时,控制扫描范围,加上其他过滤条件缩小数据量。
- 模式字符串中如果包含用户输入,先转义
%、_、\。 NOT LIKE场景要警惕NULL值被过滤,按语义补条件。- 避免在
LIKE前使用函数,例如WHERE LOWER(name) LIKE '%abc%'会导致索引失效。
9.2 索引设计建议
- 高频前缀查询字段建议建立普通 B+ 树索引。
- 不要只为了
LIKE建立冗余索引,先分析是否真的高频。 - 如果高频
LIKE场景较多,考虑引入全文索引或外部搜索引擎。 - 联合索引要使用左前缀规则,
LIKE匹配字段放在联合索引靠后位置意义很小。
9.3 工程化建议
- 保留一份最小可运行测试表结构和样例数据,方便新同事快速理解
LIKE行为。 - 批量任务必须加日志。每处理一批记录输出时间、处理行数、失败行数。
- 批量任务要支持断点续跑。设计主键游标,不要单纯依赖
OFFSET。 - 接口服务中,模糊查询参数要做长度限制和字符白名单。
- 涉及人名的模糊搜索,建议结合排序规则明确大小写规则,避免业务反馈结果不稳定。
9.4 合规与授权提醒
- 数据库中的个人信息查询,必须有合法的业务授权,不得越权查询他人数据。
- 从公开网络搜索材料中截取的 SQL 示例,只用于本地学习,不用于生产环境。
- 在正式环境执行
UPDATE或DELETE前,务必先在同结构测试库验证。 - 发布或商用前,要对查询结果做人工复核,避免因
LIKE通配符误匹配导致数据泄露。
10. 总结与下一步
LIKE的语法本身是 SQL 入门中最简单的一环,但工程中的LIKE远不止一个关键字那么简单。它牵扯出三个核心问题:能不能走索引、能不能防注入、能不能应对批量数据。
最值得深入验证的功能是前缀匹配的索引效果。建一张百万行测试表,分别用LIKE 'alice%'和LIKE '%alice%'跑一遍EXPLAIN,实际观察type从range变成ALL,比死记硬背结论更有价值。
最容易踩的坑有两个:一是用户输入的通配符没有转义,导致LIKE变成万能匹配;二是在大表上用LIKE '%关键词%'做搜索,把数据库拖到全表扫描。这两个坑在面试题和实际故障中出现频率都很高,建议单独建立测试用例反复验证。
对于已经熟悉LIKE基本写法的读者,下一步可以尝试将LIKE查询改造成参数化接口,外加一个批量数据清洗脚本,跑通“传入关键字 -> 模糊匹配 -> 结果导出”的完整链路。这个链路在数据库管理系统类的日常开发任务中出现频率相当高。
如果你正在重温 SQL 基础,建议把这里提到的排序规则、索引失效、转义规则、注入防护四个知识点整理成自己的笔记。真正能区分新手和熟练开发者的,往往不是LIKE本身,而是这些围绕LIKE展开的边界情况。