使用 JDBC 的 SQLite 查询比使用 Firefox SqliteManager 插件慢得多

Posted

技术标签:

【中文标题】使用 JDBC 的 SQLite 查询比使用 Firefox SqliteManager 插件慢得多【英文标题】:SQLite queries much slower using JDBC than in Firefox SqliteManager plugin 【发布时间】:2013-11-08 18:40:32 【问题描述】:

我在 Firefox 的 SQLiteManager 插件中测试的查询有问题。

这个特定的查询在 Firefox 插件中执行大约需要 60 毫秒,但是当我使用最新的 Sqlite JDBC 驱动程序在 Java 中使用相同的查询和相同的数据库时,它需要高达 3.7 秒的时间来执行。

对于我的其他运行良好的查询,Firefox 插件通常要快一点(顶部快 50 毫秒,有时 JDBC 更快),但这可能是创建连接并将结果添加到列表的开销,但是这个特定查询的性能差异简直荒谬。

这里是查询:

SELECT p1.Id, p1.FirstName || ' ' || p1.LastName AS PlayerName, sch1.LaneNum, l1.Name AS LeagueName, l1.Season, SUM(s1.Score) AS Series, e1.Date FROM Scores s1
JOIN SchedulePlayers sp1 ON  s1.SchedulePlayerId = sp1.Id
JOIN Schedules sch1 ON sp1.ScheduleId = sch1.Id
JOIN Players p1 ON sp1.PlayerId = p1.Id
JOIN TeamEncounters te1 ON sch1.TeamEncounterId = te1.Id
JOIN Encounters e1 ON te1.EncounterId = e1.Id
JOIN Leagues l1 ON e1.LeagueId = l1.Id

WHERE s1.GameNum < 4 AND l1.Name LIKE 'Juniors%' AND l1.Season = 2013 AND (sch1.LaneNum = 1 OR  sch1.LaneNum = 2) AND s1.IsBowlout = 0
GROUP BY p1.Id, l1.Id, e1.Id
ORDER BY Series DESC LIMIT 0,20

很明显,慢的部分是“LIKE 'Juniors%'”,但这并不能解释为什么它在 Java 中慢而不是在插件中。

如果我执行 EXPLAIN QUERY PLAN,我看到 firefox 插件对 Leagues 表使用以下索引: Columns: "Season, Name, RealName"(此查询中尚未使用 RealName )。

如果我在 Java 中执行 EXPLAIN QUERY PLAN,用于 Leagues 表的索引是 INTEGER PRIMARY KEY 索引,这是我认为问题所在。

在 java 中,我运行上面的查询,然后使用相同的连接我再次运行相同的查询两次,但是将 l1.Name LIKE 'Juniors% 部分替换为 p1 .Sex = 1p1.Sex = 2 第二次。最后两个查询在这两种情况下都很快,这进一步证明问题来自 l1.Name LIKE 'Juniors%'

我在所有表上都有主键,在所有需要它的列上都有外键。我还有许多其他索引,因为我正在从头开始重新设计旧数据库,因为有很多重复的字段,我决定添加索引以使其更快,但在这种特殊情况下,我被卡住了,特别是因为它可以在一个案例,但不是另一个。是否有可能是我对表的索引过于激进,导致经理更难选择正确的索引?

请随时询问有关表、列、查询等的更多信息。

编辑

Firefox 插件使用 SQLite 3.7.17,JDBC 驱动程序使用 SQLite 3.8.0。我尝试使用 3.7.20 JDBC 驱动程序(找不到 3.7.17 驱动程序的下载链接),我遇到了同样的性能问题,并且其他一些查询的性能更差,所以我切换回 3.8 .0.

我编辑了性能时间,因为我在基准测试时犯了一个错误:之前的时间是为了多次运行查询。所以在 Firefox 中,执行一次查询大约需要 60 毫秒,而在 Java 中则需要 3600 毫秒,因此是 60 倍以上,这对于我的应用程序来说是不可接受的。

