1

我在 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 中,我运行上面的查询,然后使用相同的连接我再次运行相同的查询两次,但是第二次用p1.Sex = 1p1.Sex = 2替换l1.Name LIKE 'Juniors%部分. 最后两个查询在这两种情况下都很快,这进一步证明问题来自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 插件的 EXPLAIN QUERY PLAN 是:

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 是关于 Season、Name、RealName 的索引,根据@CL 发布的此链接中的 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 中测试我的查询,然后再在我的应用程序中尝试它们,因为这样更快。

4

1 回答 1

3

差异可能是由于不同的 SQLite 版本。(检查SELECT sqlite_version();。)

阅读优化器清单
在这个特定的查询中,您可以通过编写强制使用索引:

... JOIN Leagues l1 INDEXED BY MyThreeColumnIndex ON ...
于 2013-11-09T08:41:50.703 回答