SQLite3使用索引查询任何列的子集

时间:2015-11-07 21:31:32

标签: indexing sqlite combinations

我已将性能问题缩小到特定的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可能是可以接受的,但在这种情况下它绝对是不可能的。

当然,有许多方法可以重新组织数据库,使这种类型的查询更加合理,但是假设它也不实用。

那么,如何在不损失这些列索引的性能优势的情况下搜索表中任何列的子集?

2 个答案:

答案 0 :(得分:0)

我唯一能做的就是使用这样的查询:

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次搜索实际上比单次传递整个表要慢。对于M,结果是N行的表,您需要O[M log N] << O[N],所以m < (N / log N)Call it 3% as a rule of thumb,减去索引扫描的费用:

n/nlogn

答案 1 :(得分:0)

不要试图变得聪明。 SQLite准备好的语句不需要太多内存,所以你实际上可以保留所有2 ^ N的内存。但是准备查询也不需要太多时间,因此在需要时动态构造每个查询会更好。

对于索引:documentation表示必须在查询中使用索引中最左侧的列。这意味着您只需要索引中的几个列组合(甚至for queries that do not use all index columns)。在任何情况下,您都应该对具有高selectivity的列的索引进行优先级排序。