这是来自 Java 查询执行的详细 EXPLAIN QUERY PLAN,其中各列按顺序排列:SelectId、Order、From、Detail:

0 0 0 SEARCH TABLE Scores AS s1 USING INDEX idxScoresGameNumScore (GameNum<?)
0 1 1 SEARCH TABLE SchedulePlayers AS sp1 USING INTEGER PRIMARY KEY (rowid=?)
0 2 3 SEARCH TABLE Players AS p1 USING INTEGER PRIMARY KEY (rowid=?)
0 3 2 SEARCH TABLE Schedules AS sch1 USING INTEGER PRIMARY KEY (rowid=?)
0 0 0 EXECUTE LIST SUBQUERY 1
0 4 4 SEARCH TABLE TeamEncounters AS te1 USING INTEGER PRIMARY KEY (rowid=?)
0 5 5 SEARCH TABLE Encounters AS e1 USING INTEGER PRIMARY KEY (rowid=?)
0 6 6 SEARCH TABLE Leagues AS l1 USING INTEGER PRIMARY KEY (rowid=?)
0 0 0 USE TEMP B-TREE FOR GROUP BY
0 0 0 USE TEMP B-TREE FOR ORDER BY

如您所见,Leagues 使用整数主键,因此它完全忽略了其中包含“名称”的索引。

Firefox 插件的解释查询计划是:

0 0 6 SEARCH TABLE Leagues AS l1 USING COVERING INDEX idxLeaguesRealName (Season=?) (~19 rows)
0 1 5 SEARCH TABLE Encounters AS e1 USING INDEX idxEncounters (LeagueId=?) (~16 rows)
0 2 4 SEARCH TABLE TeamEncounters AS te1 USING AUTOMATIC COVERING INDEX (EncounterId=?) (~6 rows)
0 3 2 SEARCH TABLE Schedules AS sch1 USING INDEX sqlite_autoindex_Schedules_1 (TeamEncounterId=?) (~1 rows)
0 4 1 SEARCH TABLE SchedulePlayers AS sp1 USING COVERING INDEX idxSchedulePlayers (ScheduleId=?) (~6 rows)
0 5 3 SEARCH TABLE Players AS p1 USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)
0 6 0 SEARCH TABLE Scores AS s1 USING INDEX sqlite_autoindex_Scores_1 (SchedulePlayerId=? AND GameNum<?) (~1 rows)
0 0 0 USE TEMP B-TREE FOR GROUP BY
0 0 0 USE TEMP B-TREE FOR ORDER BY

如您所见,表的顺序也不相同,实际上,所有表都使用 Java 中的 PRIMARY KEY 索引,而它们使用 Java 中似乎“更好”的索引, 我觉得很奇怪。

*我尝试在 JOIN Leagues l1 之后使用 INDEXED BY idxLeaguesRealName *,但性能保持不变(可能是因为 SEARCH TABLE Leagues 在 Java 中位于底部而不是第一个表)。

idxLeaguesRealName 是关于赛季、姓名、实名的索引,根据@CL 发布的link 中的 5.3。是一个低质量指数,因为赛季对于 230 个不同的联赛只需要大约 4 个不同的值。不过,我在运行查询之前已经运行了 ANALYZE 命令,因此根据该链接,它应该可以解决使用低质量索引的问题。

我尝试的另一件事是创建一个新索引,该索引也使用主键字段(例如:Id、Season、Name),但 Query Planner 不使用它。 我什至不知道将主键作为用户创建的索引中的一个字段是否是个好主意。我只是在尝试我能想到的一切,因为我因为我不了解运行查询的两种方式之间的性能差异,所以在这里迷路了。

关于其他几乎相同的查询的额外信息

正如我之前提到的,我运行的其他查询几乎相同,除了 l1.Name LIKE 'Juniors%' 被替换为 p1.Sex = 1p1.Sex = 2。这些查询在 Firefox 中的执行时间约为 62 毫秒,在 Java 中为 52 毫秒,这意味着查询规划器在此类似查询上做得很好。

