关于mysql的join

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群版 2核4GB 100GB
推荐场景:
搭建个人博客
云数据库 RDS MySQL,高可用版 2核4GB 50GB
简介: 关于mysql的join

在qq群,经常听到 "最好不要用join","join用了网站会很卡"类似与这样的言论,那么事实上是这样吗?

本来本人是想用理论来说服大家的,但是可能有些人不信理论,只信某些"大神"的凭空言论,所以本人喜欢拿事实说话,再加上理论进行说明

测试环境:

3张表:

create table if not exists article\_category\_list
(
 categoryId int unsigned auto_increment comment '分类id'
  primary key,
 categoryName varchar(64) not null comment '分类名称',
 pid int unsigned not null comment '父级分类id',
 sort smallint(6) null,
 state tinyint null,
 addTime int null,
 note varchar(255) null
)
charset=utf8mb4;
create table if not exists user_list
(
 userId int unsigned auto_increment
  primary key,
 userName varchar(30) default '默认用户' not null comment '用户名',
 phone varchar(11) not null comment '手机',
 sex tinyint unsigned default 0 not null comment '性别 0 未知  1 男  2女',
 userPassword varchar(255) not null comment '密码',
 userSession varchar(32) null comment '会话信息',
 lastLoginIp varchar(20) null comment '最后一次登录ip',
 lastLoginTime int unsigned null comment '最后一次登录时间',
 addTime int(10) not null comment '用户添加时间',
 constraint uq_phone
  unique (phone)
)
comment '用户' charset=utf8mb4;
create table if not exists article_list
(
 articleId int unsigned auto_increment comment '文章id'
  primary key,
 categoryId int not null comment '分类id',
 categoryName varchar(64) not null comment '分类名称',
 userId int null,
 title varchar(64) not null comment '标题',
 imgUrl varchar(255) not null,
 description varchar(255) null comment '简介',
 author varchar(32) not null comment '作者',
 content text null comment '内容',
 addTime int unsigned not null comment '新增时间',
 updateTime int unsigned null comment '更新时间',
 state tinyint(2) unsigned not null comment '状态 1正常,2隐藏',
 note varchar(255) null
)
charset=utf8mb4;
create index userId
 on article_list (userId);
create index categoryId
 on article_list (categoryId);

mysql为 5.7环境

数据量为: article_category_list:100,user_list:80万,article_list:90万,

本文使用 easyswoole orm组件进行测试.

一对一大数据测试:

通过随机获取10.0个文章数据,同时join获取用户id:

<?php
/**
 * Created by PhpStorm.
 * User: tioncico
 * Date: 20-8-13
 * Time: 下午10:46
 */
include "./vendor/autoload.php";
\\EasySwoole\\EasySwoole\\Core::getInstance()->initialize()->globalInitialize();
go(function () {
    $startTime = microtime_float();
    $count = 0;
    for ($i = 0; $i < 500; $i++) {
        $sql = "select * from article\_list as a inner join user\_list as b on a.userId=b.userId limit 1000";
        $query = new \\EasySwoole\\Mysqli\\QueryBuilder();
        $query->raw($sql);
        $data = \\EasySwoole\\ORM\\DbManager::getInstance()->getConnection()->defer()->query($query);
        $count += count($data->getResult());
    }
    $endTime = microtime_float();
    echo "join查询数据量:{$count}\\n";
    echo $endTime - $startTime . "秒" . PHP_EOL;
    \\EasySwoole\\Component\\Timer::getInstance()->clearAll();
});
/*
*
*返回当前 Unix 时间戳和微秒数(用秒的小数表示)浮点数表示,常用来计算代码段执行时间
*/
function microtime_float()
{
    list($usec, $sec) = explode(" ", microtime());
    return ((float)$usec + (float)$sec);
}

同样的逻辑,改为分次查询:

<?php
/**
 * Created by PhpStorm.
 * User: tioncico
 * Date: 20-8-13
 * Time: 下午10:46
 */
