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 SCANning 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操作的查询计划?的主要内容,如果未能解决你的问题,请参考以下文章

火山引擎:ClickHouse增强计划之“多表关联查询”

火山引擎:ClickHouse增强计划之“多表关联查询”

火山引擎:ClickHouse增强计划之“多表关联查询”

MySQL 执行计划说明

在 Spark 上打印查询 Hive 的物理计划

DRDS分布式SQL引擎—执行计划介绍