如何使用RediSearch匹配复杂通配符文件路径模式?
为什么你的查询会返回多余结果?
你用的TextField是基于全文分词的——它会把路径里的/当成分隔符,把整个路径拆成独立的词(比如foo/animals会被拆成foo和animals)。当你执行fo*/*nimals查询时,RediSearch的逻辑是找包含任意以fo开头的词,且包含任意以nimals开头的词的文档,完全不关心这两个词在路径里的顺序和层级。比如如果有个文档的prefix是animals/foo,也会被匹配到,这就是你拿到2条结果的原因。
关于TEXT索引的几个关键逻辑澄清
1. 顺序处理
TEXT索引是倒排索引,只记录文档包含哪些词,不保留词的原始顺序和位置。如果你需要严格匹配路径的分段顺序(比如必须是第一分段/第二分段),TEXT字段本身做不到,得换用其他方式。
2. 通配符的生效规则
- 前缀通配符(
fo*):默认支持,倒排索引天然存储词的前缀信息,匹配效率高。 - 后缀通配符(
*der):默认不支持高效匹配,必须在创建索引时添加SUFFIXTRIE <最小长度>参数,比如SUFFIXTRIE 3,这样RediSearch会预先生成后缀字典,才能快速匹配后缀。 - 中缀通配符(
f*o):TEXT字段很难高效支持,因为分词后是按整词处理的,中缀匹配需要遍历大量词。如果必须用,只能结合WITHPOSITIONS和短语查询,但性能较差,建议换用TagField或直接用字符串匹配。
3. 路径深度匹配
TEXT的分词逻辑会破坏路径的层级结构,拆分后的词是独立的,无法区分a/b/c和c/b/a。要匹配固定深度的路径,必须通过字段类型或查询语法强制层级约束。
不用改键分组的修复方案
方案1:用TagField存储路径,按分段匹配
把prefix字段定义为TagField,指定/为分隔符,这样每个路径分段会被当成独立的标签:
FT.CREATE idx ON JSON PREFIX 1 doc: SCHEMA $.prefix AS prefix TAG SEPARATOR "/"
查询时用标签匹配语法,强制每个分段的通配符:
FT.SEARCH idx "@prefix:{fo*} @prefix:{*nimals}"
如果需要严格保证分段顺序,这个方案不行,因为TagField的标签是无序的。这时候用方案2。
方案2:TEXT字段加位置存储,用短语查询强制顺序
创建索引时给TextField加上WITHPOSITIONS参数,让RediSearch记录每个词在原字符串中的位置:
FT.CREATE idx ON JSON PREFIX 1 doc: SCHEMA $.prefix AS prefix TEXT WITHPOSITIONS
查询时用PHRASE语法,强制fo*开头的词出现在*nimals开头的词之前:
FT.SEARCH idx "PHRASE(@prefix, 'fo* *nimals')"
这样就能过滤掉分段顺序相反的文档,得到你预期的1条结果。
方案3:结合RedisJSON的字符串匹配(灵活但性能一般)
如果需要更复杂的路径匹配,可以用FILTER子句调用RedisJSON的字符串匹配能力:
FT.SEARCH idx "*" FILTER "redis.call('JSON.GET', KEY[1], '$.prefix') =~ '^fo.*/.*nimals$'"
这个方式不需要修改索引结构,但会在查询结果上二次过滤,数据量大时性能不如前两种。
后缀匹配(*der)的正确配置
如果要高效匹配类似*der的后缀,创建索引时必须开启后缀字典:
FT.CREATE idx ON JSON PREFIX 1 doc: SCHEMA $.prefix AS prefix TEXT SUFFIXTRIE 3
这里的3是最小后缀长度,比如匹配*der时,后缀长度是3,刚好符合要求。
内容的提问来源于stack exchange,提问作者BPDev

