摘要:在数据库查询中,往往会需要查询多个表的数据,比如查询会员信息同时查询关于这个会员的订单信息,如果分语句查询的话,效率会很低,就需要用到join关键字来连表查询了。

Join并行

Join并行1. 多表join介绍2. 多表Join的方式不使用Join buffer使用Join buffer3. Join执行流程(老执行器)

1. 多表join介绍

JOIN子句用于根据两个或多个表之间的相关列来组合它们。 例如:

Orders:

Customers:

SELECT Orders.OrderID, Customers.CustomerName, Orders.OrderDate
FROM Orders
INNER JOIN Customers ON Orders.CustomerID=Customers.CustomerID;

2. 多表Join的方式

Hash join使用新执行器实现,在这里不做讨论

MySQL支持的都是Nested-Loop Join,以及它的变种。

不使用Join buffer

a) Simple Nested-Loop

对r表的每一行,完整扫描s表,根据r[i]-s[i]组成的行去判断是否满足条件,并返回满足条件的结果给客户端。

mysql> show create table t1;
+-------+----------------------------------------------------------------------------------------------------------------+
| Table | Create Table |
+-------+----------------------------------------------------------------------------------------------------------------+
| t1 | CREATE TABLE `t1` (
`id` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci |
+-------+----------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec) mysql> show create table t3;
+-------+--------------------------------------------------------------------------------------------------------------------+
| Table | Create Table |
+-------+--------------------------------------------------------------------------------------------------------------------+
| t3 | CREATE TABLE `t3` (
`id` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci |
+-------+--------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec) mysql> explain select /*+ NO_BNL() */ * from t1, t3 where t1.id = t3.id;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | t1 | NULL | ALL | NULL | NULL | NULL | NULL | 2 | 100.00 | NULL |
| 1 | SIMPLE | t3 | NULL | ALL | NULL | NULL | NULL | NULL | 2 | 50.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)

b) Index Nested-Loop

对r表的每一行,先根据连接条件去查询s表索引,然后回表查到匹配的数据,并返回满足条件的结果给客户端。

mysql> show create table t2;
+-------+---------------------------------------------------------------------------------------------------------------------------------------+
| Table | Create Table |
+-------+---------------------------------------------------------------------------------------------------------------------------------------+
| t2 | CREATE TABLE `t2` (
`id` int(11) NOT NULL,
KEY `index1` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci |
+-------+---------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec) mysql> explain select * from t1, t2 where t1.id = t2.id;
+----+-------------+-------+------------+------+---------------+--------+---------+------------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+--------+---------+------------+------+----------+-------------+
| 1 | SIMPLE | t1 | NULL | ALL | NULL | NULL | NULL | NULL | 2 | 100.00 | NULL |
| 1 | SIMPLE | t2 | NULL | ref | index1 | index1 | 4 | test.t1.id | 1 | 100.00 | Using index |
+----+-------------+-------+------------+------+---------------+--------+---------+------------+------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)

使用Join buffer

a) Block Nested Loop

从r表读取一部分数据到join cache中,当r表数据读完或者join cache满后,做join操作。

JOIN_CACHE_BNL::join_matching_records(){
do {
//读取s表的每一行
qep_tab->table()->file->position(qep_tab->table()->record[0]);
//针对s的每一行,遍历join buffer
for(each record in join buffer) {
get_record();
rc = generate_full_extensions(get_curr_rec());
//如果不符合条件,直接返回
if (rc != NESTED_LOOP_OK) return rc;
}
} while(!(error = iterator->Read()))
}
mysql> explain select  * from t1, t3 where t1.id = t3.id;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
| 1 | SIMPLE | t1 | NULL | ALL | NULL | NULL | NULL | NULL | 2 | 100.00 | NULL |
| 1 | SIMPLE | t3 | NULL | ALL | NULL | NULL | NULL | NULL | 2 | 50.00 | Using where; Using join buffer (Block Nested Loop) |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
2 rows in set, 1 warning (0.00 sec)

b) Batched Key Access

从r表读取一部分数据到join cache中,s表中记录r表被连接的列的值作为索引,查询所有符合条件的索引,然后将这些符合条件的索引排序,然后统一回表查询记录。

其中,对于每一个cached record,都会有一个key,通过这个key去s表扫描所需的数据。

