SQLite3 使用索引查询列的任何子集
Posted
技术标签:
【中文标题】SQLite3 使用索引查询列的任何子集【英文标题】:SQLite3 query any subset of columns with indexing 【发布时间】:2015-11-07 21:31:32 【问题描述】:我已将性能问题缩小到如下所示的特定 SQLite 查询:
select *
from test
where (?1 is null or ident = ?1)
and (?2 is null or name = ?2)
and (?3 is null or region = ?3);
这允许输入参数的任何子集(超过三个)使用单个查询。不幸的是,在此使用 explain query plan
会产生:
1|0|0|SCAN TABLE test
所以无论传入什么,SQLite 都会读取整个表。
将查询更改为from table indexed by test_idx
会导致它失败:Error: no query solution
。
删除?1 is null or
会产生更有利的查询:
1|0|0|SEARCH TABLE test USING INDEX idx (ident=?)
但是,请注意只能使用一个索引。将扫描ident
的所有匹配项以查找与其他字段的匹配项。使用包含所有匹配字段的单个索引可以避免这种情况:
0|0|0|SEARCH TABLE test USING INDEX test_idx_3 (ident=? AND region=? AND name=?)
认为SQLite's query planner 能够消除或简化每个条件以进行简单的索引列检查似乎是合理的,但显然情况并非如此,因为查询优化发生在参数绑定之前,并且不会发生进一步的简化.
显而易见的解决方案是有 2^N 个单独的查询,并在运行时根据要检查的输入组合选择合适的查询。对于 N=2 或 3,这可能是可以接受的,但在这种情况下绝对不可能。
当然,有很多方法可以重新组织数据库,使这种类型的查询更加合理,但假设这也不实用。
那么,我怎样才能搜索表中的任何列子集,而不会失去这些列上索引的性能优势?
【问题讨论】:
一问一答? @lad2025 是的,它一直受到鼓励,我不确定它是什么时候添加的,但现在可以选择同时组合它们。 http://***.com/help/self-answer 我知道你可以回答自己的问题。但是我不知道你是否需要帮助 @TimSylvester 我有点不清楚你到底在这里绑定了什么。如果它是文字,那么您不会事先知道它是否为 null 或不使?1 is null
部分不必要吗?占位符是否要被列名替换?
【参考方案1】:
我能够取得的唯一进展是使用这样的查询:
select ident, name, region
from test
where (case when ?1 is null then 1 when ident = ?1 then 1 else 0 end)
and (case when ?2 is null then 1 when name = ?2 then 1 else 0 end)
and (case when ?3 is null then 1 when region = ?3 then 1 else 0 end)
这将查询减少为索引扫描,而不是表扫描:
0|0|0|SCAN TABLE test USING COVERING INDEX test_idx_3
但是,只有一个索引包含所有感兴趣的列,并且只有被选中的列是索引中的列时,它才有效。如果索引不是“覆盖索引”(包含所有需要的值),则 SQLite 根本不使用该索引。
绕过第二个限制的方法是像这样构造查询:
select ident, name, region, location
from test
where rowid in (
select rowid
from test
where (case when ?1 is null then 1 when ident = ?1 then 1 else 0 end)
and (case when ?2 is null then 1 when name = ?2 then 1 else 0 end)
and (case when ?3 is null then 1 when region = ?3 then 1 else 0 end)
)
屈服:
0|0|0|SEARCH TABLE test USING INTEGER PRIMARY KEY (rowid=?)
0|0|0|EXECUTE LIST SUBQUERY 1
1|0|0|SCAN TABLE test USING COVERING INDEX test_idx_3
这通常比全表扫描快,但快多少取决于几个因素:
每行中有多少数据不在索引中?如果很小,那么索引扫描几乎就是表扫描。
有多少个结果?每个结果都是一个单独的主键搜索,因此对于一个大表中的一些大量结果,N 次搜索实际上会比单次遍历整个表要慢。对于 N 行表中的 M 个结果,您需要 O[M log N] << O[N]
,因此需要 m < (N / log N)
。 Call it 3% as a rule of thumb,减去索引扫描的成本:
【讨论】:
【参考方案2】:不要试图变得聪明。 SQLite 的预处理语句不需要太多内存,因此您实际上可以保留所有 2^N 个语句。但是准备查询也不需要太多时间,因此最好在需要时动态构建每个查询。
关于索引:documentation 表示在查询中必须使用索引中最左边的列。这意味着您只需要索引中的少数列组合(甚至是for queries that do not use all index columns)。在任何情况下,您都应该优先考虑具有高 selectivity 的列上的索引。
【讨论】:
以上是关于SQLite3 使用索引查询列的任何子集的主要内容,如果未能解决你的问题,请参考以下文章
如何在 postgresql 中使用带有数组列的 GIN 索引?