这是一些伪SQL,其中的“问题”很容易被复制:
create table Child (
childId text primary key,
some_int int not null
);
create table Person (
personId text primary key,
childId text,
foreign key (childId) references Child (childId) on delete cascade
);
create index Person_childId on Person (childId);
explain query plan select count(1)
from Person
left outer join Child on Child.childId = Person.childId
where Person.childId is null or Child.some_int = 0;
查询计划的结果如下:
SCAN Person USING COVERING INDEX Person_childId
SEARCH Child USING INDEX sqlite_autoindex_Child_1 (childId=?) LEFT-JOIN
这看起来很棒,对吧?但我很好奇这是不是“完整”的计划。这是因为some_int
没有索引。但查询计划没有发现这一点,我在任何地方都没有看到过滤。数据库必须过滤此字段,对吗?
当我在单独的查询中执行some_int
字段时,它显示一个SCAN
,这与我在前面的查询计划中看到的完全一样因为没有索引:
explain query plan select * from Child where some_int = 0;
提供:
SCAN Child
现在我的问题是:
- 为什么第一个查询计划中没有显示
SCAN Child
? - 为什么
Person
上有SCAN
,而不是SEARCH
? - 第一个查询计划是‘快速’,还是我仍然需要添加索引?
1条答案
按热度按时间k3bvogb11#
您应该看看this page。它深入解释了SQLite查询规划器,您可以找到所有问题的答案。请注意,像
WHERE some_int=0
这样的过滤条件不会显示在查询计划中,因为它们不会影响计划,而只会影响结果集。简而言之:
为什么第一个查询计划中没有显示Scan Child?
因为,由于
LEFT JOIN
的原因,SQLite需要SCAN Person
,并且对于每一行Person,使用ChildID上的索引来查找Child
中的相应记录。为什么扫描的是Person,而不是搜索?
SCAN
表示按存储顺序读取表中的所有行。SEARCH
是在表中查找单个值,使用索引查找行ID,并使用行ID访问表中的该行,而不需要扫描所有TE表来查找该行。因为您的查询需要读取所有Person.childId
,所以它会执行完全扫描。第一个查询计划是“快速”的,还是我仍然需要添加索引?
您的查询已经使用了它可以使用的所有索引,所以它已经是您能得到的最快的了。