改善SQLite反连接性能

时间:2014-06-06 01:56:59

标签: performance sqlite join

查看此问题底部的更新,下面提到的查询时间意外差异的原因已被确定为sqliteman怪癖的结果

我在SQLite数据库中有以下两个表(结构可能看起来毫无意义,我知道但是请耐心等待)

+-----------------------+
| source                |
+-----------------------+
| item_id | time | data |
+-----------------------+

+----------------+
| target         |
+----------------+
| item_id | time |
+----------------+

--Both tables have a multi column index on item_id and time

源表包含大约500,000行,目标表中永远不会有多个匹配记录,实际上几乎所有源行都有匹配的目标行。

我正在尝试执行一个相当标准的反连接来查找源中没有相应行的所有记录,但我发现很难创建一个具有可接受执行时间的查询。

我使用的查询是:

SELECT
    source.item_id,
    source.time,
    source.data
FROM source
LEFT JOIN target USING (item_id, time)
WHERE target.item_id IS NULL;

没有WHERE子句的LEFT JOIN需要大约200ms来完成,用它增加到5000ms。

虽然我最初注意到我的消费应用程序中的慢查询,但是通过直接从sqliteman中执行语句来获得上述时间。

为什么这个看似简单的条款会大大增加执行时间,是否有某种方法可以重构这个查询以改进它?

我也尝试了以下相同的结果。 (我想底层的查询计划是一样的)

SELECT 
    source.item_id,
    source.time,
    source.data
FROM source
WHERE NOT EXISTS (
    SELECT 1 FROM target
    WHERE target.item_id = source.item_id
    AND target.time = source.time
);

非常感谢!

更新

非常抱歉,事实证明这些明显的结果实际上是由于对sqliteman的怪癖。

似乎sqliteman任意对返回到256的行数应用限制,并且在滚动它们时会更加动态加载。这将使对大型数据集的查询显得比实际更快,使其成为估算查询性能的不良选择。

尽管如此,它们是改善此查询性能的明显方法,还是我只是限制了SQLite的功能?

1 个答案:

答案 0 :(得分:1)

这是您的查询query plan(其中之一):

0|0|0|SCAN TABLE source
0|1|1|SEARCH TABLE target USING COVERING INDEX ti (item_id=? AND time=?)

这几乎尽可能高效:

  1. 必须通过
  2. 检查source中的每一行
  3. target中搜索匹配的行。
  4. 有可能做一点改进。 source行可能没有排序,因此target搜索将在索引中的随机位置进行查找。 如果我们可以强制source扫描按索引顺序排列,target查找也将按顺序排列,这使得这些索引页面更有可能已经在缓存中。

    如果我们不使用索引中没有的任何列,SQLite将使用source索引,即,如果我们删除data列:

    > EXPLAIN QUERY PLAN
      SELECT source.item_id, source.time
      FROM source
      LEFT JOIN target USING (item_id, time)
      WHERE target.item_id IS NULL;
    0|0|0|SCAN TABLE source USING COVERING INDEX si
    0|1|1|SEARCH TABLE target USING COVERING INDEX ti (item_id=? AND time=?)
    

    这可能没多大帮助。 但如果它有帮助,并且如果您想要source中的其他列,则可以先执行加入,然后按source查找rowid行(额外的如果结果很少,查找不应该受到影响):

    SELECT *
    FROM source
    WHERE rowid IN (SELECT source.rowid
                    FROM source
                    LEFT JOIN target USING (item_id, time)
                    WHERE target.item_id IS NULL)