include "./vendor/autoload.php";
\\EasySwoole\\EasySwoole\\Core::getInstance()->initialize()->globalInitialize();
go(function () {
    $startTime = microtime_float();
    $count = 0;
    for ($i = 0; $i < 500; $i++) {
        $sql = "select * from article_list limit 1000";
        $query = new \\EasySwoole\\Mysqli\\QueryBuilder();
        $query->raw($sql);
        $data = \\EasySwoole\\ORM\\DbManager::getInstance()->getConnection()->defer()->query($query);
        $count += count($data->getResult());
        $articleData = $data->getResult();
        $userIds = \[\];
        foreach ($articleData as $article) {
            if (empty($article\['userId'\])) {
                continue;
            }
            $userIds\[\] = "'{$article\['userId'\]}'";
        }
        $sql = "select * from user_list where \`userId\` in (" . implode(',', $userIds) . ')';
        $query = new \\EasySwoole\\Mysqli\\QueryBuilder();
        $query->raw($sql);
        \\EasySwoole\\ORM\\DbManager::getInstance()->getConnection()->defer()->query($query);
        //如果需要获取join一样的数据,那就需要对数据进行二次处理,以下代码先注释
//        $userData = $data->getResult();
//        $userList = \[\];
//        foreach ($userData as $user) {
//            $userList\[$user\['userId'\]\] = $user;
//        }
//        foreach ($articleData as $key=>$article){
//            if (!isset($userList\[$article\['userId'\]\])){
//                continue;
//            }
//            $articleData\[$key\] = array_merge($article,$userList\[$article\['userId'\]\]);
//            $articleData\[$key\]\['userInfo'\] = $userList\[$article\['userId'\]\];//这是第二种写法
//        }
    }
    $endTime = microtime_float();
    echo "分开查询数据量:{$count}\\n";
    echo $endTime - $startTime . "秒" . PHP_EOL;
    \\EasySwoole\\Component\\Timer::getInstance()->clearAll();
});
/*
*
*返回当前 Unix 时间戳和微秒数(用秒的小数表示)浮点数表示,常用来计算代码段执行时间
*/
function microtime_float()
{
    list($usec, $sec) = explode(" ", microtime());
    return ((float)$usec + (float)$sec);
}

测试结果如下:

\[root@tioncico-server homeTest\]# php test.php 
join查询数据量:500000
6.7306478023529秒
\[root@tioncico-server homeTest\]# php test.php 
join查询数据量:500000
6.0196189880371秒
\[root@tioncico-server homeTest\]# php test.php 
join查询数据量:500000
6.0013608932495秒
\[root@tioncico-server homeTest\]# php test.php 
join查询数据量:500000
5.9181699752808秒
\[root@tioncico-server homeTest\]# php test8.php 
分开查询数据量:500000
4.5411529541016秒
\[root@tioncico-server homeTest\]# php test8.php 
分开查询数据量:500000
4.5213208198547秒
\[root@tioncico-server homeTest\]# php test8.php 
分开查询数据量:500000
5.7194919586182秒
\[root@tioncico-server homeTest\]# php test8.php 
分开查询数据量:500000
4.231143951416秒
\[root@tioncico-server homeTest\]# php test8.php 
分开查询数据量:500000
5.1239991188049秒
\[root@tioncico-server homeTest\]# php test8.php 
分开查询,组装join的数据,数据量:500000
6.9564120769501秒
\[root@tioncico-server homeTest\]# php test8.php 
分开查询,组装join的数据,数据量:500000
6.7047250270844秒
\[root@tioncico-server homeTest\]# php test8.php 
分开查询,组装join的数据,数据量:500000
6.7332708835602秒
\[root@tioncico-server homeTest\]# php test8.php 
分开查询,组装join的数据,数据量:500000
6.772262096405秒
\[root@tioncico-server homeTest\]# php test8.php 
分开查询,组装join的数据,数据量:500000
6.7405600547791秒
\[root@tioncico-server homeTest\]#

可以看出,当获取1000条数据时,join查询大致为6秒左右,分开查询为4.5秒左右,比join快了25%,但是由于分开查询的数据并不是可以直接使用的数据,还需要做数据拼接,这个时候消耗到了6.7秒,比join查询还更慢了0.7秒

一对多小数据测试

为了测试的严谨性,我们每次查出10个user,并且直接join获取所有发布的文章数据.

也就是说,10个user,每个关联n条文章数据.

join方式

<?php
/**
 * Created by PhpStorm.
 * User: tioncico
 * Date: 20-8-13
 * Time: 下午10:46
 */
include "./vendor/autoload.php";
\\EasySwoole\\EasySwoole\\Core::getInstance()->initialize()->globalInitialize();
go(function () {
    $startTime = microtime_float();
    $count = 0;
    for ($i = 0; $i < 5000; $i++) {
        $userIds = \[\];
        for ($y=0;$y<10;$y++){
            $userIds\[\] = mt_rand(1,800000);
        }
        $sql = "select * from user\_list as a inner join article\_list as b on a.userId=b.userId where a.userId in ( ".implode(',',$userIds)." )";
        $query = new \\EasySwoole\\Mysqli\\QueryBuilder();
        $query->raw($sql);
        $data = \\EasySwoole\\ORM\\DbManager::getInstance()->getConnection()->defer()->query($query);
        $count += count($data->getResult());
    }
    $endTime = microtime_float();
    echo "join查询数据量:{$count}\\n";
    echo $endTime - $startTime . "秒" . PHP_EOL;
    \\EasySwoole\\Component\\Timer::getInstance()->clearAll();
});
/*
*
*返回当前 Unix 时间戳和微秒数(用秒的小数表示)浮点数表示,常用来计算代码段执行时间
*/
function microtime_float()
{
    list($usec, $sec) = explode(" ", microtime());
    return ((float)$usec + (float)$sec);
}

