SQLite3查询几乎在某些版本的数据库上运行,但在其他版本的数据库上运行的时间要长几个数量级

时间:2018-02-14 02:32:00

标签: sqlite banshee

我很老套,多年来一直使用Banshee作为我的主要音乐播放器。幸运的是,我对此感到非常满意,除了我过去几天一直在调试的某个问题,其中,当播放到播放列表中的新曲目时,它会冻结并且具有很高的CPU使用率。时间(与播放列表的大小成比例)。例如,对于512轨播放列表,大约需要16秒,对于6000轨播放列表,大约需要3分钟。我也在我的工作计算机上使用Banshee(尽管库有点小),那里完全没有问题;在任何大小的播放列表中都不会有任何可察觉的时间,即使是6000轨道的播放列表。

正如我所说,我一直在密集研究这个问题。我首先尝试以各种方式修改Banshee库文件,看看我是否可以生成一个与原始文件具有相同基本内容的文件,但没有缓慢的洗牌问题。我最终发现删除大部分播放列表“解决”了这个问题。通过比较有和没有问题的库,通过近似二进制搜索的过程,我设法生成一个库文件,表明缓慢的shuffle问题,但可以转换成一个不通过运行单个更新命令的文件:

UPDATE sqlite_stat1 SET stat='18800 1447 1' WHERE rowid=18;

在研究了sqlite_stat1 table之后,我意识到这不是Banshee中的错误,而是SQLite3的一个深层问题以及它如何优化查询。运行ANALYZE命令来更新sqlite_stat1表解决了库文件的问题,该文件只是一个命令远离工作,而不是我的“真实”库;我不确定为什么。

然后我注意到Banshee有一个--debug-sql参数,它允许我检索花了这么长时间的实际查询。它是在几百个其他设置命令(例如创建和填充临时表,CoreCache)之后运行,以将数据库转换为临时状态,在该状态下,此查询会立即或缓慢地运行:

SELECT CoreTracks.Rating,CoreTracks.LastStreamError,CoreTracks.TrackID,
CoreTracks.PrimarySourceID,CoreTracks.ArtistID,CoreTracks.AlbumID,CoreTracks.TagSetID,
CoreTracks.MusicBrainzID,CoreTracks.MimeType,CoreTracks.FileSize,
CoreTracks.FileModifiedStamp,CoreTracks.LastSyncedStamp,CoreTracks.Attributes,
CoreTracks.Title,CoreTracks.TitleSort,CoreTracks.TrackNumber,CoreTracks.TrackCount,
CoreTracks.Disc,CoreTracks.DiscCount,CoreTracks.Duration,CoreTracks.Year,
CoreTracks.Genre,CoreTracks.Composer,CoreTracks.Conductor,CoreTracks.Grouping,
CoreTracks.Copyright,CoreTracks.LicenseUri,CoreTracks.Comment,CoreTracks.BPM,
CoreTracks.BitRate,CoreTracks.SampleRate,CoreTracks.BitsPerSample,CoreTracks.Score,
CoreTracks.PlayCount,CoreTracks.SkipCount,CoreTracks.ExternalID,
CoreTracks.LastPlayedStamp,CoreTracks.LastSkippedStamp,CoreTracks.DateAddedStamp,
CoreTracks.DateUpdatedStamp,CoreTracks.Uri,CoreArtists.Name,CoreArtists.NameSort,
CoreAlbums.Title,CoreAlbums.TitleSort,CoreAlbums.ArtistName,CoreAlbums.ArtistNameSort,
CoreAlbums.IsCompilation,CoreAlbums.MusicBrainzID,CoreArtists.MusicBrainzID
, OrderID, CoreCache.ItemID
FROM CoreTracks,CoreArtists,CoreAlbums
INNER JOIN CorePlaylistEntries
    ON CoreTracks.TrackID = CorePlaylistEntries.TrackID
INNER JOIN CoreCache
    ON CorePlaylistEntries.EntryID = CoreCache.ItemID 
WHERE
    CoreCache.ModelID = 188 AND
    CoreArtists.ArtistID = CoreTracks.ArtistID AND
    CoreAlbums.AlbumID = CoreTracks.AlbumID 
AND 1=1
AND LastStreamError = 0
AND (LastPlayedStamp < 1518483204 OR LastPlayedStamp IS NULL)
AND (LastSkippedStamp < 1518483204 OR LastSkippedStamp IS NULL)
ORDER BY RANDOM ()
LIMIT 1;

Here是运行耗时查询时相关表的架构和大小。这些在数据库版本中都是相同的,其中缓慢的随机播放问题会发生并且不会发生。

对发生问题的数据库中的查询运行EXPLAIN QUERY PLAN会给出以下计划:

0|0|0|SEARCH TABLE CoreTracks USING AUTOMATIC COVERING INDEX (LastStreamError=?)
0|1|1|SEARCH TABLE CoreArtists USING INTEGER PRIMARY KEY (rowid=?)
0|2|2|SEARCH TABLE CoreAlbums USING INTEGER PRIMARY KEY (rowid=?)
0|3|4|SEARCH TABLE CoreCache USING AUTOMATIC COVERING INDEX (ModelID=?)
0|4|3|SEARCH TABLE CorePlaylistEntries USING INTEGER PRIMARY KEY (rowid=?)
0|0|0|USE TEMP B-TREE FOR ORDER BY

在没有发生问题的数据库中运行它会产生不同的计划:

0|0|4|SCAN TABLE CoreCache
0|1|3|SEARCH TABLE CorePlaylistEntries USING INTEGER PRIMARY KEY (rowid=?)
0|2|0|SEARCH TABLE CoreTracks USING INTEGER PRIMARY KEY (rowid=?)
0|3|1|SEARCH TABLE CoreArtists USING INTEGER PRIMARY KEY (rowid=?)
0|4|2|SEARCH TABLE CoreAlbums USING INTEGER PRIMARY KEY (rowid=?)
0|0|0|USE TEMP B-TREE FOR ORDER BY

即使在我收集到有关问题的所有信息之后,我也有很多问题。为什么SQLite使用不同的,较慢的查询计划?为什么简单地更新有关CorePlaylistEntriesIndex的缓存信息有时会解决问题,但并非总是如此?这个查询在几乎完全不同的时间运行几乎相同的数据库的基本原因是什么? (我猜测正在或未使用的一些优化)

作为参考,我正在运行SQLite版本3.8.2,并且(当我通过Python调用SQLite时)正在使用Python 3.4.2。我已经尝试运行SQL来在当前版本的SQLite 3.22上产生问题,并发现一些设置命令(带有嵌套SELECT的INSERT)现在花费了很长时间。我简单地尝试将SQLite 3.22可执行文件修补到我的系统中并使用它运行Banshee,但缓慢的shuffle问题没有改变。

1 个答案:

答案 0 :(得分:0)

SQLite将连接实现为嵌套循环,即,对于一个表中的每个(已过滤的)行,它会查找另一个表中的所有匹配行。当行数较少时,运行速度会更快,因此数据库会尝试重新排序连接,以便最外面的表上的WHERE子句筛选出大多数行。

为了估计WHERE子句的选择性,SQLite使用ANALYZE收集的信息。但是当你没有运行ANALYZE时,或者当列未被索引时,数据库没有这个信息,它只是假设column = value形式的任何WHERE子句都非常合适。

表达式LastStreamError = 0查找数据库,好像它可能会过滤掉许多行,但在实践中,它可能不会。

要加快查询速度,请尝试在LastStreamError上添加索引,然后运行ANALYZE。

如果可能,请更新SQLite版本;查询优化器不断得到改进。