【问题标题】:Improve SQLite anti-join performance提高 SQLite 反连接性能
【发布时间】:2014-06-06 01:56:59
【问题描述】:

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

我在 SQLite DB 中有以下两个表(我知道该结构似乎毫无意义,但请耐心等待)

+-----------------------+
| 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 大约需要 200 毫秒才能完成,而这会增加到 5000 毫秒。

虽然我最初注意到我的消费应用程序中的查询速度很慢,但上面的时间是通过直接从 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 的能力极限?

【问题讨论】:

  • 您如何准确测量执行时间? IS NULL 查询不应该更慢。
  • 我在问题中添加了一个注释。这些时间是使用 sqliteman 中的查询工具获得的。
  • 这实际上获取了所有结果行吗?如果没有,请改为测量SELECT COUNT(*) FROM (the actual query)
  • 真诚的道歉,我应该使用其他查询工具验证执行时间,因为事实证明更快的查询是 sqliteman 怪癖的结果。谢谢!
  • 查询计划实际上并没有改变,但是当结果较少时,需要搜索更多的表行才能得到前256个结果。

标签: performance sqlite join


【解决方案1】:

这是您查询的query plan(任意一个):

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

这是尽可能高效的:

  1. source 中的每一行都必须检查,由
  2. target 中搜索匹配行。

也许可以做一点小改进。 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 中的其他列,您可以先执行连接,然后通过它们的 rowid 查找 source 行(如果您使用额外的查找应该不会受到伤害)结果很少):

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

【讨论】:

  • 非常感谢。我可以确认,虽然您描述的查询最初并没有更快,但随后的调用速度明显加快,这是我原始查询未观察到的行为。
猜你喜欢
  • 2012-06-16
  • 2013-02-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-06-11
  • 1970-01-01
相关资源
最近更新 更多