尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

SQL LIKE 查询全解析:通配符、性能优化与防注入实践

SQL LIKE 查询全解析:通配符、性能优化与防注入实践 关于 SQL 中的 LIKE很多初学者容易陷入一个误区以为它只是“模糊查询”而已写个%关键词%就能走遍天下。但真正上过生产环境、写过慢查询排查报告、或者被 SQL 注入漏洞折腾过的人会明白LIKE 远不止“模糊匹配”这么简单——它既可以是快速筛选数据的利器也可能成为拖垮数据库性能的隐患甚至在你疏忽大意时变成攻击者绕过认证的入口。这篇文章会从 LIKE 的基础语法讲起把%、_、转义符这些通配符掰开揉碎再结合性能优化和安全性问题给出实际项目中可落地的建议。无论你是刚学 SQL 的学生、准备面试的求职者还是需要维护业务数据库的工程师这篇文章都值得收藏备用。1. 为什么 LIKE 值得你花时间系统学习在数据库管理系统中WHERE条件里的精确匹配用等号就能解决但现实业务里我们经常面临的是“不确定的查询条件”。典型的场景包括搜索框里输入“华为”期望查出“华为 Mate 60 Pro”“华为 MatePad”“华为智能手表”等所有相关商品。输入手机号后四位“8899”需要反查用户全号。统计所有以“2024”开头的订单编号。判断某个 URL 是否包含指定域名关键字。这些场景用完全无能为力而LIKE正是为这类“模式匹配”而生的操作符。它通过配合通配符wildcard让我们不需要知道完整值只需要描述出一段“模式”就能把符合条件的数据捞出来。但使用 LIKE 的代价也不小。如果对 LIKE 的执行原理不够了解随手写一个LIKE %关键字%放到几百万行的表上就可能导致一次全表扫描接口响应从 50 毫秒变成 5 秒。另一面如果把用户输入直接拼进 SQL 语句LIKE 关键字还会被用于构造注入 payload造成严重的数据安全事故。所以这篇关于 LIKE 的文章要解决的不只是“怎么写”还包括“什么时候用”“怎么写才快”“怎么写才安全”。2. LIKE 基础语法与核心概念先明确技术定义LIKE是 SQL 标准中的模式匹配运算符用于在WHERE子句中判断某个字段的值是否匹配指定的字符模式。它返回布尔结果匹配返回TRUE不匹配返回FALSE如果字段值为NULL则结果既不是TRUE也不是FALSE而是UNKNOWN。2.1 基本语法结构SELECT 列名 FROM 表名 WHERE 列名 LIKE 模式;当模式中不包含任何通配符时LIKE 的行为等同于。-- 等价于 WHERE name 张三 SELECT * FROM users WHERE name LIKE 张三;这一点常被忽略但理解它对后续排查“为什么 LIKE 没生效”很有帮助。2.2 LIKE 与等号、IN 的对比操作符匹配方式典型场景索引利用情况精确匹配查 id100 的记录支持索引性能最好IN枚举匹配查 id IN (1,2,3)支持索引性能较好LIKE模式匹配查 name LIKE 张%前缀匹配时可部分利用索引前后都带%时基本全表扫描这里要强调一个核心判断LIKE 不是不能走索引而是取决于通配符的位置。模式以固定前缀开头如张%数据库查询优化器有较大机会使用 B 树索引进行范围扫描而模式以%开头如%张因为无法确定起始位置大多数数据库会退化为全表扫描。这一点后面章节会专门展开。3. 通配符全面解析% 、_ 、转义符LIKE 模式匹配的核心在于通配符如果对通配符理解不准确很容易写出“查不到数据”或“查出脏数据”的语句。3.1 百分号%%匹配任意数量的字符包括零个字符。-- 找出所有以张开头的姓名 SELECT * FROM users WHERE name LIKE 张%; -- 找出所有包含工程师的职位名称 SELECT * FROM jobs WHERE title LIKE %工程师%; -- 找出所有以.com结尾的邮箱 SELECT * FROM users WHERE email LIKE %.com;需要特别注意的是%可以匹配零个字符所以LIKE 张%也能匹配“张”这个单字。LIKE %%表示匹配所有非 NULL 的值这在实际代码 review 中属于典型的无意义条件。3.2 下划线__精确匹配一个字符。它比%严格得多一个_对应且仅对应一个字符。-- 匹配以张开头后面紧跟两个字的姓名 SELECT * FROM users WHERE name LIKE 张__; -- 匹配手机号第二位是5的记录 SELECT * FROM users WHERE phone LIKE _5%;新手最常见的错误是把_和%混用比如想匹配“张”开头的所有名字却写成了LIKE 张_结果只返回了两个字的名字漏掉了三个字以上的记录。3.3 中括号[]与排除符[^]SQL Server 等数据库支持在 SQL Server 中LIKE 还支持字符列表匹配。-- 匹配以 A、B 或 C 开头的产品代码 SELECT * FROM products WHERE product_code LIKE [ABC]%; -- 匹配 NOT 以 A、B、C 开头的产品代码 SELECT * FROM products WHERE product_code LIKE [^ABC]%;需要注意这个语法不是所有数据库都支持。MySQL 中[ABC]会被当作普通字符处理PostgreSQL 则需要通过正则表达式或SIMILAR TO实现类似效果。跨数据库迁移时这里是一个容易踩坑的兼容性问题。3.4 转义符与特殊字符匹配如果业务数据本身包含%或_直接写LIKE %50%会把“50%”当作任意字符加“50”加任意字符结果会出乎意料。此时需要使用ESCAPE子句指定转义符号。-- 在 MySQL / PostgreSQL / SQL Server 中通用写法 SELECT * FROM products WHERE discount_rate LIKE 50!% ESCAPE !;上述语句中!被定义为转义符!%表示真正的百分号字符而不是通配符。同理!_表示真正的下划线。判断是否应该转义的规则很简单当搜索关键字本身含有%、_或自定义转义符时必须先转义再拼接模式。4. 完整示例从单表查询到多条件组合为了让案例可以实际运行我们模拟一个电商订单表。假设表结构如下CREATE TABLE orders ( order_id INT PRIMARY KEY, customer_name VARCHAR(50), product_name VARCHAR(100), order_no VARCHAR(50), order_status VARCHAR(20) );插入几条测试数据INSERT INTO orders VALUES (1, 张三, 华为 Mate 60 Pro, 20240101001, PAID), (2, 李四, 苹果 iPhone 15, 20240201002, UNPAID), (3, 张伟, 华为 MatePad, 20240301003, SHIPPED), (4, 王五, 小米电视, 20240401004, PAID), (5, 张芳, 华为智能手表, 20240501005, CANCELLED);4.1 场景一搜索框前缀匹配业务需求是搜索“华为”相关商品搜索框输入“华为”后后端应查询 product_name 以“华为”开头的记录。此时应使用前缀匹配SELECT order_id, customer_name, product_name FROM orders WHERE product_name LIKE 华为%;执行结果会返回订单 1、3、5。这种写法是 LIKE 查询中对索引最友好的形式。4.2 场景二任意位置模糊匹配如果业务要求搜索所有包含“Mate”的商品因为“Mate”可能出现在品牌名之后也可能出现在名称末尾所以需要前后都加%SELECT order_id, customer_name, product_name FROM orders WHERE product_name LIKE %Mate%;这种写法会返回“华为 Mate 60 Pro”和“华为 MatePad”但数据库大概率需要扫描整张表的 product_name 列。数据量小时无所谓数据量大了就要考虑性能预案。4.3 场景三按订单号后缀匹配订单号是20240101001业务需要查所有以001结尾的订单。此时通配符在前是典型的“无法走索引”查询SELECT * FROM orders WHERE order_no LIKE %001;如果订单号表数据极大建议额外存储“订单号倒序”字段或者使用数据库的生成列Generated Column来优化否则每次查询都是全表扫描。4.4 场景四多条件组合查询LIKE 可以和AND、OR、IN、BETWEEN组合使用用于构建复杂的业务筛选逻辑。SELECT order_id, customer_name, product_name, order_status FROM orders WHERE customer_name LIKE 张% AND product_name LIKE %Mate% AND order_status IN (PAID, SHIPPED);这里要注意 AND 与 OR 的优先级AND优先级高于OR。如果条件中混合了 LIKE、AND、OR务必用括号明确分组否则结果可能不符合预期。-- 推荐写法用括号明确 OR 分组 SELECT * FROM orders WHERE product_name LIKE 华为% OR (product_name LIKE %电视% AND order_status PAID);4.5 场景五大小写与字符集问题不同数据库对 LIKE 的大小写敏感性不同MySQL 默认排序规则collation不区分大小写LIKE huawei%和LIKE Huawei%结果一致。PostgreSQL 默认区分大小写需要写成LIKE Huawei%或使用ILIKE。SQL Server 取决于数据库或列的排序规则。如果生产环境需要强制忽略大小写在 MySQL 中可以给字段指定utf8mb4_general_ci排序规则在 PostgreSQL 中可以直接使用ILIKE。5. LIKE 的隐藏角色SQL 注入入口这是很多教程不会细讲但实际项目中极其重要的部分。热搜词中出现了“sql注入万能密码绕过”它与 LIKE 有密切关系。在网络搜索材料中可以看到一种经典的万能密码绕过姿势是登录 SQL 为SELECT * FROM users WHERE username admin AND password xxx;攻击者如果知道背后的 LIKE 查询逻辑就能通过输入 OR 11 --这类内容改变原查询语义。但这里有一个更常见的 LIKE 注入场景搜索接口。假设后端代码写的是String sql SELECT * FROM products WHERE product_name LIKE % keyword %;攻击者在搜索框输入% OR 11 --拼接后 SQL 变成SELECT * FROM products WHERE product_name LIKE %% OR 11 -- %;OR 11会让 WHERE 条件恒真相当于一次全表数据泄出。如果数据库用户权限过大甚至可以用UNION SELECT读取其他表的数据。所以关于 LIKE 的安全性必须记住两条铁律永远不要直接拼接用户输入构造 LIKE 模式。即使用户只输入普通文本也要对%、_、\等特殊字符做转义处理。更推荐的做法是使用参数化查询。以 Java 的 PreparedStatement 为例// 1. 先对用户输入做转义 String keyword userInput .replace(!, !!) .replace(%, !%) .replace(_, !_); // 2. 构造模式 String pattern % keyword %; // 3. 使用参数化查询 String sql SELECT * FROM products WHERE product_name LIKE ? ESCAPE !; PreparedStatement ps conn.prepareStatement(sql); ps.setString(1, pattern); ResultSet rs ps.executeQuery();用 Python MySQL 的写法类似cursor conn.cursor(preparedTrue) sql SELECT * FROM products WHERE product_name LIKE %s ESCAPE ! pattern f%{escape_like(keyword)}% cursor.execute(sql, (pattern,))只有把输入当作参数传递数据库驱动才会对输入做安全处理。LIKE 模式中的通配符则需要我们自己在业务层管理这恰恰是模糊查询安全性最容易失控的地方。6. LIKE 与索引性能分析前面多次提到 LIKE 和索引的关系这里展开讲清楚原理和优化手段。6.1 为什么前缀匹配能走索引数据库的 B 树索引结构是按列的值排序存储的。当模式是abc%时查询优化器知道所有符合条件的记录在索引中会连续分布在abc到abd之间于是可以在索引上进行范围扫描定位到起始位置后顺序读取性能接近等值查询。6.2 为什么%abc%会全表扫描模式%abc%无法确定起始值位置优化器只能遍历整张表的每一行检查每个字段值是否包含abc。这就是经典的“不能使用索引”的场景。数据量上百万之后这类查询的延迟会显著增大。6.3 场景实测思路可以先用EXPLAIN观察执行计划。以 MySQL 为例EXPLAIN SELECT * FROM orders WHERE product_name LIKE 华为%; EXPLAIN SELECT * FROM orders WHERE product_name LIKE %Mate%;EXPLAIN输出中type列如果从range变成ALL就说明查询从索引范围扫描退化成了全表扫描。rows列的估算值也能直观看出扫描行数的差异。6.4 LIKE 性能优化常用方案针对无法避免的%关键字%查询业界常见的优化手段有以下几种方案思路适用场景注意点前缀匹配改写尽量让模式以固定前缀开头业务允许只按前缀搜索改变业务语义需评估覆盖索引查询列都包含在索引中查询字段较少减少回表开销全文索引使用 FULLTEXT 索引或 ES搜索内容较长、词频明显语法不同需维护分词生成列把反转字段存入生成列查后缀走前缀索引订单号、手机号后缀查询存储成本增加引入搜索引擎数据同步到 Elasticsearch复杂搜索、高并发增加运维成本一个典型的生成列优化例子订单需要频繁按订单号后缀查询可以在建表时增加一个order_no_rev存储REVERSE(order_no)查询时把LIKE %001改写为LIKE 100%。ALTER TABLE orders ADD COLUMN order_no_rev VARCHAR(50) GENERATED ALWAYS AS (REVERSE(order_no)) STORED; CREATE INDEX idx_order_no_rev ON orders(order_no_rev); SELECT * FROM orders WHERE order_no_rev LIKE CONCAT(REVERSE(001), %);这类优化适合查询量大的核心业务表小表没必要这么复杂。6.5 慢日志与 LIKE 排查如果线上出现 LIKE 慢查询第一步不是急着改 SQL而是确认慢在哪。用 MySQL 慢查询日志或performance_schema找到对应 SQL 后重点看两个指标扫描行数和返回行数。如果扫描行数是返回行数的几十上百倍说明该 SQL 在 LIKE 部分消耗了大量无效 IO优先考虑改写或加索引。7. 常见问题与排查方法结合 LIKE 使用中的高频问题整理成排查表格方便读者直接对照。问题现象可能原因排查方式解决方案LIKE 查不出预期数据模式中%或_被当作通配符使用检查搜索词是否含特殊字符使用 ESCAPE 转义只匹配到单字匹配不到多字_数量写错每个_只代表一个字符检查模式中下划线个数改用%表示任意长度查询很慢接口超时模式以%开头全表扫描用 EXPLAIN 查看 type 列改写为前缀匹配或引入索引/搜索引擎大小写不一致导致漏数据数据库排序规则区分大小写查数据库 collation 配置统一排序规则或使用 ILIKE搜索框输入特殊字符报错通配符未转义或单引号未处理查看详细报错信息参数化查询 转义处理LIKE 结果包含了 NULL 行但显示为空WHERE 条件对 NULL 判断为 UNKNOWN检查表中数据是否为空用 IS NULL / IS NOT NULL 单独处理高并发搜索拖垮数据库大量%关键字%查询同时发生监控慢查询数和 QPS加缓存或引入搜索引擎跨数据库迁移后 LIKE 行为不同不同数据库对大小写、字符列表语法支持不同查询目标数据库文档统一切换到标准语法补充一个经常被忽略的点LIKE对NULL字段永远不返回TRUE。所以写法是WHERE remark LIKE %test%时remark为 NULL 的记录不会出现在结果集里。如果业务上需要把 NULL 一起查出来就要写成SELECT * FROM products WHERE remark LIKE %test% OR remark IS NULL;8. 最佳实践与工程建议把 LIKE 使用经验收敛成几条能直接落到项目里的建议。8.1 查询模式设计不要什么都写成%关键字%。先分析业务场景能前缀匹配就前缀匹配。搜索“华为手机”如果业务上更希望返回“华为手机”开头的商品就优先使用LIKE 华为手机%。这种方式既快又准确。8.2 用户输入统一转义所有走到 LIKE 的用户输入统一走一个工具方法处理。不要在每个业务代码里各自实现避免漏转义。public static String escapeLike(String input) { if (input null) { return ; } return input .replace(!, !!) .replace(%, !%) .replace(_, !_); }这样在其他开发同事使用 LIKE 时只需要调用工具方法即可降低整体安全风险。8.3 禁止拼接 SQL这是所有 SQL 相关文章都要强调的红线。任何用户可控的输入一律使用参数化查询或 ORM 的绑定参数特性。MyBatis 中应使用#{}而不是${}select idsearchProducts resultTypeProduct SELECT * FROM products WHERE product_name LIKE CONCAT(%, #{keyword}, %) /select注意不要写成!-- 反例存在 SQL 注入风险 -- select idsearchProducts resultTypeProduct SELECT * FROM products WHERE product_name LIKE %${keyword}% /select8.4 约定字段顺序建立规范对核心表建议根据查询频率建立“搜索字段白名单”。例如商品名称允许 LIKE商品描述默认不参与 LIKE。业务上需要搜索描述的场景单独走搜索引擎避免每个查询都在全表重字段上扫描。8.5 大数据量下优先考虑搜索引擎当单表数据量达到千万级以上且 LIKE 无法命中索引时与其不断优化 SQL不如直接考虑引入 Elasticsearch。数据库负责事务和精确查询搜索引擎负责全文检索。这是目前中大型项目的通用做法。8.6 测试环境验证再上线任何涉及 LIKE 改写、加索引的操作都必须在测试环境用真实数据量验证。重点看执行计划是否从ALL变为range或ref以及接口 P95 延迟是否达到预期。先小流量放量再全量发布。9. 总结SQL 中的 LIKE 看似是一个基础操作符实际使用时却涉及语法细节、通配符语义、索引机制、安全防护等多个层面。这篇文章并没有停留在“怎么用 LIKE”的表面而是把 LIKE 放到真实业务环境中做了完整拆解前缀匹配走索引、%关键字%全表扫描、特殊字符需要转义、用户输入必须参数化。如果你手头正好在写或者 review 包含 LIKE 的查询逻辑建议回到自己的表结构上检查两个点第一模式是否以通配符开头第二用户输入是否走了安全的传参方式。接下来你可以继续深入的方向包括复合索引在多个查询条件下的选择逻辑、全文索引与 LIKE 的适用边界、以及不同数据库MySQL、PostgreSQL、SQL Server之间模糊查询语法的差异。从 LIKE 这个点切进去你其实已经开始理解数据库查询优化和系统安全设计这两座大山了。
返回列表