dsmrr_fill_buffer(){
while((rowids_buf_cur < rowids_buf_end) &&
!(res = h2->handler::multi_range_read_next(&range_info))){
//下压的index条件
if (h2->mrr_funcs.skip_index_tuple &&
h2->mrr_funcs.skip_index_tuple(h2->mrr_iter, curr_range->ptr))
continue;
memcpy(rowids_buf_cur, h2->ref, h2->ref_length);
}
varlen_sort(
rowids_buf, rowids_buf_cur, elem_size,
[this](const uchar *a, const uchar *b) { return h->cmp_ref(a, b) < 0; });
} dsmrr_next(){
do{
if (rowids_buf_cur == rowids_buf_last) {
dsmrr_fill_buffer();
}
// first match
if (h2->mrr_funcs.skip_record &&
h2->mrr_funcs.skip_record(h2->mrr_iter, (char *)cur_range_info, rowid))
continue;
res = h->ha_rnd_pos(table->record[0], rowid);
break;
} while(true);
} JOIN_CACHE_BKA::join_matching_records(){
while (!(error = file->ha_multi_range_read_next((char **)&rec_ptr))) {
get_record_by_pos(rec_ptr);
rc = generate_full_extensions(rec_ptr);
if (rc != NESTED_LOOP_OK) return rc;
}
}
mysql> show create table t1;
+-------+-------------------------------------------------------------------------------------------------------------------------------------------------+
| Table | Create Table |
+-------+-------------------------------------------------------------------------------------------------------------------------------------------------+
| t1 | CREATE TABLE `t1` (
`f1` int(11) DEFAULT NULL,
`f2` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci |
+-------+-------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec) mysql> show create table t2;
+-------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table | Create Table |
+-------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| t2 | CREATE TABLE `t2` (
`f1` int(11) NOT NULL,
`f2` int(11) NOT NULL,
`f3` char(200) DEFAULT NULL,
KEY `f1` (`f1`,`f2`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci |
+-------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec) mysql> explain SELECT /*+ BKA() */ t2.f1, t2.f2, t2.f3 FROM t1,t2 WHERE t1.f1=t2.f1 AND t2.f2 BETWEEN t1.f1 and t1.f2 and t2.f2 + 1 >= t1.f1 + 1;
+----+-------------+-------+------------+------+---------------+------+---------+-------------+------+----------+---------------------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+-------------+------+----------+---------------------------------------------------------------+
| 1 | SIMPLE | t1 | NULL | ALL | NULL | NULL | NULL | NULL | 3 | 100.00 | Using where |
| 1 | SIMPLE | t2 | NULL | ref | f1 | f1 | 4 | test1.t1.f1 | 7 | 11.11 | Using index condition; Using join buffer (Batched Key Access) |
+----+-------------+-------+------------+------+---------------+------+---------+-------------+------+----------+---------------------------------------------------------------+
2 rows in set, 1 warning (0.00 sec)

c) Batched Key Access(unique)

与Batched Key Access不同的是,r中的列是s的唯一索引,在r记录写入join cache的时候,会记录一个key的hash table,仅针对不同的key去s表中查询。(疑问,为什么只有unique的时候才能用这种方式?不是unique的话,s表中可能会扫描出多条数据,也可以用这种方式去处理,减少s表的重复扫描)。
JOIN_CACHE_BKA_UNIQUE::join_matching_records(){
while (!(error = file->ha_multi_range_read_next((char **)&key_chain_ptr))) {
do(each record in chain){
get_record_by_pos(rec_ptr);
rc = generate_full_extensions(rec_ptr);
if (rc != NESTED_LOOP_OK) return rc;
}
}
}
与Batched Key Access不同的是,r中的列是s的唯一索引,在r记录写入join cache的时候,会记录一个key的hash table,仅针对不同的key去s表中查询。(疑问,为什么只有unique的时候才能用这种方式?不是unique的话,s表中可能会扫描出多条数据,也可以用这种方式去处理,减少s表的重复扫描)。
JOIN_CACHE_BKA_UNIQUE::join_matching_records(){
while (!(error = file->ha_multi_range_read_next((char **)&key_chain_ptr))) {
do(each record in chain){
get_record_by_pos(rec_ptr);
rc = generate_full_extensions(rec_ptr);
if (rc != NESTED_LOOP_OK) return rc;
}
}
}

3. Join执行流程(老执行器)

sub_select <--------------------------------------------+
| -> iterator::read() // 读一行数据 |
| -> evaluate_join_record() //检查这行数据是否符合条件 |
| -> next_select() ---+ |
| |
sub_select_op <--------+ |
| -> op->put_record() // 前表数据写入join cache |
| -> put_record_in_cache() |
| -> join->record() |
| -> join_matching_records() |
| -> (qep_tab->next_select)(join, qep_tab + 1, 0) // 继续调用next_select
| -> end_send()

点击关注,第一时间了解华为云新鲜技术~

