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长啥样?的更多相关文章

  1. 使用HAProxy、PHP、Redis和MySQL支撑每周10亿请求

    在公司的发展中,保证服务器的可扩展性对于扩大企业的市场需要具有重要作用,因此,这对架构师提出了一定的要求.Octivi联合创始人兼软件架构师Antoni Orfin将向你介绍一个非常简单的架构,使用H ...

  2. 转 使用HAProxy,PHPRedis,和MySQL支撑10亿请求每周架构细节

    [编者按]在公司的发展中,保证服务器的可扩展性对于扩大企业的市场需要具有重要作用,因此,这对架构师提出了一定的要求.Octivi联合创始人兼软件架构师Antoni Orfin将向你介绍一个非常简单的架 ...

  3. “军装照”背后——天天P图如何应对10亿流量的后台承载。

    WeTest 导读 天天P图"军装照"活动交出了一份10亿浏览量的答卷,一时间刷屏朋友圈,看到这幕,是不是特别想复制一个如此成功的H5?不过本文不教你如何做一个爆款H5,而是介绍天 ...

  4. 安装win10操作系统的设备将要突破10亿台

    导读 该公司最初的目标是在发布后的三年内在 10 亿台设备上运行 Windows 10. 据微软高管梅赫迪 (Yusuf Mehdi) 周四在 Twitter 上透露,目前已经有 8 亿多台设备安装了 ...

  5. TOP100summit:【分享实录-QQ空间】10亿级直播背后的技术优化

    本篇文章内容来自2016年TOP100summit QQ空间客户端研发总监王辉的案例分享.编辑:Cynthia 王辉:腾讯SNG社交平台部研发总监.腾讯QQ空间移动客户端技术负责人高级工程师.09年起 ...

  6. 海量数据处理 - 10亿个数中找出最大的10000个数(top K问题)

    前两天面试3面学长问我的这个问题(想说TEG的3个面试学长都是好和蔼,希望能完成最后一面,各方面原因造成我无比想去鹅场的心已经按捺不住了),这个问题还是建立最小堆比较好一些. 先拿10000个数建堆, ...

  7. 有10 亿个 url,每个 url 大小小于 56B,要求去重,内存只给你4G

    问题:有10 亿个 url,每个 url 大小小于 56B,要求去重,内存只给你4G 思路: 1.首先将给定的url调用hash方法计算出对应的hash的value,在10亿的url中相同url必然有 ...

  8. 转 DataTorrent 1.0每秒处理超过10亿个实时事件

    DataTorrent是一个实时的流式处理和分析平台,它每秒可以处理超过10亿个实时事件. 与Twitter平均每秒大约6000条微博相比,最近发布的DataTorrent 1.0似乎已经超出了需求, ...

  9. (2.10)Mysql之SQL基础——约束及主键重复处理

    (2.10)Mysql之SQL基础——约束及主键重复处理 关键词:mysql约束,批量插入数据主键冲突 [1]查看索引: show index from table_name; [2]查看有约束的列: ...

  10. 你知道军装照H5浏览了多少次吗? 10亿

    7月29日,由人民日报客户端推出的<快看呐!这是我的军装照>(以下简称<军装照>)H5页面,由它所引发的全民晒“军装照”现象级事件,据统计,截至8月18日,<军装照> ...

随机推荐

  1. TENGSHE-OS-渗透测试系统-win11版

    下载ISO文件 创建新的虚拟机 VM17 已支持直接创建 win11 x64 稍后安装系统 选中win11 修改路径 win11需要设置8位加密密码 勾选安全引导 根据自身情况选择 默认即可 150G ...

  2. WPF Button MouseDown事件

    Button的MouseDown事件 WPF的Button控件,鼠标点击时,MouseDown事件没有触发. 经确认,Button的MouseDown被内部处理了.下面是基类ButtonBase的部分 ...

  3. NC54585 小魂和他的数列

    题目链接 题目 题目描述 一天,小魂正和一个数列玩得不亦乐乎. 小魂的数列一共有n个元素,第i个数为Ai. 他发现,这个数列的一些子序列中的元素是严格递增的. 他想知道,这个数列一共有多少个长度为K的 ...

  4. markdown插入图片、音频视频

    1.markdown 简介 Markdown 是一种轻量级标记语言,它允许人们使用易读易写的纯文本格式编写文档. Markdown 编写的文档后缀为 .md, .markdown 简单易学容易上手,十 ...

  5. elSelect点击空白处无法收起下拉框(失去焦点并隐藏)

    学习记录,为了以后有同样的问题,省得再百度了,方便自己也方便你们element 中多选的select 有个问题,就是点击空白或者关闭弹窗,下拉还会一直展示出来百度了好一会,觉得下面两位大佬说的最合理, ...

  6. Pwn系列之Protostar靶场 Stack6题解

    源码如下: #include <stdlib.h> #include <unistd.h> #include <stdio.h> #include <stri ...

  7. 【易车网实例】x-sign逆向保姆级教程

    易车号x-sign逆向 前言 许多网站都有反爬机制,x-sign加密就是许多反爬虫机制的其中一种,本次将以易车号作为目标进行演示. 方法仅供学习参考. 链接:https://hao.yiche.com ...

  8. 高精度------C++

    高精度运算------C++ (加减乘除) 例:ZOJ2001 http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=1001 The ...

  9. 2022-08-12:方案1 : {7, 10}; xxxx : {a , b}; 1 2 3 4; FunnyGoal = 100; OffenseGoal = 130。 找到一个最少方案数,让Fu

    2022-08-12:方案1 : {7, 10}: xxxx : {a , b}: 1 2 3 4: FunnyGoal = 100: OffenseGoal = 130. 找到一个最少方案数,让Fu ...

  10. 2022-04-17:给定一个数组arr,其中的值有可能正、负、0, 给定一个正数k。 返回累加和>=k的所有子数组中,最短的子数组长度。 来自字节跳动。力扣862。

    2022-04-17:给定一个数组arr,其中的值有可能正.负.0, 给定一个正数k. 返回累加和>=k的所有子数组中,最短的子数组长度. 来自字节跳动.力扣862. 答案2022-04-17: ...