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 使用索引查询列的任何子集的主要内容,如果未能解决你的问题,请参考以下文章

Sqlit--学习教程(建立数据库表)

在应用于列的函数上创建索引:适用于一个安装,而不是另一个

python连接sqlite3出错

如何在 postgresql 中使用带有数组列的 GIN 索引?

如何使用 sequelize 基于 sqlite3 中的“日期”列进行查询?

busybox filesystem httpd php-5.5.31 sqlit3 webserver