数据库实践丨MySQL多表join分析的更多相关文章

  1. 数据库实践丨使用MTK迁移Mysql源库后主键自增列导致数据无法插入问题

    摘要:用户使用Mogdb 2.0.1版本进行业务上线测试,发现在插入数据时,应用日志中提示primary key冲突,用户自查业务SQL没有问题,接到通知后,招手处理故障. 本文分享自华为云社区< ...

  2. mysql分表场景分析与简单分表操作

    为什么要分表 首先要知道什么情况下,才需要分表个人觉得单表记录条数达到百万到千万级别时就要使用分表了,分表的目的就在于此,减小数据库的负担,缩短查询时间. 表分割有两种方式: 1水平分割:根据一列或多 ...

  3. MySQL数据库学习笔记----MySQL多表查询之外键、表连接、子查询、索引

    本章主要内容: 一.外键 二.表连接 三.子查询 四.索引 一.外键: 1.什么是外键 2.外键语法 3.外键的条件 4.添加外键 5.删除外键 1.什么是外键: 主键:是唯一标识一条记录,不能有重复 ...

  4. mysql 多表join

    两个表可以简单地写为 select a.,b. from a left join b on a.id =b.id; 三个以上 select a.,b. from a left join b on a. ...

  5. 【数据库_Mysql】MySQL—修改表时给表添加联合主键约束

      添加语法如下: “ALTER TABLE table_name ADD CONSTRAINT pk_table_name PRIMARY KEY(列名1,列名2):” [示例1]假设订房信息表(O ...

  6. mysql锁表机制分析

    http://blog.csdn.net/u010942020/article/details/51925653

  7. mysql merge表介绍

    在Mysql数据库中,Mysql Merge表有点类似于视图.下面就让我们来一起了解一下Mysql Merge表都有哪些优点,希望对您能有所帮助. Mysql Merge表的优点: A: 分离静态的和 ...

  8. [转]数据库中间件 MyCAT源码分析——跨库两表Join

    1. 概述 2. 主流程 3. ShareJoin 3.1 JoinParser 3.2 ShareJoin.processSQL(...) 3.3 BatchSQLJob 3.4 ShareDBJo ...

  9. [转]两表join的multi update语句在MySQL中的执行流程分析

    出自:http://hedengcheng.com/?p=209 两表join的multi update语句,执行结果与预计不一致的分析过程 — multi update结论在实际应用中,不要轻易使用 ...

  10. 重新学习MySQL数据库5:根据MySQL索引原理进行分析与优化

    重新学习MySQL数据库5:根据MySQL索引原理进行分析与优化 一:Mysql原理与慢查询 MySQL凭借着出色的性能.低廉的成本.丰富的资源,已经成为绝大多数互联网公司的首选关系型数据库.虽然性能 ...

随机推荐

  1. 算法解析:LeetCode——机器人碰撞和最低票价

    摘要:本文由葡萄城技术团队原创.转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者. 机器人碰撞 问题: 现有 n 个机器人,编号从 1 开始,每个机器人包含在路 ...

  2. windows系统和IE的兼容性问题

    IE浏览器用户正在逐步减少,但是以前的基数较大,为了解决win和ie的版本混乱问题,特记录一下:      一.从操作系统角度出发: XP最高支持到IE8,XP支持ie6,ie7,ie8 WIN7自带 ...

  3. 黄金眼PAAS化数据服务DIFF测试工具的建设实践

    一.背景介绍 黄金眼PAAS化数据服务是一系列实现相同指标服务协议的数据服务,各个服务间按照所生产指标的主题作划分,比如交易实时服务提供实时交易指标的查询,财务离线服务提供离线财务指标的查询.黄金眼P ...

  4. Pinely Round 2 (Div. 1 + Div. 2) (CF1863)

    本来开了某场远古 Div 1,然后学了一堆前置知识至今仍然不会 E.换一场写来得及吗? A. Channel 模拟,略. B. Split Sort Description 给你一个长度为 \(n\) ...

  5. QString类常用属性

    目录 1. isNull() 2. isEmpty() 3. length() 4. truncate() 5. indexOf()/lastIndexOf() 6. arg() 7. at() 8. ...

  6. Util应用框架基础(一) - 依赖注入

    本节介绍Util应用框架依赖注入的使用和配置扩展. 文章分为多个小节,如果对设计原理不感兴趣,只需阅读基础用法部分即可. 概述 当你想调用某个服务的方法完成特定功能时,首先需要得到这个服务的实例. 最 ...

  7. 批处理(Batch或离线计算)和流计算(Streaming或实时计算)

    大数据处理流程 课程:https://developer.aliyun.com/learning/course/432/detail/5385 流程 发 批处理(Batch或离线计算) 基础:goog ...

  8. JVM SandBox 的技术原理与应用分析

    https://www.infoq.cn/article/tsy4lgjvsfweuxebw*gp https://blog.csdn.net/qq_40378034/article/details/ ...

  9. 【Android】打卡app 今日完成情况记录

    已完成内容:页面的设计.数据库的建立 今日目标:实现数据库的增删改查,并能够在页面实现输出 逻辑: 1.用户进入注册页面,进行注册,注册成功后,可以成功登录(这一阶段就完成了) 2.登录后,显示用户的 ...

  10. 快速认识什么是:Docker

    Docker,一种可以将软件打包到容器中并在任何环境中可靠运行的工具.但什么是容器以及为什么需要容器呢?今天就来一起学快速入门一下Docker吧!希望本文对您有所帮助. 假设您使用 Cobol 构建了 ...