当前位置: 首页 > news >正文

无意中测试了下MySQL里面的join操作,发现还是存在理解偏差

这是学习笔记的第 2179 篇文章

读完需要

9

分钟

速读仅需5分钟

在一个很偶然的场景下,我看到了一个关于数据库中间件的SQL测试,对比测试的内容大体是对于一条查询语句的输出。看到输出结果,虽然结果是客观的,但是我总是感觉缺少了些什么,于是做了下测试。

为了简化起见,我们把测试场景做到至简。创建两张表,就1个字段,4行记录,来说明下JOIN的一些问题和隐患。

但凡不是太懒的话,这个场景都可以很快实现的。

mysql> create table test1(id int);
mysql> create table test2(id int)
mysql> insert into test2 values(1),(2),(2),(3)
mysql> insert into test1 values (1),(2),(2),(3);

和我们预期的一样,这2张表的输出就是4行记录。 

mysql> select *from test1;
+------+
| id   |
+------+
|    1 |
|    2 |
|    2 |
|    3 |
+------+
4 rows in set (0.00 sec)

数据情况是完全一样的。

mysql> select *from test2;
+------+
| id   |
+------+
|    1 |
|    2 |
|    2 |
|    3 |
+------+
4 rows in set (0.00 sec)

对于下面的SQL,你猜猜分别会有几行输出结果。 

select * from test1 m,test2 n where m.id=n.id and n.id=2;
select m.id from test1 m,test2 n where m.id=n.id and n.id=2;

可以先思考几秒钟,再往下看。

 

2

   

输出结果如下:

mysql> select * from test1 m,test2 n where m.id=n.id and n.id=2;
+------+------+
| id   | id   |
+------+------+
|      2 |    2 |
|     2 |    2 |
|      2 |    2 |
|     2 |    2 |
+------+------+
4 rows in set (0.00 sec)

第2条SQL的输出如下:

mysql> select m.id from test1 m,test2 n where m.id=n.id and n.id=2; 
+------+
| id   |
+------+
|    2 |
|    2 |
|    2 |
|    2 |
+------+
4 rows in set (0.00 sec)

结果是不是很简单,当然我要表达的不是这一层含义,我想要说的是结果和我们的需求其实是存在一些偏差。

3

   

从我们的预期来看,输出既然是m(test1)的数据,那么m应该是作为驱动表,那么从我们的常规思路来看,应该是期望看到2条记录。

因为m(test1)表一共就4行记录,一共输出了4行,而且有2行还是完全一样的,对于需求来说实在是找不出有什么场景需要这样的预期结果。

    如果要实现这种需求,显然使用distinct,group by是不等价的。 

mysql> select  distinct m.id from test1 m join test2 n on  m.id=n.id and m.id=2 ;
+------+
| id   |
+------+
|    2 |
+------+
1 row in set (0.00 sec)

所以要实现这种需求,一种很自然的处理方式就浮出水面,那就是半连接。 

mysql> select   m.id from test1 m  where m.id in (select n.id from test2 n where m.id=n.id and n.id=2); 
+------+
| id   |
+------+
|    2 |
|    2 |
+------+
2 rows in set (0.00 sec)

还有一种是exists,在MySQL中其实是更偏爱exists的方式的。

mysql> select   m.id from test1 m  where exists (select 1 from test2 n where m.id=n.id and n.id=2);             
+------+
| id   |
+------+
|    2 |
|    2 |
+------+
2 rows in set (0.00 sec)

可以看到在这种场景下,从SQL要表达的含义层面才是符合我们的需求出发点的。 

我们来看看使用单纯的JOIN带来的一些副作用。 

4

   

第一个是过滤数据的偏差,按照distinct,group by的处理方式是始终做唯一性处理的,也就意味着这种场景下只有1行记录输出。

mysql> select distinct    m.id from test1 m  join test2 n on  m.id=n.id where  n.id=2;     
+------+
| id   |
+------+
|    2 |
+------+
1 row in set (0.00 sec)

第二个是带来的数据统计偏差

我们其实想看一下匹配的记录,预期是2行,但是输出了4行,如果数据量较大的情况下,这种查询导致的结果影响面就足够大。

mysql> select count(*) from test1 m  join test2 n on  m.id=n.id where  n.id=2;                
+----------+
| count(*) |
+----------+
|        4 |
+----------+
1 row in set (0.00 sec)

所以很多不好的查询习惯就开始了,比如:

mysql> select count(*) from (select m.id from test1 m  join test2 n on  m.id=n.id where  n.id=2) t;
+----------+
| count(*) |
+----------+
|        4 |
+----------+
1 row in set (0.00 sec)

而这种逻辑方式就很容易适配了。

mysql> select   count(*) from test1 m  where exists (select 1 from test2 n where m.id=n.id and n.id=2);      
+----------+
| count(*) |
+----------+
|        2 |
+----------+
1 row in set (0.00 sec)

第三点影响最大,也是我们最容易忽略的。那就是去重过滤带来的副作用。 

我们知道输出结果是4行,但是我们预期的是2行,所以如果处理得当,我们需要过滤的数据比例就是50%,而如果匹配记录数是3,则过滤的数据比例是1-3/3^2将近70%,所以一个很基本的公式 1- N/N^2=1-1/N,过滤比例是很高的,如果匹配的记录数是100,那么常规的SQL处理要过滤的就是99.99%的数据。这个过滤比例实在是太高了。 

