DB引擎如何决定JOIN操作的查询计划?
Posted
技术标签:
【中文标题】DB引擎如何决定JOIN操作的查询计划?【英文标题】:How DB engine decides the query plan for JOIN operation? 【发布时间】:2020-08-13 19:57:41 【问题描述】:使用以下架构:
sqlite>
sqlite>
sqlite> .schema
CREATE TABLE movie (
id INTEGER PRIMARY KEY, title TEXT, year INTEGER, nth TEXT, for_video BOOLEAN
);
CREATE TABLE actor (
id INTEGER PRIMARY KEY, name TEXT, gender TEXT
);
CREATE TABLE role (
movie_id INTEGER, actor_id INTEGER, name TEXT
);
CREATE TABLE sqlite_stat1(tbl,idx,stat);
sqlite>
在两个表上运行JOIN
,如下所示:
sqlite> select * from movie JOIN role ON (movie.id = role.movie_id) WHERE movie.title='Batman' LIMIT 1;
"id" "title" "year" "nth" "for_video" "movie_id" "actor_id" "name"
"47844" "Batman" "1989" "" "0" "47844" "84264" "Napier Hood"
sqlite>
为了性能,我添加了以下索引:
sqlite> create index id1 on role(movie_id);
sqlite>
sqlite>
sqlite> create index id2 on movie(title);
sqlite>
sqlite>
然后查询计划说:
案例一
sqlite> EXPLAIN QUERY PLAN select * from movie JOIN role ON (movie.id = role.movie_id) WHERE movie.title='Batman' LIMIT 1;
"selectid" "order" "from" "detail"
"0" "0" "0" "SEARCH TABLE movie USING INDEX id2 (title=?)"
"0" "1" "1" "SEARCH TABLE role USING INDEX id1 (movie_id=?)"
案例 2
sqlite> EXPLAIN QUERY PLAN select movie.title, role.name from movie JOIN role ON (movie.id = role.movie_id)
...> WHERE role.name = 'King Arthur' LIMIT 2;
"selectid" "order" "from" "detail"
"0" "0" "0" "SCAN TABLE movie USING COVERING INDEX id2"
"0" "1" "1" "SEARCH TABLE role USING INDEX id1 (movie_id=?)"
sqlite>
sqlite>
对于给定的两种情况:
DB引擎如何决定,它需要先搜索movie
表,然后搜索role
表?
为什么 DB engine SCAN
ning movie
table 在第二种情况下?而不是SEARCH
【问题讨论】:
你使用的是 mysql 还是 SQLite? @jarlh 正在使用 sqlite 【参考方案1】:DB引擎是如何决定先搜索电影表再搜索角色表的?
你的WHERE
子句通过它的title
请求一部电影,并且为此有一个索引,所以肯定先只获取具有该标题的电影,然后获取它们的 ID,然后获取具有这些 ID 的角色(也有索引查找),然后将几个结果连接在一起。
反过来就没有意义了:获取所有 80,000 部电影,然后将它们与 1000 个不同的角色联系起来,给出一个包含 80,000 个电影角色的列表,然后把它们都扔掉,除了标题为 X 的那个
这是一个关于特定数据库如何计划此特定查询的非常简单的视图;有许多不同的方式可以计划和执行查询。解释优化器/规划器采取的每一步和每一个决定都超出了 SO 答案的范围
对于您的第二种情况,SQLite 似乎已经得出结论,它必须通过未索引的内容进行搜索,并且必须返回两位数据;一个是索引的,一个不是。它决定了一种策略,将所有电影标题从索引而不是表中提取出来(索引可以提供标题,SQLite 更喜欢使用它来检索数据而不是表),将电影加入角色基于角色中的 movie_id 索引,然后过滤所有工作,只留下亚瑟王角色名称和相关的电影标题
为什么 DB 引擎在第二种情况下扫描电影表?而不是搜索
它不是在搜索表,它是在扫描索引,它正在执行扫描,因为查询不要求任何被索引的东西,因此必须检索每个值并进行比较以找到您要查找的内容
【讨论】:
我在查询中添加了另一个案例,尽管我在role
上给出了 WHERE 子句,但它首先是 movie
我们是否会有你提出的问题之一,我们回答,然后你稍微编辑问题,我们编辑答案,你再次移动球门柱,我们兜兜转转?因为如果我们是的话,我希望你只是阅读关于查询计划的详细手册 - sqlite.org/queryplanner.html 和 sqlite.org/eqp.html【参考方案2】:
不是特定于 mySql - 所以如果 MySQL 绝对低于标准,这更像是“在你的语句中从左到右”。
通常,任何数据库服务器都有 STATISTICS 和 estimages 它将针对哪个索引上的给定过滤器返回多少行,然后它会尝试各种方法(全部基于这些统计数据)以查看哪种方法最有效。然后它执行这个。当您按 Movie.Title 进行过滤时,它很可能会先执行此操作,然后在另一个表中找到匹配的行。
【讨论】:
以上是关于DB引擎如何决定JOIN操作的查询计划?的主要内容,如果未能解决你的问题,请参考以下文章