分开查询方式:

<?php
/**
 * Created by PhpStorm.
 * User: tioncico
 * Date: 20-8-13
 * Time: 下午10:46
 */
include "./vendor/autoload.php";
\\EasySwoole\\EasySwoole\\Core::getInstance()->initialize()->globalInitialize();
go(function () {
    $startTime = microtime_float();
    $count = 0;
    for ($i = 0; $i < 5000; $i++) {
        $userIds = \[\];
        for ($y=0;$y<10;$y++){
            $userIds\[\] = mt_rand(1,800000);
        }
        //先查询user
        $sql = "select * from user_list where userId in ( ".implode(',',$userIds)." )";
        $query = new \\EasySwoole\\Mysqli\\QueryBuilder();
        $query->raw($sql);
        $data = \\EasySwoole\\ORM\\DbManager::getInstance()->getConnection()->defer()->query($query);
        //再查询article_list
        $sql = "select * from  article_list where userId in ( ".implode(',',$userIds)." )";
        $query = new \\EasySwoole\\Mysqli\\QueryBuilder();
        $query->raw($sql);
        $data = \\EasySwoole\\ORM\\DbManager::getInstance()->getConnection()->defer()->query($query);
        $count += count($data->getResult());
    }
    $endTime = microtime_float();
    echo "join查询数据量:{$count}\\n";
    echo $endTime - $startTime . "秒" . PHP_EOL;
    \\EasySwoole\\Component\\Timer::getInstance()->clearAll();
});
/*
*
*返回当前 Unix 时间戳和微秒数(用秒的小数表示)浮点数表示,常用来计算代码段执行时间
*/
function microtime_float()
{
    list($usec, $sec) = explode(" ", microtime());
    return ((float)$usec + (float)$sec);
}

测试结果:

\[tioncico@tioncico-server homeTest\]$ php test10.php 
join查询数据量:4469
4.4179630279541秒
\[tioncico@tioncico-server homeTest\]$ php test10.php 
join查询数据量:4467
2.8955171108246秒
\[tioncico@tioncico-server homeTest\]$ php test10.php 
join查询数据量:4506
2.9106030464172秒
\[tioncico@tioncico-server homeTest\]$ php test10.php 
join查询数据量:4443
2.7280490398407秒
\[tioncico@tioncico-server homeTest\]$ php test10.php 
join查询数据量:4386
2.7762699127197秒
\[tioncico@tioncico-server homeTest\]$ php test10.php 
join查询数据量:4430
2.8715240955353秒
\[tioncico@tioncico-server homeTest\]$ php test11.php 
分开查询数据量:4537
4.8987159729004秒
\[tioncico@tioncico-server homeTest\]$ php test11.php 
分开查询数据量:4586
4.4783749580383秒
\[tioncico@tioncico-server homeTest\]$ php test11.php 
分开查询数据量:4547
5.2708139419556秒
\[tioncico@tioncico-server homeTest\]$ php test11.php 
分开查询数据量:4583
4.5162749290466秒
\[tioncico@tioncico-server homeTest\]$ php test11.php 
分开查询数据量:4477
7.7116341590881秒

可以看出,join查询时,时间为2.7-4秒之间徘徊,而分开查询时,却变成了4-7秒内徘徊,这个数据还是没有做拼接数据的情况

1对1小数据测试

我们进行第三个案例测试,每次获取10条文章数据,并且关联文章分类表:

<?php
/**
 * Created by PhpStorm.
 * User: tioncico
 * Date: 20-8-13
 * Time: 下午10:46
 */
