我在Firefox中的SQLiteManager插件中测试了一个查询问题。
这个特定的查询在Firefox插件中执行大约60毫秒,但是当我使用最新的Sqlite JDBC驱动程序在Java中使用相同的查询和相同的数据库时,执行需要大约3.7秒。
对于其他运行良好的查询(在顶部快50分钟,有时JDBC速度更快),Firefox插件通常会快一点,但这可能是创建连接并将结果添加到列表,但这个特定查询的性能差异是荒谬的。
以下是查询:
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
显然,缓慢的部分是&#34; LIKE&#39; Juniors%&#39;&#34;,但这并不能解释为什么它在Java中很慢而不在插件中。
如果我执行 EXPLAIN QUERY PLAN ,我看到firefox插件为Leagues表使用以下索引:Columns:&#34; Season,Name,RealName&#34; (RealName尚未在此查询中使用)。
如果我在Java中执行 EXPLAIN QUERY PLAN ,那么用于Leagues表的索引就是INTEGER PRIMARY KEY索引,这是我认为问题所在。
在java中,我运行上面的查询,然后使用相同的连接,我再运行两次相同的查询,但用<替换 l1.Name LIKE&#39; Juniors%部分em> p1.Sex = 1 和 p1.Sex = 2 第二次。在这两种情况下,最后两个查询都很快,这进一步证明问题来自 l1.Name LIKE&#39; Juniors%&#39;
我在所有表上都有主键,而在需要它的所有列上都有外键。我还有许多其他索引,因为我从头开始重新设计旧数据库,因为有很多重复的字段,我决定添加索引以使其更快,但在这种特殊情况下,我被卡住了,特别是因为它有效在一个案例但不是另一个案件。我是否有可能过于积极地对表进行索引,这使得Manager更难以选择正确的索引?
随时可以询问有关表格,列,查询等的更多信息。
修改
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使用整数主键,因此它完全忽略了具有&#39; Name&#39;在它。
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索引,而他们使用的似乎是更好的& #39;我认为Java中的索引很奇怪。
* 我在JOIN Leagues l1之后尝试使用INDEXED BY idxLeaguesRealName * ,但性能保持不变(可能是因为SEARCH TABLE Leagues在Java而不是第一个表中位于底部)。 / p>
idxLeaguesRealName是Season,Name,RealName的索引,根据@CL发布的link中的5.3。是一个低质量的指数,因为赛季只有大约4个不同的值,230个不同的联赛。我在运行查询之前运行了 ANALYZE 命令,因此根据该链接,它应该解决使用低质量索引的问题。
我尝试的另一件事是创建一个也使用主键字段的新索引(例如:Id,Season,Name),但查询规划器并没有使用它。 我甚至不知道将主键作为用户创建的索引中的一个字段是否是个好主意。我只是尝试了我能做的一切想想,因为我不知道这两种运行查询的方式之间的性能差异,因此我迷失在这里。
关于其他几乎相同的查询的其他信息
正如我之前提到的,我运行几乎相同的其他查询,除了 l1.Name LIKE&#39; Juniors%&#39; 被 p1替换。性别= 1 或 p1.Sex = 2 。这些查询在Firefox中每个执行大约62ms,在Java中执行大约52ms,这意味着查询计划程序在这个类似的查询上做得很好。
在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索引更有意义。比如在其他情况下。
我刚检查过,我的应用程序中还有其他查询执行缓慢。所有缓慢的查询都是具有&#39; l1.Name LIKE&#39; Juniors%&#39; 的查询,其他所有查询都运行得非常快。
我已经读过使用 LIKE 的查询运行缓慢,这会让我改变我设计一些表的方式,比如添加一个字段&#39; IsJuniorLeague&#39;相比之下,这可能会解决这个问题,但是因为我已经看到它可以让这些查询足够快,就像在Firefox插件中一样,我真的很想知道背后发生了什么因为我通常在我的应用程序中尝试查询之前通常在firefox中测试我的查询,因为它的速度更快。
答案 0 :(得分:3)
差异可能是由于不同的SQLite版本所致。
(查看SELECT sqlite_version();
。)
阅读optimizer checklist 在此特定查询中,您可以通过编写强制使用索引:
... JOIN Leagues l1 INDEXED BY MyThreeColumnIndex ON ...