Fak*_*ame 7 sqlite performance optimization query-performance
我有一个使用 sqlite3 作为数据库的小网络应用程序(数据库相当小)。
现在,我正在使用以下查询生成一些要显示的内容:
SELECT dbId,
dlState,
retreivalTime,
seriesName,
<snip irrelevant columns>
FROM DataItems
GROUP BY seriesName
ORDER BY retreivalTime DESC
LIMIT ?
OFFSET ?;
Run Code Online (Sandbox Code Playgroud)
其中limit通常为 ~200,并且offset为 0(它们驱动分页机制)。
无论如何,现在,这个查询完全扼杀了我的表现。在具有约 67K 行的表上执行大约需要 800 毫秒。
我在seriesName和上都有索引retreivalTime。
sqlite> SELECT name FROM sqlite_master WHERE type='index' ORDER BY name;
<snip irrelevant indexes>
DataItems_seriesName_index
DataItems_time_index // This is the index on retreivalTime. Yeah, it's poorly named
Run Code Online (Sandbox Code Playgroud)
但是,EXPLAIN QUERY PLAN似乎表明它们没有被使用:
sqlite> EXPLAIN QUERY PLAN SELECT dbId,
dlState,
retreivalTime,
seriesName
FROM
DataItems
GROUP BY
seriesName
ORDER BY
retreivalTime
DESC LIMIT 200 OFFSET 0;
0|0|0|SCAN TABLE DataItems
0|0|0|USE TEMP B-TREE FOR GROUP BY
0|0|0|USE TEMP B-TREE FOR ORDER BY
Run Code Online (Sandbox Code Playgroud)
上的索引seriesName是COLLATE NOCASE,如果这是相关的。
如果我删除GROUP BY,它会按预期运行:
sqlite> EXPLAIN QUERY PLAN SELECT dbId, dlState, retreivalTime, seriesName FROM DataItems ORDER BY retreivalTime DESC LIMIT 200 OFFSET 0;
0|0|0|SCAN TABLE DataItems USING INDEX DataItems_time_index
Run Code Online (Sandbox Code Playgroud)
基本上,我天真的假设是执行此查询的最佳方法是从 中的最新值向后走retreivalTime,每次seriesName看到的新值时,将其附加到临时列表,最后返回该值。对于OFFSET较大的情况,这会带来一些性能不佳的情况,但在此应用程序中这种情况很少发生。
如何优化此查询?如果需要,我可以提供原始查询操作。
插入性能在这里并不重要,所以如果我需要创建一个或两个额外的索引,那很好。
我目前的想法是更新一个单独的表的提交钩子,该表仅用于跟踪唯一项目,但这似乎有点过头了。
可以使用索引来优化 GROUP BY,但是如果 ORDER BY 使用不同的列,则排序不能使用索引(因为只有当数据库能够以排序顺序从表中读取行时,索引才有帮助)。
如果您在查询中使用不同的排序规则,则 COLLATE NOCASE 索引无济于事。添加“正常”索引,或者使用GROUP BY seriesName COLLATE NOCASE,如果允许的话。
使用 OFFSET 子句进行分页不是很有效,因为数据库在开始单步执行之前仍然必须对所有行进行分组和排序。最好使用滚动光标。
注意:不能保证dbId和dlState值来自任何特定行;SQLite 允许聚合查询中的非聚合列仅用于与 MySQL 的错误兼容性。
这里有一个建议:添加一个索引(seriesName, retreivalTime)并尝试这个查询。它不会超级快,但可能比你所拥有的更有效:
SELECT d.dbId,
d.dlState,
d.retreivalTime,
d.seriesName,
<snip irrelevant columns>
FROM DataItems AS d
JOIN
( SELECT seriesName,
MAX(retreivalTime) AS max_retreivalTime
FROM DataItems
GROUP BY seriesName
ORDER BY max_retreivalTime DESC
LIMIT ?
OFFSET ?
) AS di
ON di.seriesName = d.seriesName
AND di.max_retreivalTime = d.retreivalTime
ORDER BY di.max_retreivalTime ;
Run Code Online (Sandbox Code Playgroud)
或者(变体)也使用 PK,并带有索引(seriesName, retreivalTime, dbId)和查询:
SELECT d.dbId,
d.dlState,
d.retreivalTime,
d.seriesName,
<snip irrelevant columns>
FROM DataItems AS d
JOIN
( SELECT dbId
FROM DataItems
GROUP BY seriesName
ORDER BY MAX(retreivalTime) DESC
LIMIT ?
OFFSET ?
) AS di
ON di.dbId = d.dbId
ORDER BY d.max_retreivalTime ;
Run Code Online (Sandbox Code Playgroud)
查询背后的逻辑是仅使用索引进行派生表计算(查找每个系列名称的最大值(检索时间),然后按顺序排序并执行偏移限制操作。)
然后表本身将仅用于获取要显示的 200 行。