Indexing SQLite3使用索引查询列的任何子集
我已将性能问题缩小到一个特定的SQLite查询,如下所示:Indexing SQLite3使用索引查询列的任何子集,indexing,sqlite,combinations,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); select ident, name, region from test where (case when ?1 is null then 1 when ident = ?1 the
select *
from test
where (?1 is null or ident = ?1)
and (?2 is null or name = ?2)
and (?3 is null or region = ?3);
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)
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)
)
这允许输入参数的任何子集(有三个以上)使用单个查询。不幸的是,在此基础上使用解释查询计划
,会产生:
1|0|0|SCAN TABLE test
因此,无论传入什么,SQLite都在读取整个表
将查询从test\u idx索引的表更改为,会导致查询失败:错误:无查询解决方案
删除?1为空或会产生更有利的查询:
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=?)
似乎可以将每个条件消除或简化为一个简单的索引列检查,但显然不是这样,因为查询优化发生在参数绑定之前,没有进一步的简化
显而易见的解决方案是使用2^N个单独的查询,并在运行时根据要检查的输入组合选择适当的查询。对于N=2或3,这可能是可以接受的,但在这种情况下,这是绝对不可能的
当然,有很多方法可以重新组织数据库,使这种查询更合理,但假设这也不实际
那么,我如何才能搜索表中任何列的子集而不丢失这些列上索引的性能优势呢?我唯一能取得的进展就是使用如下查询:
select *
from test
where (?1 is null or ident = ?1)
and (?2 is null or name = ?2)
and (?3 is null or region = ?3);
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)
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|SCAN TABLE test USING COVERING INDEX test_idx_3
但是,只有当一个索引包含所有感兴趣的列,并且只选择索引中的列时,它才起作用。如果该索引不是“覆盖索引”(包含所有所需值的索引),则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);
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)
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]不要太聪明。SQLite准备好的语句不需要太多内存,因此实际上可以保留所有2^N个语句。但是准备一个查询也不需要很多时间,所以最好在需要时动态地构造每个查询
至于索引:显示必须在查询中使用索引中最左边的列。这意味着您只需要索引中的几个列的组合(偶数)。在任何情况下,您都应该对高优先级列的索引进行优先级排序。同时提问和回答?@lad2025是的,一直鼓励这样做,我不确定它是何时添加的,但现在有一个选项可以同时组合它们。我知道你可以回答自己的问题。但我不知道你是否需要帮助not@TimSylvester我有点不清楚你到底在这里订什么。如果它是一个文本,那么您不知道它是null还是不需要?1是null
部分吗?占位符是否要替换为列名?