include "./vendor/autoload.php";
\\EasySwoole\\EasySwoole\\Core::getInstance()->initialize()->globalInitialize();
go(function () {
    $startTime = microtime_float();
    $count = 0;
    for ($i = 0; $i < 2000; $i++) {
        $ids = \[\];
        for ($y=0;$y<10;$y++){
            $ids\[\] = mt_rand(1,800000);
        }
        $sql = "select * from article\_list as a inner join article\_category_list as b on a.categoryId=b.categoryId where a.categoryId in ( ".implode(',',$ids)." )";
        $query = new \\EasySwoole\\Mysqli\\QueryBuilder();
        $query->raw($sql);
        $data = \\EasySwoole\\ORM\\DbManager::getInstance()->getConnection()->defer()->query($query);
        $count += count($data->getResult());
    }
    $endTime = microtime_float();
    echo "join查询数据量:{$count}\\n";
    echo $endTime - $startTime . "秒" . PHP_EOL;
    \\EasySwoole\\Component\\Timer::getInstance()->clearAll();
});
/*
*
*返回当前 Unix 时间戳和微秒数(用秒的小数表示)浮点数表示,常用来计算代码段执行时间
*/
function microtime_float()
{
    list($usec, $sec) = explode(" ", microtime());
    return ((float)$usec + (float)$sec);
}

分开查询代码:

<?php
/**
 * Created by PhpStorm.
 * User: tioncico
 * Date: 20-8-13
 * Time: 下午10:46
 */
include "./vendor/autoload.php";
\\EasySwoole\\EasySwoole\\Core::getInstance()->initialize()->globalInitialize();
go(function () {
    $startTime = microtime_float();
    $count = 0;
    //我直接在这查询出所有的categoryId
    $sql = "select * from  article\_category\_list";
    $query = new \\EasySwoole\\Mysqli\\QueryBuilder();
    $query->raw($sql);
    $data = \\EasySwoole\\ORM\\DbManager::getInstance()->getConnection()->defer()->query($query);
    $categoryList = \[\];
    foreach ($data->getResult() as $datum){
        $categoryList\[$datum\['categoryId'\]\] = $datum;
    }
    for ($i = 0; $i < 2000; $i++) {
        $ids = \[\];
        for ($y=0;$y<10;$y++){
            $ids\[\] = mt_rand(1,800000);
        }
        //查询article_list
        $sql = "select * from  article_list where articleId in ( ".implode(',',$ids)." )";
        $query = new \\EasySwoole\\Mysqli\\QueryBuilder();
        $query->raw($sql);
        $data = \\EasySwoole\\ORM\\DbManager::getInstance()->getConnection()->defer()->query($query);
        $count += count($data->getResult());
    }
    $endTime = microtime_float();
    echo "分开查询数据量:{$count}\\n";
    echo $endTime - $startTime . "秒" . PHP_EOL;
    \\EasySwoole\\Component\\Timer::getInstance()->clearAll();
});
/*
*
*返回当前 Unix 时间戳和微秒数(用秒的小数表示)浮点数表示,常用来计算代码段执行时间
*/
function microtime_float()
{
    list($usec, $sec) = explode(" ", microtime());
    return ((float)$usec + (float)$sec);
}

测试结果:

\[tioncico@tioncico-server homeTest\]$ php test12.php 
join查询数据量:36182
4.1274499893188秒
\[tioncico@tioncico-server homeTest\]$ php test12.php 
join查询数据量:9023
1.3489010334015秒
\[tioncico@tioncico-server homeTest\]$ php test12.php 
join查询数据量:18254
1.5686419010162秒
\[tioncico@tioncico-server homeTest\]$ php test12.php 
join查询数据量:9109
1.3906681537628秒
\[tioncico@tioncico-server homeTest\]$ php test13.php 
分开查询数据量:20000
1.3932311534882秒
\[tioncico@tioncico-server homeTest\]$ php test13.php 
分开查询数据量:19999
3.3708009719849秒
\[tioncico@tioncico-server homeTest\]$ php test13.php 
分开查询数据量:20000
1.4796049594879秒
\[tioncico@tioncico-server homeTest\]$ php test13.php 
分开查询数据量:19998
1.6495499610901秒
\[tioncico@tioncico-server homeTest\]$ php test13.php 
分开查询数据量:20000
1.4959900379181秒
\[tioncico@tioncico-server homeTest\]$ php test13.php 
分开查询数据量:19998
1.5449161529541秒

可以看出,使用join查询,跟分开查询(这里的分开查询做了优化,因为只有100条消息,所以只需要一次性查出100条即可)

join查询在1.4-4秒之间,而分开查询也在1.5-3.4秒之间,也没有更快

原理解析:

在mysql 查询关联中,有以下几种关联形式:

1:1对1关联,每次查询数据关联数据都只有一条数据

2:1对多关联,每次查询数据关联数据为1条以上

同时,根据关联主次关系,以及数据量的大小,可以区分各种情况:

