SQLite3中SQL表的合理设计:如何优化子字符串查询?
看起来你在SQLite的子串搜索优化上卡壳了,刚好我之前处理过类似的场景,结合你的情况(短无空格字符串、需要和普通表的多列查询结合),给你几个实用的方案:
一、首推:FTS虚拟表+自定义分词器,完美兼容原表
你之前以为全文搜索不能和普通表配合?其实完全可以!不需要把所有数据都搬到FTS表,我们可以单独给第7列的文本建一个FTS虚拟表,然后通过主键和原表关联就行。
针对你那种无空格的短字符串(比如ThisIsMyExampleString),默认的分词器会把整个字符串当成一个token,确实搜不到子串,但我们可以给FTS5设置trigram分词器(需要编译SQLite时开启SQLITE_ENABLE_FTS5选项,C++环境里只要你用的SQLite版本支持,一般都能开启),它会把字符串拆成连续的三个字符组合,这样任意子串都能被快速匹配到。
举个实际操作的例子:
- 先创建FTS虚拟表,指定trigram分词器:
CREATE VIRTUAL TABLE fts_col7 USING fts5( content='', -- 空content表示不存储原始内容,只做索引 tokenize='trigram' );
- 给原表加个触发器,让第7列的内容自动同步到FTS表(这样你插入/更新原表时不用手动管FTS表):
CREATE TRIGGER sync_fts_col7 AFTER INSERT ON your_table BEGIN INSERT INTO fts_col7(rowid, col7) VALUES (new.rowid, new.col7); END; CREATE TRIGGER sync_fts_col7_update AFTER UPDATE OF col7 ON your_table BEGIN UPDATE fts_col7 SET col7 = new.col7 WHERE rowid = old.rowid; END;
- 查询的时候,先通过FTS表快速定位符合子串条件的rowid,再关联原表的6列等值条件:
SELECT t.* FROM your_table t JOIN fts_col7 f ON t.rowid = f.rowid WHERE f.col7 MATCH 'Example' -- 这里直接写要搜的子串就行,不用加% AND t.col1 = 'val1' AND t.col2 = 'val2' -- ... 剩下4列的等值条件
这样既利用了原表前6列的普通索引,又用FTS的索引快速处理了子串搜索,效率拉满。
二、备选:生成列+Trigram索引(无需FTS虚拟表)
如果你的环境没法开启FTS5扩展,那可以试试用SQLite的ICU扩展创建Trigram索引(需要开启SQLITE_ENABLE_ICU)。直接给第7列创建trigram索引后,LIKE %xxx%查询会自动用上这个索引:
CREATE INDEX idx_col7_trigram ON your_table(col7) USING icu_trigram;
之后你原来的查询语句不用改,WHERE col7 LIKE '%Example%' AND col1='val1'... 就能走索引,速度比纯LIKE快很多。
三、极端场景:直接用LIKE(数据量小的时候)
如果你的表数据量不大(比如几万条以内),而你的字符串又只有100字符左右,其实纯LIKE %xxx%的速度也不会太差——毕竟短字符串的匹配开销本身就小。这种情况下可以先试试直接用,等数据量上来了再换上面的优化方案。
最后再给你吃个定心丸:短无空格字符串完全适合全文搜索,只要调整好分词器就行,trigram分词器就是专门为这种任意子串搜索设计的,比你硬用LIKE靠谱多了。
内容来源于stack exchange

