提高 SQLite 反连接性能

Byr*_*Bos 6 sqlite performance join

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

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

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

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

--Both tables have a multi column index on item_id and time
Run Code Online (Sandbox Code Playgroud)

源表包含大约 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;
Run Code Online (Sandbox Code Playgroud)

仅不带 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
);
Run Code Online (Sandbox Code Playgroud)

非常感谢!

更新

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

看起来 sqliteman 任意地将返回的行数限制为 256,并且当您滚动它们时会更动态地加载。这将使对大型数据集的查询看起来比实际快得多,使其成为估计查询性能的糟糕选择。

尽管如此,他们是否有任何明显的方法来提高这个查询的性能,或者我只是达到了 SQLite 能力的极限?

CL.*_*CL. 3

这是您的查询的查询计划(任一):

0|0|0|SCAN TABLE source
0|1|1|SEARCH TABLE target USING COVERING INDEX ti (item_id=? AND time=?)
Run Code Online (Sandbox Code Playgroud)

这几乎是尽可能有效的:

  1. source必须检查每一行,通过
  2. 在 中搜索匹配的行target

也许可以做出一点点改进。这些source行可能没有排序,因此target搜索将在索引中的随机位置进行查找。如果我们可以强制source按索引顺序进行扫描,则target查找也将按顺序进行,这使得这些索引页更有可能已经在缓存中。

source如果我们不使用不在索引中的任何列,即如果我们删除该列, SQLite 将使用该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=?)
Run Code Online (Sandbox Code Playgroud)

这可能没有多大帮助。但如果它有帮助,并且如果您想要 中的其他列source,您可以通过首先执行连接,然后按行查找行来完成此操作sourcerowid如果结果很少,则额外的查找应该不会造成伤害):

SELECT *
FROM source
WHERE rowid IN (SELECT source.rowid
                FROM source
                LEFT JOIN target USING (item_id, time)
                WHERE target.item_id IS NULL)
Run Code Online (Sandbox Code Playgroud)