烧死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日,<军装照> ...
随机推荐
- Vue中关于keep-alive的使用
keep-alive是Vue提供的一个抽象组件,用来对组件进行缓存,从而节省性能,由于是一个抽象组件,所以在v页面渲染完毕后不会被渲染成一个DOM元素 当组件在keep-alive内被切换时组件的ac ...
- Win Pycharm + Appium + 夜神模拟器 实现APP自动化
前言: 之前的文章已经介绍完通过使用 真机 进行APP自动化.此篇文章将介绍使用 夜神模拟器(Nox) 进行APP自动化测试. 一.基础配置 1.请移步此篇文章(https://www.cnblogs ...
- (亲自实践)python OpenCV已经安装但是import cv2的方法不能用
最近在学习验证码图片识别,安装完pip install opencv-python之后,发现导入的方法命令有底纹,也就是不能使用 解决方案如下: 找到安装python的路径,安装完opencv-pyt ...
- 【Azure 媒体服务】Media Service的编码示例 -- 创建缩略图子画面的.NET代码调试问题
问题描述 在中国区Azure上,使用Media Service服务,想要使用.NET的代码来对上传视频创建缩略图(Thumbnail) . 通过官网文档(https://docs.azure.cn/z ...
- Go windows 环境搭建
下载地址 官网下载地址:https://golang.google.cn/dl/ 1.下载完之后 双击msi进行安装 路径可以不用改, 继续next 安装完之后就需要配置环境变量, 找到环境变量 GO ...
- 2020-12-15:mysql的回滚机制是怎么实现的?
福哥答案2020-12-15:[答案来自此链接:](https://www.cnblogs.com/ld-swust/p/5607983.html)在 MySQL 中,恢复机制是通过回滚日志(undo ...
- 2022-01-25:序列化和反序列化 N 叉树。 序列化是指将一个数据结构转化为位序列的过程,因此可以将其存储在文件中或内存缓冲区中,以便稍后在相同或不同的计算机环境中恢复结构。 设计一个序列化和反
2022-01-25:序列化和反序列化 N 叉树. 序列化是指将一个数据结构转化为位序列的过程,因此可以将其存储在文件中或内存缓冲区中,以便稍后在相同或不同的计算机环境中恢复结构. 设计一个序列化和反 ...
- annotate()使用聚合计数、求和、平均数 raw()执行原生的SQL
annotate()使用聚合计数.求和.平均数 raw()执行原生的SQL # 按老师分组,求课程的销量 Course.objects.values('Teacher').annotate(vol= ...
- ModuleNotFoundError: No module named 'flask_login'
ModuleNotFoundError: No module named 'flask_login' 解决: pip install flask_login
- 软硬件--智能穿戴常见BUG及原因分析
软硬件--智能穿戴常见BUG及原因分析 1.手表有常亮功能(类似熄屏表盘),开启常亮暗屏状态下 按侧键,设备时间出现倒退现象:频率切换相关问题: 2.手表有常亮功能(类似熄屏表盘),开启常亮暗屏状态下 ...