DEBUG.WIKI
V.PS:让项目靠近用户,亚太、欧洲、北美 KVM VPS
IPPeak:从目标地区访问网页,住宅代理、SEO 监测、广告验证
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国

MySQL LIKE 搜索下划线却匹配了几乎所有非空名称

MySQL LIKE 的下划线是单字符通配符。使用显式 ESCAPE 搜索字面下划线,并区分包含搜索、结尾搜索和动态输入的转义。

查询 WHERE name LIKE '%_%',本来只想找到 2_apples、final_chapter 这类带下划线的名称,却连 apple 也返回了。原因是 LIKE 模式中的 _ 表示任意单个字符;两侧的 % 再匹配任意长度内容,这个模式就能匹配任意至少有一个字符的字符串。

下面针对 MySQL 的 LIKE 字面字符搜索,采用显式 ESCAPE。示例按 MySQL 8.4 规则核对,不改变会话 SQL 模式或表中数据。

给下划线指定明确的转义字符

使用 ! 作为此次模式的转义字符:

SELECT name
FROM items
WHERE name LIKE '%!_%' ESCAPE '!';

items、name 是示例表和列。模式中 !_ 表示下划线本身,前后的 % 仍表示任意长度内容,因此可以查找出现在名称任意位置的下划线。

如果只写 LIKE '_',含义是“恰好一个字符”;写 LIKE '%_' 则仍把最后一个位置当作任意字符。给字符串加 SQL 单引号只是在声明字符串,不能取消 LIKE 对通配符的解释。

可先用常量核对匹配方向:

SELECT
  'apple' LIKE '%_%' AS broad_match,
  'apple' LIKE '%!_%' ESCAPE '!' AS plain_name,
  '2_apples' LIKE '%!_%' ESCAPE '!' AS underscore_name,
  '' LIKE '%_%' AS empty_name;

按这些规则,预期分别是 1、0、1、0。这组检查把模式问题与真实表数据分开:如果常量符合预期,而表中某条记录不符合,就继续核对实际字符、应用最终传入的模式和附加查询条件。

不用反复增加反斜线试探

MySQL 在默认情况下允许反斜线转义 LIKE 通配符,但反斜线还会受 SQL 字符串解析影响;启用 NO_BACKSLASH_ESCAPES 后,默认规则又不同。查询从应用语言发出时,字符串字面量可能再多一层解析。

显式使用 ESCAPE '!',可以把本例的模式写成 %!_%,避免依赖默认反斜线转义。无需为了这次搜索去修改整个连接的 sql_mode。如果现有查询本来就在使用另一个转义字符,应沿用并核对那个约定,不要只改模式而遗漏 ESCAPE。

位置也要和业务要求一致。只查以字面下划线结尾的名称,可使用:

SELECT name
FROM items
WHERE name LIKE '%!_' ESCAPE '!';

这里没有末尾的 %,因此下划线必须在结尾。与“包含下划线”相比,这是主动缩小范围,不能用来替代前面的查询。

搜索词来自输入时,参数绑定之外还要处理模式

参数绑定负责把值安全地传给数据库,但不会把参数里的 %、_ 自动变成普通字符。若接口承诺的是“按输入文字搜索”,构造模式时还要处理选定的转义字符。

对于本例的 ! 约定,应先把输入中的 ! 编码成 !!,再把 % 编码成 !%、_ 编码成 !_,最后根据“包含”或“前缀”搜索需求添加通配符。这个次序避免把后来加入的转义符再次处理。处理后的整个模式仍应作为参数绑定,不能直接拼接进 SQL。

例如,输入文字为 a_b 时,包含搜索的模式应为 %a!_b%;输入文字为 50% 时,应为 %50!%%。如果产品本来就允许用户输入通配模式,则不应悄悄把所有通配符取消,需要先确定接口语义。

修正条件后,先核对查询范围

用包含下划线、不含下划线、空字符串和缺失值的样本检查结果。NULL LIKE pattern 得到 NULL,在普通 WHERE 条件中不会被当作匹配;是否包含缺失值应另写明确条件。

这次改动只调整查询模式。不要直接把尚未核对的条件用于 UPDATE 或 DELETE:原来的 %_% 可能覆盖远超预期的数据。需要回退时恢复应用查询即可;若已经用错误条件写入数据,恢复查询不能撤销那些写入,应按该次写入的备份和审计记录处理。