尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

MySQL LIKE模糊查询:从基础语法到性能优化全解析

MySQL LIKE模糊查询:从基础语法到性能优化全解析 1. 从“大海捞针”到“精准定位”为什么我们需要LIKE模糊查询在数据库的世界里数据就像一座巨大的图书馆。很多时候我们并不是拿着精确的索书号去找书而是凭着一些模糊的记忆“书名里好像有‘编程’两个字”、“作者姓‘张’”、“是关于‘2023年’的”。这时候精确匹配的操作符就束手无策了。LIKE操作符就是MySQL为我们提供的这把“模糊搜索”的钥匙它允许我们使用通配符来匹配符合特定模式的数据是数据检索中最常用、最基础但也最容易用错的功能之一。无论是用户在前端搜索框输入的关键词还是后台需要筛选特定格式的记录比如所有以.com结尾的邮箱LIKE都扮演着核心角色。它看似简单一个WHERE column LIKE ‘%pattern%’就搞定了但背后却涉及到查询性能、索引失效、字符集匹配等一系列深水区问题。很多开发者初期觉得它“好用”直到某天一张百万级数据表的模糊查询把数据库CPU打满才开始重新审视这个老朋友。今天我们就来彻底拆解MySQL中的LIKE模糊查询不仅要知道怎么用更要明白为什么这么用以及如何用得高效、安全。2. LIKE操作符的核心语法与两种通配符详解LIKE操作符的语法结构非常直观它用在WHERE子句中基本形式如下SELECT column1, column2, ... FROM table_name WHERE columnN LIKE pattern;这里的pattern模式是核心它决定了匹配的规则。LIKE支持两种主要的通配符%和_。理解它们的行为差异是正确使用模糊查询的第一步。2.1 百分号%代表零个、一个或多个字符%是“万用牌”它可以匹配任意长度的任意字符序列包括零个字符。你可以把它想象成搜索引擎中的“*”号。使用场景与示例以特定字符串开头查找所有姓“张”的用户。SELECT * FROM users WHERE name LIKE 张%;这会匹配“张三”、“张伟”、“张三丰”等。‘张%’表示第一个字必须是“张”后面可以是任何字符或没有字符。以特定字符串结尾查找所有使用公司邮箱的员工。SELECT * FROM employees WHERE email LIKE %company.com;这会匹配zhangsancompany.com、lisicompany.com。‘%company.com’表示前面可以是任意字符但必须以company.com结尾。包含特定字符串在产品描述中查找含有“防水”关键词的商品。SELECT * FROM products WHERE description LIKE %防水%;这是最常用也最需要警惕的用法。‘%防水%’表示在字符串的任意位置出现“防水”二字都会被匹配如“超强防水手机壳”、“不防水涂层说明”。组合使用查找文件名以“report_”开头以“.pdf”结尾的文件记录。SELECT * FROM documents WHERE file_name LIKE report_%.pdf;这会匹配report_2023_q1.pdf、report_final.pdf但不会匹配report.pdf因为_必须匹配一个字符见下文。2.2 下划线_代表恰好一个任意字符_是“填空牌”它严格匹配单个任意字符。一个_就代表一个字符位置。使用场景与示例固定格式的匹配查找所有手机号前三位为“138”的用户假设手机号字段为11位纯数字。SELECT * FROM customers WHERE phone LIKE 138________;这里用了8个下划线表示在“138”之后必须恰好有8个数字字符。这会精确匹配13800138000但不会匹配1380013800少一位或13800138000a最后一位不是数字。与%组合限定中间部分长度查找所有第二和第三个字符为“ab”的字符串。SELECT * FROM some_table WHERE code LIKE _ab%;这会匹配xabc、1ab123、cab但不会匹配ab123缺少第一个字符或aab第二个字符是‘a’不是‘b’。注意通配符就是普通的字符如果你想搜索的内容本身就包含%或_需要使用ESCAPE关键字来定义转义字符。例如查找包含“10%”折扣的字段SELECT * FROM promotions WHERE discount_text LIKE %10!%% ESCAPE !;这里指定!为转义符!%表示匹配字面量的百分号。3. 性能深渊LIKE查询的索引失效与优化策略这是LIKE模糊查询最关键的实战部分也是初级开发者最容易踩坑的地方。很多人写了WHERE name LIKE ‘%张%’后发现查询慢如蜗牛却不知其所以然。3.1 为什么LIKE ‘%xxx%’会导致索引失效数据库索引如B-Tree索引的工作原理类似于字典的拼音目录。它按照字段值的顺序存储可以快速定位到以某个值“开头”的数据。LIKE ‘张%’左匹配固定这相当于问“字典里所有拼音以‘zhang’开头的字在哪里”。数据库可以利用索引的有序性快速定位到第一个“张”开头的记录然后向后顺序扫描直到条件不满足为止。这种情况下索引通常是有效的前缀索引。LIKE ‘%张%’或LIKE ‘%张’左模糊这相当于问“字典里所有包含‘zhang’这个拼音片段的字在哪里”。索引目录对此无能为力因为它无法告诉你中间或结尾有什么。数据库只能退回到最原始的方式——全表扫描逐行检查每一行数据是否满足条件。当表数据量巨大时性能灾难就发生了。3.2 针对模糊查询的优化方案面对必须使用模糊查询的场景我们不能因噎废食而是需要一些策略来优化。方案一尽可能使用右模糊LIKE ‘张%’这是最有效的优化。在设计搜索功能时可以引导用户进行“前缀搜索”。例如在搜索联系人时输入“张”可以列出所有姓张的人这比直接搜“三”要高效得多。很多成熟的搜索框都会默认或推荐这种模式。方案二使用覆盖索引减少IO即使索引不能用于快速定位WHERE条件它仍然可以用于“覆盖查询”。如果查询的列都包含在某个索引中数据库可以直接从索引中读取数据避免回表查询数据行从而提升速度。-- 假设在 (name, id) 上建立了联合索引 SELECT id, name FROM users WHERE name LIKE %张%;在这个查询中id和name都在索引里引擎可能会选择扫描整个索引而不是整个表虽然还是扫描但索引文件通常比数据文件小IO代价更低。方案三使用全文索引FULLTEXT Index对于大文本字段如文章内容、产品描述的模糊搜索LIKE ‘%关键词%’是绝对的下策。MySQL提供了专门的全文索引来应对这种场景。创建全文索引仅适用于MyISAM和InnoDB存储引擎且MySQL 5.6的InnoDB才支持ALTER TABLE articles ADD FULLTEXT INDEX ft_idx_content (content);使用MATCH() ... AGAINST()进行搜索SELECT * FROM articles WHERE MATCH(content) AGAINST(防水 IN NATURAL LANGUAGE MODE);全文索引不仅速度快还支持自然语言模式、布尔模式等高级搜索能根据相关性排序是文本搜索的首选。方案四引入专业的搜索引擎对于海量数据、高并发、复杂条件的搜索需求如电商网站的商品搜索最终的解决方案是将数据同步到专业的搜索引擎中如Elasticsearch或Solr。这些搜索引擎专为全文检索设计支持分词、高亮、聚合、排序等复杂功能性能远超数据库自带的模糊查询。方案五函数索引与反向存储这是一种比较“黑科技”的思路。如果查询模式固定为LIKE ‘%xxx’右模糊可以考虑将字段值反转后存储并建立索引查询时也反转查询条件。-- 新增一个反向字段 ALTER TABLE users ADD COLUMN name_reverse VARCHAR(100) AS (REVERSE(name)) STORED; CREATE INDEX idx_name_reverse ON users(name_reverse); -- 查询以‘三’结尾的名字 SELECT * FROM users WHERE name_reverse LIKE REVERSE(三) %; -- 等价于 WHERE name LIKE %三这样就把右模糊转换成了左模糊可以利用索引。但这种方法增加了存储和维护成本需谨慎评估。4. 实战中的边界问题与避坑指南掌握了基本用法和性能优化在实际编码中还会遇到一些意想不到的“坑”。4.1 字符集与排序规则Collation的影响LIKE匹配的结果严重依赖于字段的字符集和排序规则。排序规则决定了字符比较的规则比如是否区分大小写、是否区分重音。区分大小写如果字段的排序规则是utf8mb4_bin二进制比较或xxx_csCase-Sensitive那么LIKE ‘a%’和LIKE ‘A%’会返回不同的结果。不区分大小写如果排序规则是utf8mb4_general_ci或utf8mb4_unicode_ciCase-Insensitive那么LIKE ‘a%’会匹配到以 ‘A’ 或 ‘a’ 开头的记录。特殊字符某些排序规则会将特定字符序列视为等价。例如在utf8mb4_unicode_ci下德语中的 ‘ß’ 可能与 ‘ss’ 等价。我的经验是在创建表时务必根据业务需求明确指定字符集和排序规则。对于大多数中文互联网应用使用utf8mb4字符集和utf8mb4_unicode_ci排序规则是通用且稳妥的选择它支持完整的Unicode包括Emoji且不区分大小写。如果业务上必须区分再考虑_bin或_cs规则。4.2 NULL值的处理LIKE对NULL值的处理是一个静默的陷阱。任何值与NULL进行LIKE比较结果都是NULL在WHERE条件中相当于FALSE。SELECT * FROM users WHERE name LIKE %张%;如果某条记录的name字段是NULL它将不会出现在结果集中。这符合SQL的三值逻辑但有时会被忽略导致数据统计不准确。如果你也需要找出NULL值必须显式添加OR column IS NULL条件。4.3 在编程语言中的参数化查询与注入风险这是安全层面的重中之重。绝对不要直接拼接用户输入到SQL语句中-- 危险SQL注入漏洞 String sql “SELECT * FROM products WHERE name LIKE ‘%” userInput “%’”;如果用户输入是‘ OR ‘1’‘1那么整个条件就会变成LIKE ‘%’ OR ‘1’‘1%’导致查询出所有数据甚至可能引发更严重的后果。正确的做法是使用参数化查询预编译语句在Java (JDBC)中String sql “SELECT * FROM products WHERE name LIKE ?”; PreparedStatement stmt connection.prepareStatement(sql); stmt.setString(1, “%” userInput “%”); // 通配符作为参数的一部分传入在Python (PyMySQL/pymysql)中sql “SELECT * FROM products WHERE name LIKE %s” cursor.execute(sql, (“%” user_input “%”,)) # 注意参数是元组在MyBatis#{}中select id“search” resultType“Product” SELECT * FROM products WHERE name LIKE CONCAT(‘%’, #{keyword}, ‘%’) /select注意这里使用的是#{}而非${}。#{}是预编译的安全的${}是字符串替换存在注入风险。网上热词中提到的#{}模糊查询指的就是这种安全的做法。参数化查询会将用户输入始终视为数据而非SQL代码的一部分从根本上杜绝了SQL注入。4.4 与正则表达式REGEXP/RLIKE的对比LIKE简单但功能有限。当模式更复杂时可以考虑使用REGEXP或同义词RLIKE。-- 查找名字以‘张’、‘李’或‘王’开头的人 SELECT * FROM users WHERE name REGEXP ‘^(张|李|王)’; -- 使用LIKE需要写多个OR -- 查找邮箱格式不正确的记录简单示例 SELECT * FROM users WHERE email NOT REGEXP ‘^[A-Za-z0-9._%-][A-Za-z0-9.-]\.[A-Za-z]{2,}$’;但是请注意REGEXP的功能强大得多但语法也更复杂性能通常比简单的LIKE更差尤其是在数据量大时。REGEXP同样无法使用标准B-Tree索引进行优化。在MySQL 8.0中可以针对REGEXP使用函数索引但LIKE在某些情况下左匹配固定可以利用索引。除非模式复杂到必须用正则否则优先使用LIKE。对于简单的“包含”、“开头”、“结尾”查询LIKE是更清晰、更可能被优化的选择。5. 进阶应用在存储过程、触发器和复杂查询中的实践LIKE不仅用于简单的SELECT它在数据库编程中也无处不在。5.1 在存储过程中进行动态模糊查询存储过程中可能需要根据传入参数进行灵活的模糊查询。这里的关键是安全地构建SQL字符串。DELIMITER // CREATE PROCEDURE SearchProducts(IN keyword VARCHAR(255)) BEGIN -- 使用CONCAT安全地构建模式注意参数过滤 SET pattern CONCAT(‘%’, REPLACE(keyword, ‘%’, ‘\%’), ‘%’); -- 使用用户定义变量和预处理语句防止注入 SET sql CONCAT(‘SELECT * FROM products WHERE name LIKE ? OR description LIKE ?’); PREPARE stmt FROM sql; SET kw pattern; EXECUTE stmt USING kw, kw; DEALLOCATE PREPARE stmt; END // DELIMITER ;这里使用了REPLACE对输入中的通配符进行了转义并使用预处理语句PREPARE和EXECUTE来执行确保了安全性。5.2 在触发器中基于模式匹配进行逻辑判断触发器可以在数据变更前后执行逻辑。LIKE可以用于条件判断。DELIMITER // CREATE TRIGGER before_insert_user BEFORE INSERT ON users FOR EACH ROW BEGIN -- 检查新插入的邮箱是否为管理员邮箱 IF NEW.email LIKE ‘%admin.company.com’ THEN SET NEW.role ‘admin’; -- 可以记录日志或进行其他操作 INSERT INTO admin_audit_log (user_id, action) VALUES (NEW.id, ‘Auto-assigned admin role’); END IF; END // DELIMITER ;5.3 在复杂查询中与其他子句联用LIKE可以和其他SQL子句无缝结合构建强大的查询。与CASE WHEN结合在查询结果中根据模式匹配添加标记列。SELECT name, email, CASE WHEN email LIKE ‘%gmail.com’ THEN ‘Gmail’ WHEN email LIKE ‘%outlook.com’ THEN ‘Outlook’ ELSE ‘Other’ END AS email_provider FROM users;与聚合函数和GROUP BY结合统计不同类别的数量。SELECT CASE WHEN url LIKE ‘%/products/%’ THEN ‘产品页’ WHEN url LIKE ‘%/blog/%’ THEN ‘博客页’ ELSE ‘其他页面’ END AS page_type, COUNT(*) AS visit_count FROM site_logs GROUP BY page_type;在UPDATE或DELETE语句中使用批量更新或删除符合特定模式的数据。-- 将所有临时邮箱用户的状态置为无效 UPDATE users SET status ‘inactive’ WHERE email LIKE ‘%temp%%’ OR email LIKE ‘%test%%’;重要提示执行此类操作前务必先使用SELECT语句验证匹配的结果确认无误后再执行UPDATE或DELETE避免误操作。6. 性能监控与诊断当LIKE查询变慢时该怎么办即使我们遵循了优化策略在生产环境中随着数据增长模糊查询仍可能变慢。这时需要一套诊断方法。6.1 使用EXPLAIN分析查询执行计划这是MySQL性能调优的必备工具。在查询语句前加上EXPLAIN或EXPLAIN FORMATJSON。EXPLAIN SELECT * FROM orders WHERE order_no LIKE ‘202310%’;关注结果中的几个关键字段type这是最重要的指标之一。如果看到ALL就表示全表扫描对于大表来说性能极差。我们期望看到的是range范围扫描对于LIKE ‘xxx%’有可能或index全索引扫描比全表扫描好。key显示MySQL实际决定使用的索引。如果为NULL说明没有使用索引。rowsMySQL预估需要扫描的行数。这个数字越接近实际结果集越好。Extra包含额外信息。如果出现Using where表示服务器在存储引擎检索行后再进行过滤。如果出现Using index condition是个好现象表示使用了索引条件下推。对于LIKE ‘%xxx%’查询EXPLAIN结果中的type很可能是ALLkey为NULL这就是性能问题的直接证据。6.2 慢查询日志定位罪魁祸首如果应用整体变慢需要开启MySQL的慢查询日志它可以帮助你捕获所有执行时间超过指定阈值如2秒的SQL语句。在MySQL配置文件如my.cnf中设置slow_query_log 1 slow_query_log_file /var/log/mysql/mysql-slow.log long_query_time 2重启MySQL服务或动态设置。分析慢日志文件使用mysqldumpslow工具或pt-query-digestPercona Toolkit进行汇总分析找出最耗时的模糊查询。6.3 针对性优化措施根据诊断结果可以采取以下措施重写查询再次审视业务是否真的需要LIKE ‘%xxx%’能否改为前缀匹配LIKE ‘xxx%’增加或调整索引对于必须的左匹配固定模式确保字段上有索引。对于LIKE ‘%xxx’考虑前面提到的“反向索引”方案。应用层缓存对于不常变化的热点模糊查询结果如热门搜索词可以在应用层如Redis进行缓存定时更新。读写分离与分库分表对于超大规模数据终极方案是进行架构升级将查询压力分散到只读从库或者对数据进行水平拆分。引入异步搜索对于实时性要求不高的搜索可以将其放入消息队列由后台任务处理结果生成后通知前端。模糊查询是数据库操作中的一把双刃剑它提供了极大的灵活性但也对性能构成了持续挑战。我的体会是在设计之初就要对数据的增长和查询模式有预判为高频的模糊查询字段建立合适的索引并明确其使用边界。在代码层面坚持使用参数化查询是底线。当性能问题出现时从EXPLAIN开始一步步分析从查询语句、索引、到数据库架构层层递进地寻找解决方案。记住没有银弹只有最适合当前业务场景的权衡之策。
返回列表