我有一个典型的小型sqlite数据库,有3个表,一个用于项目(漫画,并有id
s),另一个用于标签(|id|name|
),另一个与它们之间的关联(|tag_id| manga_id|
)所以现在我需要一种方法来搜索项目的标题,并得到他们 * 与 * 标签。有点像这样:
| title | author | tags |
|-----------+-----------------+-------------------------|
| Mushishi | Shuichi Shigeno | supernatural, fantasy |
| Initial D | Yuki Urushibara | racing, sports, deja vu |
所以我也决定使用内置在fts虚拟表中的sqlite。它包含的所有内容都是漫画标题和它们的id。
我实际上设法提出了一个查询,但我对它持谨慎态度:
SELECT manga.title, GROUP_CONCAT(tag.name) tags FROM manga
JOIN mangafts fts ON fts.manga_id = manga.id
JOIN manga_tag_association ass ON ass.manga_id = manga.id
JOIN tag ON tag.id = ass.tag_id
WHERE fts.title MATCH 'mushishi' GROUP BY manga.id;
因为我希望它首先在fts表中查找,然后根据找到的id进行连接,但查询计划如下所示:
QUERY PLAN
|--SCAN manga
|--SEARCH ass USING AUTOMATIC COVERING INDEX (manga_id=?)
|--SEARCH tag USING INTEGER PRIMARY KEY (rowid=?)
`--SCAN fts VIRTUAL TABLE INDEX 3:
我尝试将查询更改为
SELECT manga.title, GROUP_CONCAT(tag.name) tags FROM manga
JOIN mangafts fts
ON (fts.title MATCH 'mushishi' AND fts.manga_id = manga.id)
JOIN manga_tag_association ass ON ass.manga_id = manga.id
JOIN tag ON tag.id = ass.tag_id
GROUP BY manga.id;
然而查询计划是完全相同的。
我其实有几个问题:
1.为什么要扫描manga
表,为什么要先扫描?
1.为什么它不先扫描fts表?我用它的目的就是为了加快搜索速度。
1.我是不是做错了什么,基于我的需要?
编辑:虽然这并不影响计划,但我意识到我应该将匹配搜索写成fts_table_name MATCH 'column: text to search'
,而不是像上面那样。
编辑2:好吧,我不知道为什么之前的片段有这样的计划,但我从头开始重写它,因为我意识到可能有漫画项目没有相关的标签,他们不会显示与这些连接。我把这个信息留在这里,以防别人发现它有用或像我一样学习:)
SELECT manga.id, manga.title, GROUP_CONCAT(tag.name) AS tags FROM manga
LEFT JOIN manga_tag_association ass ON ass.manga_id = manga.id
LEFT JOIN tag ON tag.id = ass.tag_id
JOIN mangafts ON mangafts.manga_id = manga.id
WHERE mangafts MATCH 'title: mushishi' GROUP BY manga.id;
现在的计划是
QUERY PLAN
|--SCAN mangafts VIRTUAL TABLE INDEX 4:
|--SEARCH manga USING INTEGER PRIMARY KEY (rowid=?)
|--SCAN ass LEFT-JOIN
|--SEARCH tag USING INTEGER PRIMARY KEY (rowid=?) LEFT-JOIN
`--USE TEMP B-TREE FOR GROUP BY
1条答案
按热度按时间brgchamk1#
找到最佳的查询计划并不容易,有时我们(人类)会根据查询优化器无法获得的信息做出假设。对于您来说,这是一个漫画,一个标签或文本索引,对于查询计划器来说,只是表A,B,C,D,由一些字段连接。
你假设sqlite应该首先扫描FTS表,因为你知道它会过滤掉大部分结果行。但是在FTS虚拟表中搜索比扫描普通的“真实的”表更复杂,所以sqlite可能会尝试搜索尽可能低的表。
此外,GROUP BY(和ORDER BY)是昂贵的操作,成本越高,必须排序/分组的行越多,因此如果计划可以避免执行这些操作,sqlite将尝试避免它们。
由于您是按www.example.com分组manga.id,因此扫描该表将避免单独排序的需要(我假设id是一个整数主键,所以表已经按id排序)。你知道每个manga都有标签关联,并且存在于mangafts中,但是sqlite不知道,它可以假设JOIN会减少总行数,因此需要更少的mangafts搜索。此外,它可以决定通过ID搜索漫画比通过标题搜索更快。
当您更改查询并添加一些左连接时,现在sqlite知道这些左连接不会减少行数,并且可以假定最好按标题搜索漫画,然后从漫画中获取相应的行,即使这意味着它必须稍后对它们进行排序。
所有这些评估都可以根据每个表中的行数和连接条件的选择性而更改。ANALYZE将从表中获取一些数据,查询规划器可以使用这些数据来计算更快的计划,但有时即使是这些数字也无法告诉查询规划器您对数据的了解。
也许Sqlite选择这个计划是因为你的数据库中只有一千个manga,而这个计划对于如此少的记录来说和其他任何计划一样快,但是如果你有一百万个manga,sqlite甚至可以用你最初的查询计算出一个更好的计划。
正如NickW在评论中所指出的,如果没有性能问题,那么尝试优化查询计划是毫无意义的,因为如果出现问题,条件和查询计划可能与您现在看到的大不相同。