1

我很守旧,多年来一直使用 Banshee 作为我的主要音乐播放器。幸运的是,我对它非常满意,除了过去几天我一直在调试的某个问题,在该问题中,当随机播放到播放列表上的新曲目时,它会冻结并且 CPU 使用率很高时间(与播放列表的大小成正比)。例如,512 首曲目的播放列表大约需要 16 秒,或 6000 首曲目的播放列表需要超过 3 分钟。我还在我的工作计算机上使用了 Banshee(尽管库有点小),但问题完全不存在;在任何大小的播放列表上,即使是 6000 首曲目,改组都不会花费可察觉的时间。

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

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

在查看了sqlite_stat1 表是什么之后,我意识到这不是 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;

以下是运行耗时查询时相关表的架构和大小。这些在慢 shuffle 问题发生和不发生的数据库版本中都是相同的。

对发生问题的数据库中的查询运行 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,但慢速随机播放问题没有改变。

4

1 回答 1

0

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

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

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

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

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

于 2018-02-18T21:14:19.103 回答