在MySQL中进行模糊查询时,开发者最常接触的便是like运算符。但当数据量增长到百万甚至千万级别,like '%xx%'的写法经常成为慢查询的根源。与此同时,部分团队开始采用instr()函数完成相似的匹配逻辑,并观察到响应时间有所下降。本文围绕这一替代方案,从原理、用法与性能边界三个维度展开说明。

一、like模糊查询的执行特征
like是SQL标准中的模式匹配运算符,支持百分号(%)与下划线(_)两种通配符。最典型的模糊查询写法为column like '%abc%',表示字段中任意位置包含abc即命中。由于前置百分号的存在,MySQL优化器无法利用该列上的有序B+树索引进行范围定位,只能对整个索引或数据行做顺序扫描。
我们可以通过执行计划观察其代价。以下建表语句与查询示例用于演示:
create table user_log ( id int primary key auto_increment, content varchar(255) not null, index idx_content (content) ) engine=innodb default charset=utf8mb4; explain select * from user_log where content like '%error%';
在上面的explain结果中,type列通常显示为ALL,即全表扫描;key列则为NULL,证明索引未被使用。当表内记录达到千万级,每次查询都要比对全部行的字符内容,CPU与IO压力显著上升。
二、instr()函数的基本用法
instr(str, substr)是MySQL提供的字符串函数,返回子串substr在字符串str中第一次出现的位置(从1开始计数),若未找到则返回0。利用这一特性,我们可以用instr(content, 'error') > 0来表达“content包含error”的语义,形式上替代like '%error%'。
基础写法如下:
select * from user_log where instr(content, 'error') > 0;
从语义等价性看,该语句与content like '%error%'结果一致。但instr作为普通函数调用,优化器同样难以对其使用idx_content索引,因此也会走入全表扫描路径。那么效率提升从何而来?核心在于函数本身的字符比对实现与like模式匹配的状态机开销不同:like需要解析通配符并维护匹配状态,而instr仅做直接的子串查找,在短模式、长文本场景下指令更少。
三、为什么instr()有时比like更快
在InnoDB的utf8mb4字符集下,like '%x%'的匹配由MySQL内部的模式匹配模块完成,需逐字符判断是否处于通配区间;instr则调用更底层的字符串搜索例程(如基于Boyer-Moore思想的实现),对固定子串的扫描效率更高。当字段平均长度较大、匹配关键词较短时,这种差异会被放大。
我们做一个简单的对照测试,在约五百万行数据上分别执行:
-- 方式一:like select count(*) from user_log where content like '%timeout%'; -- 方式二:instr select count(*) from user_log where instr(content, 'timeout') > 0;
多次取平均值后,instr版本往往比like少消耗百分之十几到三十的查询时间,具体比例依赖数据分布与服务器配置。需要强调的是,两者均未使用索引,绝对耗时仍随数据量线性增长,并非“索引级”优化。
四、使用instr()的注意事项
第一,大小写敏感问题。instr的匹配行为依赖列的排序规则(collation)。若字段为utf8mb4_general_ci,则instr表现不区分大小写;若为utf8mb4_bin,则区分。而like同样受此影响,但开发者容易误以为instr永远区分大小写,导致漏匹配。
第二,多字节字符与性能拐点。对于包含大量四字节表情符的文本,instr与like都需按字符而非字节处理,函数优势收窄。此外,若查询需同时按其他条件过滤,仅用instr无法减少扫描行数。此时应结合覆盖索引或全文索引:
-- 使用全文索引获得真正索引加速
alter table user_log add fulltext index ft_content (content);
select * from user_log where match(content) against('error' in boolean mode);
第三,可维护性。instr(字段, 常量)的写法不如like直观,团队新人可能误解其含义。在代码评审中应补充注释说明替代动机。
五、实践中的选型建议
如果业务只是偶尔在后台做日志排查,且数据量在千万以内,将like改为instr是一种低成本的微调。但若为面向用户的检索接口,应优先考虑以下方案:建立生成列加索引、使用Elasticsearch等外部搜索引擎,或采用MySQL全文索引。
下表汇总了三种常见方案的适用特征:
| 方案 | 是否用索引 | 适用场景 |
|---|---|---|
| like '%x%' | 否 | 小规模数据临时查询 |
| instr()>0 | 否 | 中等规模、追求微优化 |
| fulltext / 搜索引擎 | 是 | 高并发生产检索 |
综上,instr()替代like本质上是在同为负向索引使用的条件下,减少字符串匹配的计算冗余,而非改变扫描本质。理解这一点,才能在性能与可维护性之间做出合理权衡。