烧死10亿脑细胞的SQL长啥样?
1 前言
今天在生产中碰到了一个让我十分费解的 SQL,十分有趣。
2 现象
SQL 很好复现,就是逻辑看起来有点唬人
postgres=# create table test(id1 int,id2 int);
CREATE TABLE
postgres=# insert into test values(1,3),(2,1),(3,1),(3,3);
INSERT 0 4
postgres=# select * from test;
id1 | id2
-----+-----
1 | 3
2 | 1
3 | 1
3 | 3
(4 rows)
业务 SQL 如下 此处用 test 表替代,真实情况表中字段存在一个父子关系,根据 parent_id 查找子 id
postgres=# select (exists (select 1 as one from test a where (test.id1 = a.id2))) as b from test;
b
---
t
f
t
t
(4 rows)
postgres=# explain select (exists (select 1 as one from test a where (test.id1 = a.id2))) as b from test;
QUERY PLAN
--------------------------------------------------------------
Seq Scan on test (cost=0.00..3.14 rows=4 width=1)
SubPlan 2
-> Seq Scan on test a (cost=0.00..1.04 rows=4 width=4)
(3 rows)
SQL 是 self-join ,a 是 test 表的一个别名。
让我们把子查询单独摘出来执行一下
postgres=# select 1 as one from test a where (test.id1 = a.id2);
ERROR: invalid reference to FROM-clause entry for table "test"
LINE 1: select 1 as one from test a where (test.id1 = a.id2);
^
HINT: Perhaps you meant to reference the table alias "a".
可以看到报错了,说明此处的 test 是取自外层的 test(即 from test),根据 test.id1 去判断 a.id2,于是返回如下结果
postgres=# select * from test;
id1 | id2
-----+-----
1 | 3 ---true (id1=1,id2里面有,遍历)
2 | 1 ---false(id1=2,id2里面没有,遍历)
3 | 1 ---true (id1=3,id2里面有,遍历)
3 | 3 ---true (id1=3,id2里面有,遍历)
(4 rows)
现在让我们改写一下 SQL,修改一下别名
postgres=# select (exists (select 1 as one from test a where (a.id1 = test.id2))) as b from test;
b
---
t
t
t
t
(4 rows)
postgres=# explain select (exists (select 1 as one from test a where (a.id1 = test.id2))) as b from test;
QUERY PLAN
--------------------------------------------------------------
Seq Scan on test (cost=0.00..5.24 rows=4 width=1)
SubPlan 2
-> Seq Scan on test a (cost=0.00..1.04 rows=4 width=4)
(3 rows)
这次可以看到,结果全部是真。老样子,也是相同的原理
postgres=# select 1 as one from test a where (a.id1 = test.id2);
ERROR: invalid reference to FROM-clause entry for table "test"
LINE 1: select 1 as one from test a where (a.id1 = test.id2);
^
HINT: Perhaps you meant to reference the table alias "a".
于是根据 test.id2 去探测 a.id1,于是返回如下结果
postgres=# select * from test;
id1 | id2
-----+-----
1 | 3 ---true (id2=3,id1里面有,遍历)
2 | 1 ---true (id2=1,id1里面有,遍历)
3 | 1 ---true (id2=1,id1里面有,遍历)
3 | 3 ---true (id2=3,id1里面有,遍历)
(4 rows)
让我们再改写一下 SQL
postgres=# select (exists (select 1 as one from test a where (a.id1 = a.id2))) as b from test;
b
---
t
t
t
t
(4 rows)
postgres=# explain select (exists (select 1 as one from test a where (a.id1 = a.id2))) as b from test;
QUERY PLAN
--------------------------------------------------------------
Seq Scan on test (cost=1.05..2.09 rows=4 width=1)
InitPlan 1 (returns $0)
-> Seq Scan on test a (cost=0.00..1.05 rows=1 width=0)
Filter: (id1 = id2)
(4 rows)
这次执行计划变了,变成了 InitPlan,执行计划和结构都有所差异。那么 InitPlan 是什么意思?
This plan happens whenever there is a part of your query that can (or have to) be calculated before anything else, and it doesn't depend on anything in the rest of your query.
只要查询的一部分可以(或必须)在其他任何内容之前计算,并且它不依赖于查询的其余部分中的任何内容,就会发生此计划。
A special case of SubPlan that only needs to run once.
SubPlan 的一种特殊情况,只需要运行一次。
这就有点像相关子连接和非相关子连接的说法,相关子连接在子查询语句中引用了外层表的列属性,这就导致外层表每获得一个元组,子查询就需要重新执行一次;而非相关子连接是指在子查询语句是独立的,和外层的表没有直接的关联,子查询可以单独执行一次,外层表可以重复利用子查询的执行结果。
因此上述执行计划就变成了 a 表先进行一次独立的子查询
postgres=# select * from test where id1 = id2;
id1 | id2
-----+-----
3 | 3
(1 row)
postgres=# select exists (select 3,3) as b from test;
b
---
t
t
t
t
(4 rows)
postgres=# delete from test;
DELETE 4
postgres=# insert into test values(5,4);
INSERT 0 1
postgres=# select (exists (select 1 as one from test a where (a.id1 = a.id2))) as b from test;
b
---
f
(1 row)
postgres=# insert into test values(3,4);
INSERT 0 1
postgres=# select (exists (select 1 as one from test a where (a.id1 = a.id2))) as b from test;
b
---
f
f
(2 rows)
postgres=# insert into test values(4,4);
INSERT 0 1
postgres=# select (exists (select 1 as one from test a where (a.id1 = a.id2))) as b from test;
b
---
t
t
t
(3 rows)
可以看到,只要结果中有相等的 id1 和 id2,结果就会全部返回真。
那让我们又双叒叕改写下 SQL
postgres=# truncate table test;
TRUNCATE TABLE
postgres=# insert into test values(1,3),(2,1),(3,1),(3,3);
INSERT 0 4
postgres=# explain select (exists (select 1 as one from test a where (test.id1 = test.id2))) as b from test;
QUERY PLAN
--------------------------------------------------------------------
Seq Scan on test (cost=0.00..2.09 rows=4 width=1)
SubPlan 1
-> Result (cost=0.00..1.04 rows=4 width=0)
One-Time Filter: (test.id1 = test.id2)
-> Seq Scan on test a (cost=0.00..1.04 rows=4 width=0)
(5 rows)
postgres=# select (exists (select 1 as one from test a where (test.id1 = test.id2))) as b from test;
b
---
f
f
f
t
(4 rows)
这次多了一个 One-Time Filter,那么这个又是什么玩意?
A qualification used by a Result operation. If it is false, an empty result set can be returned without further work.
如果为 false,则可以返回空结果集,无需进一步工作。
让我们瞅瞅代码,在代码中有这么一段注释
* Result nodes are also used to optimise queries with constant
* qualifications (ie, quals that do not depend on the scanned data),
* such as:
*
* select * from emp where 2 > 1
*
* In this case, the plan generated is
*
* Result (with 2 > 1 qual)
* /
* SeqScan (emp.*)
*
* At runtime, the Result node evaluates the constant qual once,
* which is shown by EXPLAIN as a One-Time Filter. If it's
* false, we can return an empty result set without running the
* controlled plan at all. If it's true, we run the controlled
* plan normally and pass back the results.
逻辑很清晰,因此上述逻辑就好比这么一串 SQL
postgres=# select * from test where 2 > 1;
id1 | id2
-----+-----
1 | 3
2 | 1
3 | 1
3 | 3
(4 rows)
postgres=# select * from test where 1 > 1;
id1 | id2
-----+-----
(0 rows)
postgres=# select exists(select 1 from test where 1 > 1)as b;
b
---
f
(1 row)
postgres=# select exists(select 1 from test where 1 > 1)as b from test;
b
---
f
f
f
f
(4 rows)
postgres=# select (exists (select 1 as one from test a where (test.id1 = test.id2))) as b from test;
b
---
f
f
f
t
(4 rows)
因此此时的 SQL 逻辑就变成了这样:遍历 test 表,判断 id1 = id2 的行,所以结果是 false、false、false、true
3 小结
真是一段烧死脑细胞的神奇 SQL。不知道其他数据库中这个 SQL 是否是类似结果?感兴趣的读者可以私信我。当然文章中可能也有错误,欢迎指正 ~
烧死10亿脑细胞的SQL长啥样?的更多相关文章
- 使用HAProxy、PHP、Redis和MySQL支撑每周10亿请求
在公司的发展中,保证服务器的可扩展性对于扩大企业的市场需要具有重要作用,因此,这对架构师提出了一定的要求.Octivi联合创始人兼软件架构师Antoni Orfin将向你介绍一个非常简单的架构,使用H ...
- 转 使用HAProxy,PHPRedis,和MySQL支撑10亿请求每周架构细节
[编者按]在公司的发展中,保证服务器的可扩展性对于扩大企业的市场需要具有重要作用,因此,这对架构师提出了一定的要求.Octivi联合创始人兼软件架构师Antoni Orfin将向你介绍一个非常简单的架 ...
- “军装照”背后——天天P图如何应对10亿流量的后台承载。
WeTest 导读 天天P图"军装照"活动交出了一份10亿浏览量的答卷,一时间刷屏朋友圈,看到这幕,是不是特别想复制一个如此成功的H5?不过本文不教你如何做一个爆款H5,而是介绍天 ...
- 安装win10操作系统的设备将要突破10亿台
导读 该公司最初的目标是在发布后的三年内在 10 亿台设备上运行 Windows 10. 据微软高管梅赫迪 (Yusuf Mehdi) 周四在 Twitter 上透露,目前已经有 8 亿多台设备安装了 ...
- TOP100summit:【分享实录-QQ空间】10亿级直播背后的技术优化
本篇文章内容来自2016年TOP100summit QQ空间客户端研发总监王辉的案例分享.编辑:Cynthia 王辉:腾讯SNG社交平台部研发总监.腾讯QQ空间移动客户端技术负责人高级工程师.09年起 ...
- 海量数据处理 - 10亿个数中找出最大的10000个数(top K问题)
前两天面试3面学长问我的这个问题(想说TEG的3个面试学长都是好和蔼,希望能完成最后一面,各方面原因造成我无比想去鹅场的心已经按捺不住了),这个问题还是建立最小堆比较好一些. 先拿10000个数建堆, ...
- 有10 亿个 url,每个 url 大小小于 56B,要求去重,内存只给你4G
问题:有10 亿个 url,每个 url 大小小于 56B,要求去重,内存只给你4G 思路: 1.首先将给定的url调用hash方法计算出对应的hash的value,在10亿的url中相同url必然有 ...
- 转 DataTorrent 1.0每秒处理超过10亿个实时事件
DataTorrent是一个实时的流式处理和分析平台,它每秒可以处理超过10亿个实时事件. 与Twitter平均每秒大约6000条微博相比,最近发布的DataTorrent 1.0似乎已经超出了需求, ...
- (2.10)Mysql之SQL基础——约束及主键重复处理
(2.10)Mysql之SQL基础——约束及主键重复处理 关键词:mysql约束,批量插入数据主键冲突 [1]查看索引: show index from table_name; [2]查看有约束的列: ...
- 你知道军装照H5浏览了多少次吗? 10亿
7月29日,由人民日报客户端推出的<快看呐!这是我的军装照>(以下简称<军装照>)H5页面,由它所引发的全民晒“军装照”现象级事件,据统计,截至8月18日,<军装照> ...
随机推荐
- Midjourney:一步一步教你如何使用 AI 绘画 MJ
一步一步如何使用 Midjourney 教程:教学怎么用 MJ? 一.Midjourney(MJ)是什么? Midjourney是一款使用文字描述来生成高质量图像的AI绘画工具.这篇文章主要介绍了Mi ...
- win10 双开微信 微信双开
方法1:鼠标连续点击实现Windows微信双开在桌面上找到微信图标,鼠标左键连续点击2次为打开一个微信,连续点击8次就打开了4个微信. 注意:不要连续点开太多防止卡顿. 方法2:回车键双击微信图标实现 ...
- python字符串集合面试笔试题
python字符串面试笔试题 以下代码的输出是? s = 'foo' t = 'bar' print('barf' in 2 * (s + t)) A.True B.Fasle +运算符连接字符串,而 ...
- 【HTML-CSS】div中加入icon后input标签占用不满问题
做登录表单时遇到了一个宽度控制不好的问题,放入图标后,input框总是无法正确的填满剩余空间(尺寸过大/过小) 原因是input元素和父元素div宽度都写死的问题 把父元素的高度删除,宽度改成max- ...
- SQLLabs靶场 less11-20
SQLLabs靶场 less11-20 Less-11-16 请求方式 注入类型 拼接方式 POST 联合.报错.布尔盲注.延时盲注 username='x'11 请求方式 注入类型 拼接方式 POS ...
- 2022-04-23:给定你一个整数数组 nums 我们要将 nums 数组中的每个元素移动到 A 集合 或者 B 集合中 使得 A 集合和 B 集合不为空,并且 average(A) == aver
2022-04-23:给定你一个整数数组 nums 我们要将 nums 数组中的每个元素移动到 A 集合 或者 B 集合中 使得 A 集合和 B 集合不为空,并且 average(A) == aver ...
- 2020-10-22:谈谈java中的LongAdder和LongAccumulator的相同点和不同点。
福哥答案2020-10-22: 简单回答:相同点:都是多个单元操作.不同点:LongAdder相加,LongAccumulator自定义计算规则. 中级回答:相同点: LongAddr与LongAcc ...
- laravel ServiceProvider 服务提供者使用案例
1. 实例化一个类 2.全局注册这个类 3.在控制器中使用 public function register() { $this->app->singleton('wxminapp', f ...
- vue+iview 动态调整Table的列顺序
需求:因table列太多,且每个部门关注的信息不一样,拖来拖去不方便观看,客户想让Table列可以拖动,且可以保存顺序. 但是搞动态拖动太难了,我不会,于是改为操作columns数据 思路: < ...
- 【lwip】14-TCP协议分析之TCP协议之可靠传输的实现(TCP干货)
lwip_14_TCP协议之可靠传输的实现 前言 前面章节太长了,不得不分开. 这里已源码为主,默认读者已知晓概念或原理,概念或原理可以参考前面章节,有分析. 参考:李柱明博客:https://w ...