用途
本页聚焦一个具体的 SQL 陷阱:当使用 LIKE 在文本或 JSON 列中匹配机器生成的 ID 时,ID 中若包含 _ 或 %,会被 LIKE 当成通配符,从而静默地扩大匹配范围。文章旨在说明问题成因、给出正确的模式构造方式,并列出常见陷阱与验证方法。
主要能力
- 解释 LIKE 并非子串匹配:
_匹配任意单个字符,%匹配任意长度字符序列,且默认就是生效的。 - 提供嵌套
replace()转义方案:先转义反斜杠,再转义_,再转义%。 - 强调必须使用
ESCAPE子句声明转义字符,否则在许多引擎上转义无效。 - 说明参数绑定不会转义 LIKE 通配符,绑定参数同样需要处理。
- 指出当值来自列时,宿主语言无法转义,必须将
replace()写入 SQL。 - 说明裸子串匹配不是身份匹配,应加上存储格式保证的定界符。
- 提醒多层转义需要逐层确认,建议记录驱动最终收到的 SQL 文本。
- 指出前导
%会导致全表扫描且索引无法帮助,并给出替代方案(规范化到连接表)。 - 提供暴露量统计与近失配测试的验证方法。
使用方式
文章给出了一段示例 SQL(适配场景:检查某歌曲 ID 是否仍出现在任何播放列表的 JSON ID 数组中):
NOT EXISTS (
SELECT 1 FROM playlist
WHERE playlist.tracks LIKE
'%"' || replace(replace(replace(song.trackId, '\', '\\'), '_', '\_'), '%', '\%') || '"%'
ESCAPE '\'
)
其中嵌套 replace() 用于中和通配符,'%"' || … || '"%' 用于将匹配固定到完整 token。文章还给出统计暴露量的查询:
SELECT COUNT(*) FROM song WHERE trackId LIKE '%\_%' ESCAPE '\';
以及测试建议:插入两个仅在通配符位置不同的 ID(如 abc_def 与 abcXdef),正确转义应只返回一行,未转义会返回两行。
适用场景
- 使用 LIKE 在文本列或 JSON 列中匹配机器生成 ID 的 SQL 查询。
- 需要判断“某 ID 是否仍被引用”的清理/扫描任务。
- 包含查询中需要精确匹配某个 ID 的场景。
- 使用 SQLite、SQL Server、Oracle、PostgreSQL、MySQL 等数据库且涉及 LIKE 模式构造的开发与审查。
限制
- 前导
%导致全表扫描,索引无法使用;仅适合扫描表行数较少(如几百行)的情况,百万行级别应改为规范化连接表。 - 不同数据库对 LIKE 默认转义字符的定义不同:SQLite、SQL Server、Oracle 和 SQL 标准未定义默认转义字符;PostgreSQL 和 MySQL 默认反斜杠。
- 转义顺序必须正确:反斜杠必须最先转义。
- 多层转义(宿主语言字符串、SQL、其他处理层)需要逐层确认,建议记录最终 SQL 文本。
- 定界符需根据实际存储格式选择:JSON 字符串数组可用引号;逗号分隔列需要不同定界符;裸 ID 存储应使用
=而非 LIKE。