1:A,B 1对1关联,B数据总量为100,A数据总量90万 ,每次查少量数据/每次查大量数据

2:A,B 1对1关联,B数据总量80万,A数据总量90万 ,每次查少量数据/每次查大量数据

3:A,B 1对多关联,B数据总量90万,A数据总量80万,每次查少量数据/每次查大量数据

在关联需求中,影响速度的因素为:

1:查询数据量,数据量越大,传输带宽越大,则越慢,本文使用的是内网环境,可极大避免此问题

2:查询索引,命中索引并使用索引之后速度则快,否则表数据越大越慢,本文所有查询条件均使用了索引,所以可以忽略索引问题

3:查询次数,每次查询,意味着mysql都需要进行一次sql命令解析->sql查询->数据传回,查询次数越少则越快

4:数据组装,当使用join,order by,group by等sql语句时,会使得mysql查询完数据之后还需要对数据进行拼装再返回,数据越大,逻辑越复杂,则查询越慢

既然已经有了3点因素,那我们来看第一个案例:

每次查询1000条数据,1对1关联, 主要是数据量大

关系到了 第1点,第4点的说明,由于分开查询节省了数据组装流程,所以加快了查询速度,所以比join方式查询更快

如果使用php进行数据组装,速度则跟join方案几乎一致

注:本身mysql原生查询,应该是游标式while循环获取,本文使用的foreach其实在原生查询中,可以省略好几个步骤,应该是分开查询更快

第二个案例:

每次查询10条,1对多关联

该案例

由于查询数据量并不大,

user_list表字段数据并不多(如果字段数多,也会影响),一条数据大头在article_list中

同时因为分开查询,将增加一次查询次数,

关系到了第3点的说明

所以导致分开查询比join查询更慢了差不多一倍.

第三个案例:

每次查询10条,1对1小数据关联

这里的分开查询优化了每次查询,小数据只查询了一次,但是速度依旧跟join差不多

总结

通过上面的说明,或许你已经对join以及分开查询的各种应用场景有所了解了,对于性能方面也有一定的掌握了,所以,说一下几个知识点:

1:join查询会消耗性能,但是消耗的是组装数据的性能(数据量越大,越复杂时越明显)

2:join查询速度与分开查询几乎一致.(只要有索引,就非常快)

3:分开查询会多执行一条sql,意味着查询速度将会更慢

4:可以通过预先缓存方式,节省掉join小数据的组装数据开销以及带宽开销

那为什么有大佬不建议使用join呢?

1:join如果逻辑太多,代码将非常难懂

2:join如果太多,对于这条sql 的索引优化将会变得更难

3:join的sql复杂,可读性差,同时由于sql复杂,很难被mysql缓存

4:分开查询的sql简单易懂,同时查询后将会有sql缓存,提高下次查询速度

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
10月前
|
SQL 算法 关系型数据库
深入理解MySQL中的Join算法
在数据库处理中,Join操作是最基本且最重要的操作之一,它能将不同的表连接起来,实现对数据集的更深层次分析。
530 8
深入理解MySQL中的Join算法
|
3月前
|
关系型数据库 MySQL
mysql join 实践
mysql join 实践
31 0
|
7天前
|
SQL 关系型数据库 MySQL
在 MySQL 中使用 Distinct
【8月更文挑战第12天】
22 4
在 MySQL 中使用 Distinct
|
2月前
|
算法 关系型数据库 MySQL
深入理解MySQL中的JOIN算法
深入理解MySQL中的JOIN算法
|
3月前
|
存储 算法 关系型数据库
MySQL的JOIN到底是怎么玩的
在MySQL中,查询操作通常会涉及到联结不同表格,而JOIN命令则在这一过程中扮演了关键角色。在JOIN操作中,我们通常会使用三种不同的方式,分别是内连接、左连接以及右连接。
|
9月前
|
关系型数据库 MySQL 索引
mysql join
mysql join
52 0
|
SQL 算法 关系型数据库
MySQL中的Join 的算法(NLJ、BNL、BKA)
MySQL中的Join 的算法(NLJ、BNL、BKA)
202 0
|
关系型数据库 MySQL
【MySQL】MySQL的7种join
在上一篇文章中,我们建了两张表:tbl_dept,tbl_emp,并向这两张表插入了一些数据,在这篇文章中,我们借用前面的两张表,研究MySQL的join。
130 0
|
SQL 存储 缓存
【MySQL】Join原理
窗口函数对一些常见的需求还是有很大帮助的,今天总结一下常见的用法。
86 0
【MySQL】Join原理
|
SQL 缓存 关系型数据库
关于mysql的join
关于mysql的join
91 0