chi*_*ity 16 sqlite performance index optimization count
我有一个带有两个表的 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;
Run Code Online (Sandbox Code Playgroud)
当除了主键上没有索引(与此查询无关)时,它运行得很快:
[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
Run Code Online (Sandbox Code Playgroud)
但是如果我为每个表的三列添加索引(总共六个索引):
CREATE INDEX `idx_uk_givenname` ON `fakenames_uk` (`givenname` )
//etc.
Run Code Online (Sandbox Code Playgroud)
然后它运行得很慢:
[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
Run Code Online (Sandbox Code Playgroud)
这有什么押韵或理由吗?
这是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=?)
Run Code Online (Sandbox Code Playgroud)
这是索引:
0|0|0|SCAN TABLE fakenames_uk
0|1|1|SEARCH TABLE fakenames_usa USING INDEX idx_us_middleinitial (middleinitial=?)
Run Code Online (Sandbox Code Playgroud)
CL.*_*CL. 20
在 SQLite 中,连接作为嵌套循环连接执行,即数据库遍历一个表,对于每一行,从另一个表中搜索匹配的行。
如果有索引,数据库可以快速查找索引中的任何匹配项,然后去对应的表行获取任何其他需要的列的值。
在这种情况下,存在三个可能的索引。没有任何统计信息(将通过运行ANALYZE创建),数据库选择最小的一个,以减少 I/O。但是,middleinitial
索引没有用,因为它并没有大大减少需要获取的表行数;并且通过索引的额外步骤实际上增加了所需的 I/O,因为表行不再按顺序读取,而是随机读取。
如果没有索引,匹配行的查找将需要对第一个表的每一行对第二个表进行完整的表扫描。这会很糟糕,以至于数据库估计值得为这个查询创建然后删除一个临时索引。此临时(“自动”)索引是在用于搜索的所有列上创建的。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=?)
Run Code Online (Sandbox Code Playgroud)
surname
不再需要索引 on ,因为三列索引可用于对此列的任何查找。如果您仅在此列上进行查找,则
索引givenname
可能会很有用。
上的索引middleinitial
总是毫无价值:如果只扫描整个表,搜索 26 个可能值之一的查询会更快。