
很多开发者第一次接触 LIKE是在写“模糊查询”的时候输入一个关键词把包含它的记录全部捞出来。这个需求太常见了常见到我们几乎不会停下来想一个问题——LIKE 真的是实现模糊匹配的最好方案吗它有哪些容易踩的坑为什么有时候一条 LIKE 查询能把数据库拖慢好几个数量级为什么同一个 LIKE 在 MySQL 和 SQL Server 里表现还不一样这些问题才是这篇文章真正想讲清楚的东西。不是简单罗列 LIKE 的语法而是把 LIKE 放在真实的数据库管理系统场景里从基础用法、通配符语义、大小写规则、ESCAPE 转义一路讲到性能优化、SQL 注入风险和替代方案。无论你刚学 SQL还是在生产环境里被慢查询折磨过这篇文章都值得读完并收藏。1. 这篇文章真正要解决的问题先把话说在前面SQL 里的 LIKE 远不止“加两个百分号”那么简单。很多初学者以为 LIKE 就是WHERE name LIKE %关键词%写完能出结果就算会了。但实际工作中围绕 LIKE 的问题几乎都是下面这几种查出来的结果不对明明数据存在LIKE 却匹配不到或者匹配到了不该匹配的比如大小写、空格、通配符被当成普通字符。查询慢到无法接受一张几百万行的表LIKE %关键词%把全表扫了一遍接口直接超时。拼接 SQL 导致安全风险用户输入被直接拼进 LIKE 条件等于给 SQL 注入开了门。不同数据库行为不一致同样的写法在 MySQL 里没问题换到 SQL Server 或 PostgreSQL 就踩坑。这篇文章会对上述问题逐一拆解。你不是来这里背语法的你是来弄清楚 LIKE 怎么用才不出错的。读完以后你应该能回答三个问题什么场景该用 LIKE用的时候要注意什么当 LIKE 不够用时有什么更好的替代方案2. LIKE 的基础概念与适用场景2.1 什么是 LIKELIKE 是 SQL 标准中用于字符串模式匹配的操作符通常放在WHERE子句里配合通配符实现“模糊匹配”。它的核心作用可以概括为一句话判断某个字段的值是否符合指定的模式。与之对应的是精确匹配。举个例子WHERE name 张三只返回姓名恰好是“张三”的记录。WHERE name LIKE 张%返回所有姓“张”的人。这两者的差别就是程序世界里“等值判断”和“模式判断”的差别。关心的是“你是不是这个值”LIKE 关心的是“你符不符合我描述的形态”。2.2 LIKE 解决什么问题在真实业务里用户很少能给出完整准确的值。搜索框里输入“华为”可能想找的是“华为Mate 60 Pro”“华为笔记本”“华为云服务器”。这种前缀匹配prefix matching用根本写不出来而 LIKE 可以。再比如手机号中间四位做脱敏查询、订单号模糊搜索、日志表里按关键字过滤消息这些都是 LIKE 的典型应用场景。它解决的是数据库管理系统中“不确定完整值”的匹配问题是精确匹配的有力补充。2.3 什么样的场景最适合 LIKE从实际工程经验来看LIKE 最适合下面几类场景数据量可控的模糊搜索。比如一张配置表、用户表量级在几十万以内LIKE 前缀匹配配合索引可以接受。需要兼容多种数据库的简单模糊查询。LIKE 是标准 SQL 能力MySQL、Oracle、SQL Server、PostgreSQL 全都支持。模式匹配逻辑简单不需要正则表达式那么强的表达能力。作为全文检索的降级方案。数据量没到那个量级没必要引入 ElasticsearchLIKE 够用就先凑合。但也要泼一盆冷水LIKE 并不适合所有模糊搜索场景尤其是你对查询性能和匹配精度都有要求的时候。后面第五、第六部分会专门讲这个问题。3. LIKE 语法与通配符详解3.1 LIKE 基本语法LIKE 的语法非常简单标准的 SQL 写法是SELECT 列名 FROM 表名 WHERE 列名 LIKE 模式;模式pattern是一个字符串里面可以包含通配符。SQL 标准里有两个核心通配符%和_。3.2 通配符 % 的含义与用法%表示匹配任意长度包括 0的任意字符序列。模式含义匹配示例张%以“张”开头张三、张伟、张无忌%张以“张”结尾老张如果姓在后、组合张%张%包含“张”张三、老张头、小张三张%三以“张”开头以“三”结尾张三、张飞三但要求同一字段连续满足注意一个细节张%会匹配“张”本身因为%可以代表 0 个字符。这在很多面试题里出现过属于基础但容易忽略的点。3.3 通配符 _ 的含义与用法_表示匹配单个任意字符。它和%的区别关键在于“量”的精确限制。模式含义匹配示例张_“张”后面恰好一个字符张三、张四不匹配“张伟强”张__“张”后面恰好两个字符张伟强、张小美不匹配“张三”_张%第二个字符是“张”老张、小张不匹配“张伟”实际使用中_的使用频率远低于%因为业务上“精确控制一个字符长度”的需求不算多。但在字符串格式校验类场景里_很有用比如校验手机号前三位和后四位。理解%和_的最佳方式是把它们类比为正则表达式中的.*和.。虽然语义不完全等价正则里.*可以匹配换行符之外任意字符SQL 里%匹配的字符集跟排序规则有关但思维模型是一样的一个是“任意长度”一个是“单个位置”。3.4 不同数据库中的 LIKE 通配符差异%和_在 SQL 标准里是通用的这点必须记住。但不同数据库管理系统在细节上有差异MySQL默认情况下LIKE不区分大小写取决于表的排序规则通常为utf8_general_ci或utf8mb4_general_ci_ci 结尾就是不区分大小写。另外 MySQL 还支持非标准的ESCAPE子句来指定转义字符。PostgreSQLLIKE区分大小写但提供了ILIKE做不区分大小写的匹配。还支持SIMILAR TO和正则表达式函数。SQL ServerLIKE支持标准通配符还额外支持[a-z]字符集匹配和[^a-z]反向匹配。这是非常实用的增强能力。OracleLIKE区分大小写可以使用UPPER(列名) LIKE UPPER(%关键词%)来做不区分大小写的查询。这段内容先留个印象后面第四部分会通过代码示例体现这些差异。3.5 ESCAPE 子句如何匹配通配符本身如果用户搜索的关键词里恰好包含%或_怎么处理比如要查名字叫“100%纯棉”的商品LIKE %100%%%就会让数据库糊涂。答案是用ESCAPE子句指定一个转义字符。默认的转义字符可以不用写但需要显式声明-- 查询商品名称中包含“100%”的记录 SELECT * FROM products WHERE product_name LIKE %100\%% ESCAPE \;这段 SQL 的含义是\后面的%是普通字符而不是通配符。第一个%和最后一个%仍然是通配符。这个细节在实际项目中很容易踩坑。用户输入搜索关键词时程序必须对用户输入里的%、_、\做转义处理否则用户搜“100%棉”会返回一堆奇怪的数据。下面给一个 Java 后端转义 LIKE 关键字的参考实现public static String escapeLike(String input) { if (input null) { return null; } return input .replace(\\, \\\\) .replace(%, \\%) .replace(_, \\_); }然后在拼接 SQL 时使用ESCAPE \。这块如果不处理开发环境里测试数据少问题不明显一到生产环境用户乱输入就开始出问题了。4. LIKE 完整示例与代码实现4.1 环境准备为了让示例真实可跑本文以 MySQL 8.0 作为主要演示环境同时标注 SQL Server 和 PostgreSQL 的差异点。一个简单的学生表 成绩表就够用了。如果你本地没有 MySQL可以用 Docker 快速起一个docker run --name mysql-like-demo \ -e MYSQL_ROOT_PASSWORDroot123 \ -e MYSQL_DATABASEstudent_db \ -p 3306:3306 \ -d mysql:8.04.2 建表与初始化数据-- 文件路径01_create_table.sql CREATE DATABASE IF NOT EXISTS student_db; USE student_db; DROP TABLE IF EXISTS student; CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, student_no VARCHAR(20) NOT NULL, email VARCHAR(100), enroll_year INT );先插入一批测试数据覆盖各种边界情况-- 文件路径02_insert_data.sql INSERT INTO student (name, student_no, email, enroll_year) VALUES (张三, 20230001, zhangsanexample.com, 2023), (张三丰, 20220015, zhangfengexample.com, 2022), (张伟, 20210088, zhangweiexample.com, 2021), (李四, 20230022, lisiexample.com, 2023), (王五, 20200033, wangwuexample.com, 2020), (张小明, 20230056, zhangxiaomingexample.com, 2023), (陈静, 20210077, chenjingexample.com, 2021), (李静怡, 20220099, lijingyiexample.com, 2022), (张静, 20230101, zhangjingexample.com, 2023), (100%纯棉短袖, P2023001, productexample.com, 2023);注意最后一条故意插入一个包含%的字段值用来演示 ESCAPE 转义。4.3 基础 LIKE 查询示例示例 1查询所有姓“张”的学生SELECT id, name, student_no, enroll_year FROM student WHERE name LIKE 张% ORDER BY enroll_year;这条 SQL 的语义是“以张开头”匹配结果包括张三、张三丰、张伟、张小明、张静。如果表里有人叫“老张”不会被匹配到。示例 2查询姓名中包含“静”的学生SELECT id, name, student_no, enroll_year FROM student WHERE name LIKE %静%;匹配结果陈静、李静怡、张静。这条 SQL 是典型的包含匹配也是实际业务中最常见的用法。示例 3查询姓“张”且名字一共两个字的学生SELECT id, name, student_no, enroll_year FROM student WHERE name LIKE 张_;匹配结果只有“张三”和“张静”因为它们名字是两个字符且第一个字符是“张”。“张小明”是三个字匹配不了。4.4 LIKE 与 ESCAPE 转义示例示例 4查询商品名称中包含“100%”的记录SELECT id, name FROM student WHERE name LIKE %100\%% ESCAPE \;这里的关键在于ESCAPE \告诉数据库反斜杠是转义字符。第一个%和最后一个%是通配符中间\%是真实存在的百分号字符。如果不加 ESCAPE写成LIKE %100%%%数据库会把它解析成“包含100”后面任意字符结果完全不可控。示例 5SQL Server 中匹配以任意字符开头、后面是“张”的写法-- SQL Server 兼容写法 SELECT id, name FROM student WHERE name LIKE _[张]%;在 SQL Server 中[张]表示匹配单个字符集合中的任意一个。这个语法在 MySQL 里不生效MySQL 的 LIKE 不直接支持字符集合写法。如果你在 SQL Server 环境使用这行代码可以更精确地表达“第二个字符是张”的语义。4.5 NOT LIKE 与组合条件LIKE 也可以配合NOT使用-- 查询姓名中不包含“张”的所有学生 SELECT id, name FROM student WHERE name NOT LIKE %张%;还可以和AND、OR组合-- 查询 2023 年入学且姓名以“张”开头的学生 SELECT id, name, enroll_year FROM student WHERE enroll_year 2023 AND name LIKE 张%;这些都是非常常见的业务组合逻辑上并不复杂重点是避免在组合条件里写出互相矛盾的过滤逻辑。4.6 如何运行和验证如果你用的是 MySQL 客户端mysql -uroot -p root123 -h 127.0.0.1 01_create_table.sql mysql -uroot -p root123 -h 127.0.0.1 student_db 02_insert_data.sql然后进入交互式客户端逐条执行查询语句观察返回结果是否符合预期。验证的重点不是“有没有结果”而是“结果是不是多出来了”或“少掉了”。多出来往往是通配符误用了%少掉了往往是大小写或排序规则的问题。4.7 运行结果与效果验证以示例 1 为例预期输出大致如下------------------------------------- | id | name | student_no | enroll_year | ------------------------------------- | 1 | 张三 | 20230001 | 2023 | | 2 | 张三丰 | 20220015 | 2022 | | 3 | 张伟 | 20210088 | 2021 | | 6 | 张小明 | 20230056 | 2023 | | 9 | 张静 | 20230101 | 2023 | -------------------------------------判断成功的标准很简单返回的行和预期数据一致不包含“老张”“张二丰他爹”这类不符合前缀规则的记录。如果结果不对优先按下面顺序排查检查表里的真实数据确认自己以为的数据和实际存储的数据一致。检查字段排序规则collation确认是不是大小写或全角半角导致匹配失败。去掉%缩小范围先跑一个精确匹配确认命令本身没有拼错。5. LIKE 与性能为什么 % 开头会让查询变慢5.1 从全表扫描开始说起LIKE 最大的性能杀手是前导通配符也就是LIKE %关键词或LIKE %关键词%这种写法。原因要从 B 树索引的结构说起。数据库索引本质上是按照字段值排序后建立的一种快速查找结构。对于普通索引在条件col 关键词时数据库可以直接从索引树的根节点一路找到叶子节点定位到匹配记录。这就是索引带来的加速效果。但是对于LIKE %关键词%%在开头数据库不知道关键词后面是什么更不知道前面是什么所以无法利用索引进行范围扫描。它只能老老实实地从表的第一个数据块开始逐行读取、逐行尝试匹配这就是全表扫描Full Table Scan / Sequential Scan。数据量小的时候无所谓几千条记录扫就扫了。但到了千万级别全表扫描的磁盘 I/O 和 CPU 开销会迅速放大一条 LIKE 查询拖垮一个接口的事情在真实项目里并不少见。这也是“慢 SQL 优化”话题中LIKE %x%长期霸榜的原因。5.2 哪一种 LIKE 写法能走索引在 MySQL 的 InnoDB 存储引擎下规则大致如下LIKE 写法是否能走索引原因LIKE 关键词%可以前缀是确定的可以用索引范围扫描LIKE %关键词基本不行前缀通配需要全表扫描LIKE %关键词%基本不行前后都通配需要全表扫描LIKE _关键词%基本不行_本身也可能让前缀不确定这里的“基本不行”是有例外的比如 MySQL 的 ICPIndex Condition Pushdown和覆盖索引可以带来部分优化但整体结论仍然稳健前导通配符会让索引失效。5.3 查看执行计划在任何数据库里遇到 LIKE 查询变慢第一步都是看执行计划。MySQL 查看方式EXPLAIN SELECT id, name FROM student WHERE name LIKE 张%;重点关注type列。如果看到ALL说明是全表扫描如果看到range或ref说明走的是索引范围扫描。对于EXPLAIN结果有一个常见误区是只要看到key列有值就以为走了索引。实际上type ALL时即使key列有值也往往只是用到覆盖索引做扫描并没有真正利用索引加速定位。更稳妥的判断指标是看rows列是否明显小于总行数。5.4 利用前缀索引或生成列优化如果业务一定要做包含匹配但数据量又大有几种替代思路。方案一把后缀匹配转换成前缀匹配。比如要查询LIKE %abc可以在表中增加一个反转列reversed_col存储原字段反转后的值并给反转列建索引。查询时改成WHERE reversed_col LIKE cba%因为反转后的数据前缀是确定的就可以走索引了。MySQL 8.0 支持函数索引Functional Index可以直接给REVERSE(col)建立索引。SQL Server 支持计算列 索引。PostgreSQL 也有表达式索引。思路都是类似的让模糊匹配变成确定前缀的匹配。方案二在 PostgreSQL 中使用 pg_trgm 扩展通过 GIN 索引支持LIKE %关键词%和ILIKE %关键词%。这种方法利用了三元组trigram的特性可以在大量文本里做高效的相似度匹配。方案三如果业务是全文搜索且数据量大、更新频繁可以考虑 MySQL 全文索引FULLTEXT、PostgreSQL 全文搜索或者引入 Elasticsearch 这类专门做检索的中间件。LIKE 在这种场景下的角色应该是“兜底方案”而不是主角。6. LIKE 与 SQL 注入安全风险边界6.1 SQL 注入为什么会和 LIKE 扯上关系在数据库管理系统的安全风险里SQL 注入是长期霸榜的高危问题。而 LIKE 又因为它的“拼接”特性特别容易被开发者写坏。最常见的问题代码长这样String keyword request.getParameter(keyword); String sql SELECT * FROM products WHERE product_name LIKE % keyword %; Statement stmt connection.createStatement(); ResultSet rs stmt.executeQuery(sql);这段代码的问题非常明显用户输入的keyword被直接拼进 SQL 字符串。如果用户输入; DROP TABLE products; --拼接后的 SQL 就变成了SELECT * FROM products WHERE product_name LIKE %; DROP TABLE products; --%攻击者利用注释符把后面的内容注释掉再插入一条破坏性 SQL。这不是理论上的恐吓这是 SQL 注入攻击里最经典的案例任何写过几年 SQL 的开发者都应该有敬畏之心。6.2 参数化查询是底线正确的写法是使用参数化查询PreparedStatementString keyword request.getParameter(keyword); String sql SELECT * FROM products WHERE product_name LIKE ?; PreparedStatement ps connection.prepareStatement(sql); ps.setString(1, % keyword %); ResultSet rs ps.executeQuery();参数化查询的思路是把 SQL 语句结构和用户输入的数据彻底分离数据库先把 SQL 模板解析好再把参数当作纯数据传入。这样用户输入里的任何内容都只是字符串的一部分不会改变 SQL 语句的语义。这里有一个细节要注意参数化查询解决了 SQL 注入问题但并没有解决 LIKE 通配符语义问题。如果用户输入%或_它们仍然会被当作通配符处理查询出来的结果可能超出预期。所以参数化查询之外还需要对用户输入做转义处理。两者是配合关系不是替代关系。6.3 其他安全注意事项最小权限原则执行 LIKE 查询的应用账号不应该拥有 DROP、DELETE 这类高权限。即使 SQL 被注入了破坏范围也有限。不要把用户输入直接拼进数据库对象名表名、列名包括 LIKE 里的表名。日志里不要打印完整的 SQL 拼接过后的语句防止敏感信息泄露也防止调试日志本身成为攻击面。如果提供的是 REST API 接口建议在参数校验层限制搜索关键词的长度比如最大 50 个字符避免恶意构造超长 LIKE 语句。7. 什么时候该换掉 LIKE用什么替代7.1 业务场景对匹配精度有更高要求LIKE 的能力边界很清晰只能做“包含”“前缀”“后缀”这类简单的模式匹配。如果业务需要正则表达式级别的匹配比如手机号格式校验、邮箱格式校验LIKE 就力不从心了。这种场景应该使用数据库自带的正则表达式能力MySQLREGEXP_LIKE()或REGEXPPostgreSQL~、~*或SIMILAR TOSQL Server较新版本支持LIKE但正则支持不如前两者需通过 CLR 或数据库层函数实现但正则表达式的代价是性能通常比 LIKE 更差尽量不要在核心查询路径上使用。7.2 全文检索场景如果业务是做文章搜索、商品描述搜索、日志内容搜索数据量在百万以上且需要分词、排序、高亮等功能LIKE 完全不合适。此时应该考虑MySQL FULLTEXT 索引配合MATCH ... AGAINST语法。PostgreSQL 全文搜索支持 tsvector、tsquery能处理中文分词配置。Elasticsearch适合独立搜索引擎架构功能最强大但也要额外维护一套集群。7.3 数据量较小且匹配简单时如果数据量只有几百条或几千条LIKE 仍然是最简单可靠的方案。不要为了追求“高级”而给一个小系统引入全文搜索引擎那是过度设计。开发效率和可维护性同样重要在数据量可控的范畴内LIKE 的可读性和通用性没有任何替代方案能比。7.4 一个实用的选型决策思路简单归纳一下场景推荐方案少量数据模糊查询LIKE大量数据前缀匹配LIKE 索引或函数索引大量数据包含匹配生成列 索引 / pg_trgm / 全文索引复杂模式校验正则表达式大规模全文搜索全文索引 / Elasticsearch关键词搜索 排序 高亮Elasticsearch 或专业搜索组件8. LIKE 常见问题与排查方法问题现象可能原因排查方式解决方案LIKE 查询结果为空但确认数据存在大小写不匹配或字段排序规则不同用SELECT直接查看原始数据检查字段 collation使用UPPER(col) LIKE UPPER(%关键词%)或调整排序规则匹配到了预期之外的数据%、_被当作通配符解释了检查用户输入是否包含通配符对%、_、\做转义配合ESCAPE子句查询非常慢%关键词%导致全表扫描执行EXPLAIN查看typeALL改为前缀匹配、增加生成列索引或换全文检索同样 SQL 在 MySQL 有结果、SQL Server 没有两库的排序规则和 LIKE 行为不同查看数据库 collation统一排序规则或显式使用COLLATE包含中文时匹配不到编码问题字符集不一致检查连接字符集和表字符集统一使用utf8mb4NOT LIKE 排除不掉某些记录字段值为 NULLNOT LIKE 对 NULL 返回 NULL检查空值数据条件中显式加AND col IS NOT NULL后端拼接 SQL 被注入用户输入直接拼入 SQL 语句检查代码中的字符串拼接使用参数化查询搜索关键词包含空格导致匹配异常字段存储了多余空格去掉首尾空格再比较存储时使用TRIM查询时同样处理9. 最佳实践与工程建议9.1 别让 LIKE 出现在核心链路的全表扫描里这是最重要的一条。凡是用户直接触发的查询路径都要对 LIKE 的写法保持敏感。能写成前缀匹配就尽量写前缀匹配。写不了就评估数据量数据量大就引入替代方案。不要把LIKE %关键词%当成万能工具。9.2 用户输入的转义不能省后端只要接受用户输入并用 LIKE 查询就必须考虑转义逻辑。写成一个公共工具方法所有模块复用而不是每个 SQL 里手写一遍转义逻辑。这个工具方法应该对\、%、_三个字符都做处理并搭配ESCAPE子句。9.3 索引设计要结合 LIKE 的实际使用如果业务里频繁出现WHERE name LIKE 张%那么给name字段建立普通 B 树索引就能生效。如果还有WHERE enroll_year 2023 AND name LIKE 张%更适合创建组合索引(enroll_year, name)注意列顺序。如果频繁出现反向前缀匹配LIKE %com可以创建函数索引比如 MySQL 8.0 的CREATE INDEX idx_reverse_email ON student ((REVERSE(email)))。这些索引设计的小技巧能让 LIKE 查询在不改变业务逻辑的情况下快一个数量级。9.4 把查询结果纳入监控和预警在慢 SQL 统计中LIKE 查询往往占比很高。建议在数据库端开启慢查询日志定期分析哪些 LIKE 查询耗时最长针对 Top N 做优化。优化的优先级建议参考“执行频率 x 单次耗时”这个指标而不是只看单条 SQL 的绝对耗时。对于频繁执行的关键 LIKE 查询还可以加一层缓存。但要注意缓存一致性数据更新后要主动失效别让用户看到旧数据这比缓存命中率更重要。9.5 写出可读的 SQL简单说几个 LIKE 相关 SQL 的代码规范通配符尽量写在参数一侧、拼接处集中管理不要散落在多个 SQL 里。使用ESCAPE \时保持一致性同一项目的转义风格统一。涉及相同模式的逻辑抽取成数据库视图或者后端公共查询方法避免重复写。在复杂查询里给 LIKE 条件加注释说明匹配意图方便后来的人维护。9.6 防止过度设计最后一条建议是别把简单问题复杂化。如果表只有几万行LIKE %关键词%也能跑得很快就没有必要为了“性能优化”去强行引入函数索引或者全文检索。工程上最重要的判断标准是当前的数据量、查询频率和用户体验决定当前需要什么方案。永远不要为了炫技而降低系统的可维护性。10. 总结与后续学习方向关于 LIKE核心的知识点可以压缩成三句话第一它是一个模式匹配操作符%表示任意长度_表示单个字符ESCAPE用来匹配通配符本身。第二它很好用但有性能边界和安全边界。前导通配符会让索引失效用户输入不转义会让 SQL 注入有机可乘。第三它不是唯一的模糊匹配方案。前缀匹配、函数索引、全文检索、正则表达式各自有各自的最佳场景选型要结合数据量和业务复杂度。如果你刚开始学 SQL可以沿着“LIKE 基础 → 通配符细节 → 执行计划 → 索引优化”这条线继续深入每一步都动手建表、造数据、跑查询。尤其建议自己造一张百万行的测试表看看LIKE 前缀%和LIKE %后缀的执行计划差异有多大。实践带来的体感比看十篇教程都更有用。如果你是在生产环境维护数据库可以把这篇文章里关于ESCAPE、参数化查询、函数索引的内容作为团队内部 review 的 checklist减少自己和同事在 LIKE 上栽跟头的概率。LKE 虽小却贯穿了 SQL 语法、索引原理、安全编码和查询优化好几个核心方向。把一个小知识点挖深了比囫囵吞枣刷一百个 SQL 面试题更有价值。下一步建议去查一下你正在用的数据库版本对照本文的示例实际跑一遍看看执行计划里到底发生了什么。