直接在ON中写t1.col LIKE CONCAT(t2.prefix, '%')会导致全表扫描,因无法利用索引做前缀查找,t2.prefix索引失效,且每次需对t1.col逐行拼接匹配,CPU与I/O开销陡增。

用 LIKE 实现前缀匹配 JOIN 时为什么慢?
直接在 ON 子句里写 t1.col LIKE CONCAT(t2.prefix, '%') 能跑通,但几乎必然触发全表扫描——因为无法利用索引做前缀查找,MySQL/PostgreSQL 都会放弃对 t2.prefix 列的索引(哪怕它有索引)。更糟的是,如果 t2 行数多,每次都要对 t1.col 做字符串拼接和模式匹配,CPU 和 I/O 开销陡增。
实际场景常见于:日志表关联 IP 段表、商品编码匹配分类前缀、URL 路径匹配路由规则表。
- 避免在
ON中用CONCAT()或函数包裹被驱动表字段(如t2.prefix),否则无法走索引 - 若
t1.col是固定长度前缀(如前 3 位代表区域),优先考虑生成计算列 + 索引,而非运行时匹配 - PostgreSQL 可用
text_pattern_ops索引优化LIKE 'abc%',但仅限字面量,不适用于字段拼接
改用子查询 + STRAIGHT_JOIN(MySQL)或 LATERAL(PostgreSQL)控制驱动顺序
核心思路是让短表(如前缀规则表)做驱动表,对每条前缀,在被驱动表上做范围扫描而非全表扫描。MySQL 用 STRAIGHT_JOIN 强制顺序,PostgreSQL 用 LATERAL 实现类似效果。
例如 MySQL 中匹配 URL 前缀:
SELECT /*+ STRAIGHT_JOIN */ u.url, r.rule_name FROM rules r JOIN urls u ON u.url >= r.prefix AND u.url < CONCAT(r.prefix, '\xFF') WHERE r.prefix IS NOT NULL;
这里用 >= 和 < 替代 LIKE,把前缀匹配转为区间查询。关键点:
-
'\xFF'是 ASCII 最大字节,确保'/api'能覆盖'/api/v1'但不包含'/apis' -
urls.url必须有 B-tree 索引,且类型支持字典序比较(VARCHAR、TEXT均可) - 若前缀含 Unicode 字符,需确认排序规则(collation)是否影响边界判断,建议用
utf8mb4_bin
当需要模糊前缀(如通配符在中间)时,别硬扛,换方案
如果前缀本身含 % 或 _(比如规则是 '/user/*/profile'),LIKE 在 ON 中基本不可用——既无法索引,又无法下推条件。这时候应放弃 JOIN,改用应用层处理或预计算。
- 把规则表加载到内存,用正则或 trie 树做快速匹配(Python 的
pygtrie、Go 的patricia) - 对高频查询字段(如
url)增加冗余列,如提取一级路径path_level1,再建普通等值索引 - 使用 PostgreSQL 的
pg_trgm扩展支持相似度 JOIN,但属于“近似匹配”,非严格前缀
验证是否真走了索引范围扫描
执行前务必看 EXPLAIN 输出,重点确认两点:type 是 range(不是 ALL 或 index),且 key 显示用了目标索引。MySQL 中若出现 Using where; Using join buffer,说明没走索引下推,得调。
- 用
EXPLAIN FORMAT=TREE(MySQL 8.0+)看是否真正按预期顺序驱动 - PostgreSQL 中检查
EXPLAIN (ANALYZE)的Index Scan using ... on urls行,确认Index Cond包含url >= $1 AND url < $2 - 测试数据量要接近线上规模,小表上
range和ALL性能差异不明显,容易误判
前缀 JOIN 的性能陷阱不在语法,而在驱动方向和索引能否生效。一旦规则表行数超过几百,没控制好顺序或没建对索引,响应时间就从毫秒跳到秒级——这点很容易被本地小数据测试忽略。

















