为什么此SQLite查询不为相关子查询使用索引?

时间:2018-08-18 08:39:34

标签: sqlite

考虑一个包含部分内容的SQLite数据库,其中包含下表

CREATE TABLE thing (id integer PRIMARY KEY, name text, total_cost real);
CREATE TABLE part (id integer PRIMARY KEY, cost real);
CREATE TABLE thing_part (thing_id REFERENCES thing(id), part_id REFERENCES part(id));

我有一个索引来查找事物的各个部分

CREATE INDEX thing_part_idx ON thing_part (thing_id);

为说明问题,我使用以下查询用随机数据填充表

INSERT INTO thing(name)
    WITH RECURSIVE
        cte(x) AS (
            SELECT 1
            UNION ALL
            SELECT 1 FROM cte LIMIT 10000
        )
SELECT hex(randomblob(4)) FROM cte;
INSERT INTO part(cost)
    WITH RECURSIVE
        cte(x) AS (
            SELECT 1
            UNION ALL
            SELECT 1 FROM cte LIMIT 10000
        )
SELECT abs(random()) % 100 FROM cte;
INSERT INTO thing_part (thing_id, part_id)
SELECT thing.id, abs(random()) % 10000 FROM thing, (SELECT 1 UNION ALL SELECT 1), (SELECT 1 UNION ALL SELECT 1);

因此,每个事物都与少量零件(在此示例中为4个零件)关联。

至此,我还没有确定事情的总费用。我以为我可以使用以下查询

UPDATE thing SET total_cost = (
    SELECT sum(part.cost)
    FROM thing_part, part
    WHERE thing_part.thing_id = thing.id
    AND thing_part.part_id = part.id);

但是它非常慢(我没有耐心等待它完成)。

EXPLAIN QUERY PLAN显示thingthing_part都被扫描了,只有part中的查询是使用rowid完成的:

SCAN TABLE thing
EXECUTE CORRELATED SCALAR SUBQUERY 0
SCAN TABLE thing_part
SEARCH TABLE part USING INTEGER PRIMARY KEY (rowid=?)

如果我查看带有固定thing_id的内部查询的查询计划,即

SELECT sum(part.cost)
FROM thing_part, part
WHERE thing_part.thing_id = 1000
AND thing_part.part_id = part.id;

它确实使用了thing_part_idx

SEARCH TABLE thing_part USING INDEX thing_part_idx (thing_id=?)
SEARCH TABLE part USING INTEGER PRIMARY KEY (rowid=?)

我希望第一个查询等同于遍历thing的所有行并每次都执行内部查询,但是显然并非如此。为什么?我应该使用其他索引还是重写查询,或者应该在客户端中进行迭代来生成多个查询?

如果有问题,我正在使用SQLite 3.22.0版

2 个答案:

答案 0 :(得分:0)

我会将您的查询重写为:

-- calculating sum for each thing_id at once
WITH cte AS (
   SELECT thing_part.thing_id, sum(part.cost) AS s
    FROM thing_part 
    JOIN part
      ON thing_part.part_id = part.id
    GROUP BY thing_part.thing_id
)
UPDATE thing 
SET total_cost = (SELECT s FROM cte WHERE thing.id = cte.thing_id);

答案 1 :(得分:0)

SQLite可能使用动态类型,但是列类型对于affinity仍然很重要,并且只有在数据库可以证明索引查找的行为与对实际表值的比较相同时,才可以使用索引。亲和力要兼容。

因此,当您告诉数据库thing_part的值是整数时:

CREATE TABLE thing_part (
  thing_id integer REFERENCES thing(id),
  part_id  integer REFERENCES part(id)
);

然后该索引上的索引将具有正确的亲和力,并将被使用:

QUERY PLAN
|--SCAN TABLE thing
`--CORRELATED SCALAR SUBQUERY
   |--SEARCH TABLE thing_part USING INDEX thing_part_idx (thing_id=?)
   `--SEARCH TABLE part USING INTEGER PRIMARY KEY (rowid=?)