背景
我正在对存储在SQLite中的一组电子邮件进行全文搜索,利用其出色的内置FTS4引擎.虽然不完全符合我的预期,但我的查询性能相当差.让我们来看看.
代表架构
我将提供一些有关代码的简化示例,并在适用的地方提供完整代码的链接.
我们有一个MessageTable存储有关电子邮件消息的数据(完整版本分布在几个文件,这里,这里和这里):
CREATE TABLE MessageTable (
id INTEGER PRIMARY KEY,
internaldate_time_t INTEGER
);
CREATE INDEX MessageTableInternalDateTimeTIndex
ON MessageTable(internaldate_time_t);
Run Code Online (Sandbox Code Playgroud)
可搜索的文本被添加到名为MessageSearchTable(此处为完整版)的FTS4表中:
CREATE VIRTUAL TABLE MessageSearchTable USING fts4(
id INTEGER PRIMARY KEY,
body
);
Run Code Online (Sandbox Code Playgroud)
该id搜索表作为一个外键消息表.
我将把它作为练习让读者在这些表格中插入数据(我当然不能透露我的私人电子邮件).我每张桌子的记录不到26k.
问题查询
当我们检索搜索结果时,我们需要按顺序对它们进行排序,internaldate_time_t这样我们才能获取最新的几个结果.这是一个示例搜索查询(此处为完整版):
SELECT id
FROM MessageSearchTable
JOIN MessageTable USING (id)
WHERE MessageSearchTable MATCH 'a'
ORDER BY internaldate_time_t DESC
LIMIT 10 OFFSET 0
Run Code Online (Sandbox Code Playgroud)
在我的机器上,通过我的电子邮件,运行大约150毫秒,通过以下方式测量:
time sqlite3 test.db <<<"..." > /dev/null
Run Code Online (Sandbox Code Playgroud)
150毫秒不是查询的野兽,但对于简单的FTS查找和索引顺序,它是缓慢的.如果我省略ORDER BY,它会在10毫秒内完成,例如.还要记住,实际的查询还有一个子选择,所以通常会进行更多的工作:查询的完整版本在大约600毫秒内运行,这是在野兽领域,ORDER BY在这种情况下省略削减500毫秒的时间.
如果我打开内部的统计信息sqlite3并运行查询,我会注意到该行:
Sort Operations: 1
Run Code Online (Sandbox Code Playgroud)
如果我对这些统计数据的文档的解释是正确的,看起来查询完全是使用了MessageTableInternalDateTimeTIndex.查询的完整版本也有一行:
Fullscan Steps: 25824
Run Code Online (Sandbox Code Playgroud)
听起来像是在某个地方走过桌子,但是现在让我们忽略它.
我发现了什么
所以让我们继续优化一下.我可以将查询重新排列为子选择并强制SQLite使用带有INDEXED BY扩展名的索引:
SELECT id
FROM MessageTable
INDEXED BY MessageTableInternalDateTimeTIndex
WHERE id IN (
SELECT id
FROM MessageSearchTable
WHERE MessageSearchTable MATCH 'a'
)
ORDER BY internaldate_time_t DESC
LIMIT 10 OFFSET 0
Run Code Online (Sandbox Code Playgroud)
瞧,运行时间已经下降到大约100毫秒(在查询的完整版本中为300毫秒,运行时间减少了50%),并且没有报告排序操作.请注意,只是重新组织这样的查询但不强制使用索引INDEXED BY,仍然有一个排序操作(尽管我们仍然已经奇怪地消磨了几毫秒),所以看起来SQLite确实忽略了我们的索引,除非我们强制它.
我还尝试了一些其他的事情,看看他们是否有所作为,但他们没有:
DESC作为描述在这里,有和无INDEXED BYid在索引中明确添加列,有和没有internaldate_time_t有序DESC,有和没有INDEXED BY问题
这里100毫秒似乎仍然非常缓慢,似乎它应该是一个简单的FTS查找和索引顺序.
谢谢!
索引对于根据索引列的值查找表行很有用.找到表行后,索引不再有用,因为在任何其他标准中查找索引中的表行效率不高.
这意味着不可能为查询中访问的每个表使用多个索引.
您的第一个查询具有以下EXPLAIN QUERY PLAN输出:
0 0 0 SCAN TABLE MessageSearchTable VIRTUAL TABLE INDEX 4: (~0 rows)
0 1 1 SEARCH TABLE MessageTable USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)
0 0 0 USE TEMP B-TREE FOR ORDER BY
Run Code Online (Sandbox Code Playgroud)
会发生什么事
MessageSearchTable行;MessageTable主键索引用于查找匹配的行;您的第二个查询具有以下EXPLAIN QUERY PLAN输出:
0 0 0 SCAN TABLE MessageTable USING COVERING INDEX MessageTableInternalDateTimeTIndex (~100000 rows)
0 0 0 EXECUTE LIST SUBQUERY 1
1 0 0 SCAN TABLE MessageSearchTable VIRTUAL TABLE INDEX 4: (~0 rows)
Run Code Online (Sandbox Code Playgroud)
会发生什么事
MessageSearchTable行;MessageTableInternalDateTimeTIndex以索引顺序遍历所有条目,并在id值为步骤1中找到的值之一时返回一行.SQLite在第十行之后停止.在此查询中,可以使用索引进行(隐含)排序,但这只是因为没有其他索引用于查找此表中的行.以这种方式使用索引意味着SQLite必须遍历所有条目,而不是查找与其他条件匹配的少数行.
当您INDEXED BY从第二个查询中省略该子句时,您将获得以下EXPLAIN QUERY PLAN输出:
0 0 0 SEARCH TABLE MessageTable USING INTEGER PRIMARY KEY (rowid=?) (~25 rows)
0 0 0 EXECUTE LIST SUBQUERY 1
1 0 0 SCAN TABLE MessageSearchTable VIRTUAL TABLE INDEX 4: (~0 rows)
0 0 0 USE TEMP B-TREE FOR ORDER BY
Run Code Online (Sandbox Code Playgroud)
这与第一个查询基本相同,只是联接和子查询的处理方式略有不同.
使用您的表结构,实际上不可能变得更快.你正在做三个操作:
MessageSearchTable;MessageTable;MessageTable值排序行.就索引而言,步骤2和3相互冲突.数据库必须选择是否使用第2步的索引(在这种情况下必须明确地进行排序)或第3步(在这种情况下必须遍历所有MessageTable条目).
您可以尝试通过使消息时间成为FTS表的一部分并仅搜索最近几天(并且如果没有获得足够的结果来增加或减少时间)来从FTS搜索返回更少的记录.
| 归档时间: |
|
| 查看次数: |
3994 次 |
| 最近记录: |