或者换一个问法,如何在1万条记录中如何有效的过滤掉99%以上的数据,可想而知这个复杂度和资源消耗。 

第四点,如果是在分布式场景中,那么这个影响的面会被最大化,复杂度和消耗可能是和节点数成正比的。 

5

   

到了这里,会发现我需求出发点的JOIN竟然会变得如此复杂。而换个角度来看,其实就容易理解在我们优化中经常看到的一些distinct和一些看起来蹩脚的组合查询了。 

换句话来说,我们的需求是什么,从需求相关的驱动表来入手,可能也是我们需要明白的一个优化点。

QQ群号:763628645

QQ群二维码如下, 添加请注明:姓名+地区+职位,否则不予通过

订阅我的微信公众号“杨建荣的学习笔记”,第一时间免费收到文章更新。别忘了加星标,以免错过新推送提示。

1

   

近期热文

你可能也会对以下话题感兴趣。点击链接就可以查看。

  • MySQL的主键命名挺任性,就这么定了

  • 华裔教授发现二次方程极简解法,我默默的做了下验算

  • 回答:我不小心把公司的数据库给删了,该不该离职?

  • 迁移到MySQL的业务架构演进实战

  • 数据库修改密码风险高,如何保证业务持续,这几种密码双活方案可以参考

  • MySQL业务双活的初步设计方案

  • 如何优化MySQL千万级大表,我写了6000字的解读

  • 一道经典的MySQL面试题,答案出现三次反转

  • 业务双活的数据切换思路设计(下)

  • 业务双活的数据切换思路设计(一)

  • MySQL中的主键和rowid,看似简单,其实有一些使用陷阱需要注意

  • 小白学MySQL要多久?我整理了10多个问题的答案

2

   

转载热文

你可能也会对以下话题感兴趣,文章来源于转载,点击链接就可以查看。

  • 去IOE or Not?

  • 拉里·佩奇(Larry Page)的伟大归来

  • 《吊打面试官》系列-Redis基础

  • 唯一ID生成算法剖析,看看这篇就够了

  • 关于大数据运维能力的一些思考

  • DBA菜鸟的进化简史:不忘初心,记工作中踩过的三个坑

  • 美女主持直播,被突发意外打断!湾区网友却高喊: 我懂!超甜

相关文章:

  • 说几点关于数据库的见解
  • Oracle和MySQL的数据导入,差别为什么这么大
  • 使用Python分析北京积分落户数据,分析完我陷入了深思
  • 私有云MySQL多租户权限的初版设计
  • 关于中间件服务的配置管理,分为5个阶段
  • 一个诡异的MySQL慢日志问题
  • 关于MySQL中insert ignore,insert on duplicate和replace into,你可能没想过区别
  • 你可能不了解的《唐诗三百首》
  • 趣头条基于ClickHouse玩转每天1000亿数据量
  • 疏通厨房水槽的感悟
  • 《生活中的魔法数学》读后感
  • 《唐诗三百首》中的童年记录
  • 关于远程办公的一些思考
  • 从Oracle新特性看数据库发展
  • 用Python对2019年二手房价格进行数据分析
  • Angular 4.x 动态创建组件
  • docker容器内的网络抓包
  • ECMAScript 6 学习之路 ( 四 ) String 字符串扩展
  • egg(89)--egg之redis的发布和订阅
  • ES学习笔记(10)--ES6中的函数和数组补漏
  • java小心机(3)| 浅析finalize()
  • Just for fun——迅速写完快速排序
  • PV统计优化设计
  • Python连接Oracle
  • redis学习笔记(三):列表、集合、有序集合
  • REST架构的思考
  • windows-nginx-https-本地配置
  • 从伪并行的 Python 多线程说起
  • 等保2.0 | 几维安全发布等保检测、等保加固专版 加速企业等保合规
  • 多线程 start 和 run 方法到底有什么区别?
  • 给第三方使用接口的 URL 签名实现
  • 聊聊directory traversal attack
  • 实现菜单下拉伸展折叠效果demo
  • 微信小程序:实现悬浮返回和分享按钮
  • 一道闭包题引发的思考
  • 移动端 h5开发相关内容总结(三)
  • ###51单片机学习(1)-----单片机烧录软件的使用,以及如何建立一个工程项目
  • #100天计划# 2013年9月29日
  • #LLM入门|Prompt#3.3_存储_Memory
  • #NOIP 2014#Day.2 T3 解方程
  • #每日一题合集#牛客JZ23-JZ33
  • (17)Hive ——MR任务的map与reduce个数由什么决定?
  • (20050108)又读《平凡的世界》
  • ./indexer: error while loading shared libraries: libmysqlclient.so.18: cannot open shared object fil
  • .describe() python_Python-Win32com-Excel
  • .MSSQLSERVER 导入导出 命令集--堪称经典,值得借鉴!
  • .NET CORE 2.0发布后没有 VIEWS视图页面文件
  • .net core 调用c dll_用C++生成一个简单的DLL文件VS2008
  • .NET delegate 委托 、 Event 事件
  • .NET Standard、.NET Framework 、.NET Core三者的关系与区别?
  • .NET 依赖注入和配置系统
  • .NET/C# 解压 Zip 文件时出现异常:System.IO.InvalidDataException: 找不到中央目录结尾记录。
  • .NET/C# 使用 #if 和 Conditional 特性来按条件编译代码的不同原理和适用场景
  • .pings勒索病毒的威胁:如何应对.pings勒索病毒的突袭?
  • .sdf和.msp文件读取