为什么在我索引列时此sqlite查询要慢得多?


14

我有一个带两个表的sqlite数据库,每个表有50,000行,其中包含(假)人的名字。我构建了一个简单的查询,以找出两个表共有的名称(给定名称,中间名缩写,姓氏):

select count(*) from fakenames_uk inner join fakenames_usa on fakenames_uk.givenname=fakenames_usa.givenname and fakenames_uk.surname=fakenames_usa.surname and fakenames_uk.middleinitial=fakenames_usa.middleinitial;

当除了主键之外没有索引(与该查询无关)时,它将快速运行:

[james@marlon Downloads] $ time sqlite3 generic_data_no_indexes.sqlite "select count(*) from fakenames_uk inner join fakenames_usa on fakenames_uk.givenname=fakenames_usa.givenname and fakenames_uk.surname=fakenames_usa.surname and fakenames_uk.middleinitial=fakenames_usa.middleinitial;"
131

real    0m0.115s
user    0m0.111s
sys     0m0.004s

但是,如果我将索引添加到每个表的三列中(总共六个索引):

CREATE INDEX `idx_uk_givenname` ON `fakenames_uk` (`givenname` )
//etc.

然后它会缓慢缓慢地运行:

[james@marlon Downloads] $ time sqlite3 generic_data.sqlite "select count(*) from fakenames_uk inner join fakenames_usa on fakenames_uk.givenname=fakenames_usa.givenname and fakenames_uk.surname=fakenames_usa.surname and fakenames_uk.middleinitial=fakenames_usa.middleinitial;"
131

real    1m43.102s
user    0m52.397s
sys     0m50.696s

这有什么押韵或原因吗?

这是EXPLAIN QUERY PLAN不带索引的版本的结果:

0|0|0|SCAN TABLE fakenames_uk
0|1|1|SEARCH TABLE fakenames_usa USING AUTOMATIC COVERING INDEX (middleinitial=? AND surname=? AND givenname=?)

这是与索引:

0|0|0|SCAN TABLE fakenames_uk
0|1|1|SEARCH TABLE fakenames_usa USING INDEX idx_us_middleinitial (middleinitial=?)

1
您的索引没有覆盖。看来您正在分别索引各列。当您创建包含在索引的所有三列的覆盖索引(会发生什么middleinitialsurnamegivenname)?
伦道夫·西

@Randoph West我明白您的意思,但是您使用的术语不正确:“覆盖索引”也是一个包含所选列的索引。例如,对于查询SELECT c FROM t WHERE a=1 AND b=2,索引t(a,b,c)覆盖而t(a,b)不是覆盖。覆盖索引的好处是可以将整个查询结果直接从索引中拉出,而非覆盖索引可以快速找到相关的行,但仍需要引用主表数据来选择值。
亚瑟塔卡

Answers:


15

在SQLite中,联接作为嵌套循环联接执行,即数据库遍历一个表,对于每一行,从另一表搜索匹配的行。

如果有索引,数据库可以快速查找索引中的所有匹配项,然后转到相应的表行以获取所需的任何其他列的值。

在这种情况下,存在三个可能的索引。如果没有任何统计信息(可以通过运行ANALYZE创建),数据库将选择最小的统计信息以减少I / O。但是,middleinitial索引是无用的,因为它不会大大减少需要获取的表行的数量。并且通过索引执行的附加步骤实际上会增加所需的I / O,因为不再按顺序读取表行,而是随机读取表行。

如果没有索引,则查找匹配行将需要对第一张表的每一行进行第二张表的完整表扫描。这将非常糟糕,以至于数据库估计值得为该查询创建然后删除一个临时索引。该临时(“ AUTOMATIC”)索引是在用于搜索的所有列上创建的。COUNT(*)操作不需要任何其他列的值,因此该索引恰好是覆盖索引,这意味着不必实际查找与索引条目对应的表行,从而节省了更多的时间。 / O。

为了加快查询速度,请永久创建此索引,这样就不再需要构造一个临时索引:

CREATE INDEX uk_all_names ON fakenames_uk(surname, givenname, middleinitial);

EXPLAIN QUERY PLAN
SELECT count(*)
FROM fakenames_uk
JOIN fakenames_usa USING (givenname, middleinitial, surname);

0|0|1|SCAN TABLE fakenames_usa
0|1|0|SEARCH TABLE fakenames_uk USING COVERING INDEX uk_all_names (surname=? AND givenname=? AND middleinitial=?)

surname不再需要索引on ,因为三列索引可用于此列上的任何查找。如果仅在此列上进行查找,则
on上的索引givenname可能很有用。
索引middleinitial始终是毫无价值的:如果只扫描整个表,则搜索26个可能值之一的查询会更快。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.