在 JDBC 中,EXPLAIN QUERY PLAN 给出以下输出:

0 0 4 SCAN TABLE TeamEncounters AS te1 USING COVERING INDEX idxTeamEncounters
0 1 5 SEARCH TABLE Encounters AS e1 USING INTEGER PRIMARY KEY (rowid=?)
0 2 2 SEARCH TABLE Schedules AS sch1 USING INDEX sqlite_autoindex_Schedules_1 (TeamEncounterId=?)
0 0 0 EXECUTE LIST SUBQUERY 1
0 3 6 SEARCH TABLE Leagues AS l1 USING INTEGER PRIMARY KEY (rowid=?)
0 4 1 SEARCH TABLE SchedulePlayers AS sp1 USING COVERING INDEX idxSchedulePlayers (ScheduleId=?)
0 5 3 SEARCH TABLE Players AS p1 USING INTEGER PRIMARY KEY (rowid=?)
0 6 0 SEARCH TABLE Scores AS s1 USING INDEX sqlite_autoindex_Scores_1 (SchedulePlayerId=? AND GameNum<?)
0 0 0 USE TEMP B-TREE FOR GROUP BY
0 0 0 USE TEMP B-TREE FOR ORDER BY

这与原始查询的计划有很大不同,因为这个查询使用的索引似乎比其他情况下仅使用 PRIMARY KEYs 索引更有意义。

我刚刚检查过,我的应用程序中还有其他查询执行缓慢。所有慢查询都是那些有 'l1.Name LIKE 'Juniors%',其他的都运行得非常快。

我读到使用 LIKE 的查询运行缓慢,这会让我改变我设计一些表格的方式,比如添加一个字段“IsJuniorLeague”并与之进行比较,这可能会解决问题,但是由于我已经看到可以使这些查询足够快,就像在 Firefox 插件中一样,所以我真的很想了解幕后发生的事情,因为我通常先在 Firefox 中测试我的查询,然后再在我的应用程序中尝试它们这样更快。

【问题讨论】:

【参考方案1】:

差异可能是由于不同的 SQLite 版本。 (与SELECT sqlite_version();联系。)

阅读optimizer checklist。 在这个特定的查询中,您可以通过以下方式强制使用索引:

... JOIN Leagues l1 INDEXED BY MyThreeColumnIndex ON ...

【讨论】:

感谢您的回复,非常感谢。我尝试切换 SQLite 版本,但我遇到了同样的问题。至于INDEXED BY [index],我也试过了,也没有成功(它使用索引,但是表搜索的顺序不一样,所以看起来完全是另一个问题)。有关详细信息,请参阅我编辑的问题。 我跳过了这一点,因为它提到要在未分析的数据库上执行此操作,但它最终解决了我的问题。非常感谢! 好链接:“优化器清单”。我有类似的情况,Navicat 中的查询运行速度很快(v3.7),但在 System.Data.SQLite(v3.8)中运行缓慢。我发现清单中的第 4 点起到了作用:运行“分析;”。由于某种原因,使用 v3.8 服务器访问文件的软件使用了错误的统计信息(查询时间 ~ 2 分钟),而 v3.7 没有问题(查询时间 ~ 0.16 秒)。

以上是关于使用 JDBC 的 SQLite 查询比使用 Firefox SqliteManager 插件慢得多的主要内容,如果未能解决你的问题,请参考以下文章

使用 System.Data.SQLite (C#) 的 SQLite 查询比在 SQLiteStudio 中要慢得多

使用 java 中的 jdbc 驱动程序进行缓慢的 sqlite 插入

SQLite 无法通过 JDBC 和 jOOQ 在 SELECT 中找到现有列

在 sqlite 中删除查询

SQLite/JDBC 内连接

JDBC - SQLITE 选择变量