烧死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日,<军装照> ...
随机推荐
- Android 通过scheme跳转支付宝实现支付
原文地址: Android 通过scheme跳转支付宝实现支付 - Stars-One的杂货小窝 需求的来源是支付功能,由于支付宝不允许个人开通具有webhook的支付服务,所以是对接了一个支付系统( ...
- macOS下安装 n 管理包(node版本管理工具)
1. 安装 n 管理包 终端命令全局安装 npm install -g n 安装成功后在终端输入 n --version 或 n 查看,可看到 n 的默认安装目录 下面就是使用 n 的方式了, 首先查 ...
- React 富文本编辑 braft-editor
推荐一种react-富文本编辑器,braft-editor braft-editor的github:https://github.com/margox/braft-editor braft-edito ...
- python的format方法中文字符输出问题
format方法的介绍 前言 提示:本文仅介绍format方法的使用和中文的输出向左右和居中输出问题 一.format方法的使用 format方法一般可以解决中文居中输出问题,假如我们设定宽度,当中文 ...
- 【Python基础】推导式(列表、字典、集合)
推导式是一种简洁.高效的语法,用于从一个可迭代对象中生成新的可迭代(iterable)对象. 通常情况下,在以下情况可以考虑使用推导式: 只需要简单的表达式来计算新的可迭代对象的元素. 可迭代对象不是 ...
- 在chatGPT的帮助下成功从Rancher中删除无效的集群
只要你坚持,不放弃,问题总有解决的一天! 与chatgpt进行了几次沟通,成功解决历史遗留问题,成功从rancher中删除了无效的集群 chatGPT回答1 如果您在 Rancher UI 中无法删除 ...
- 2021-12-30:分裂问题。 一个数n,可以分裂成一个数组[n/2, n%2, n/2], 这个数组中哪个数不是1或者0,就继续分裂下去。 比如 n = 5,一开始分裂成[2, 1, 2], [2
2021-12-30:分裂问题. 一个数n,可以分裂成一个数组[n/2, n%2, n/2], 这个数组中哪个数不是1或者0,就继续分裂下去. 比如 n = 5,一开始分裂成[2, 1, 2], [2 ...
- 【设计模式】使用 go 语言实现简单工厂模式
最近在看<大话设计模式>,这本书通过对话形式讲解设计模式的使用场景,有兴趣的可以去看一下. 第一篇讲的是简单工厂模式,要求输入两个数和运算符号,得到运行结果. 这个需求不难,难就难在类要怎 ...
- 如何将jq动画做出高帧的感觉?(丝滑顺畅)
前言 我最近在一点一点研究我 博客园 的前端代码,算是边敲边学吧,还算是挺有意思的. 是这样的,之前见过一个效果,就是先显示博客的背景,然后博客主界面缓缓的上升到正确位置,于是乎,干他!开撸代码! 各 ...
- 基于 Dash Bio 的生物信息学数据可视化
Dash 是用于搭建响应式 Web 应用的 Python 开源库.Dash Bio 是面向生物信息学,且与 Dash 兼容的组件,它可以将生物信息学领域中常见的数据整合到 Dash 应用程序,以实现响 ...