PostgreSQL 同一种SQL为什么这样写会提升45%性能 --程序员和DBA思维方式不同决定...

开头还是介绍一下群,如果感兴趣PolarDB ,MongoDB ,MySQL ,PostgreSQL ,Redis, Oceanbase, Sql Server等有问题,有需求都可以加群群内有各大数据库行业大咖,可以解决你的问题。加群请联系 liuaustin3 ,(共2300人左右 1 + 2 + 3 + 4 +5 + 6) 新人奖直接分配到5群,开始建6群。

1c7a65d8207bad8d577a315d79548ba5.png

SQL语句,简单的大家都会写,但如果是复杂的SQL语句,撰写起来很多程序员就不会了,甚至一些DBA也不会写,今天我们来模拟一些个别的例子来看看一些复杂的SQL语句怎么写。同时最后我们演示一下复杂的SQL 程序员和 DBA 在专项的思维模式不同导致的运行效率不同的案例。(想要看程序员和DBA的SQL撰写的比较直接到练习3)

我们先循序渐进,热身一下,在开始写SQL之前我们,需要测试数据, 我们用三个简单的表以及相关的数据来去演示一些复杂的SQL怎么写。下面我们创建一些表,以及一些数据方便我们开始撰写一些比较复杂的SQL

CREATE TABLE students (
    student_id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    age INTEGER NOT NULL,
    gender VARCHAR(10) NOT NULL
);

CREATE TABLE subjects (
    subject_id SERIAL PRIMARY KEY,
    name VARCHAR(50) NOT NULL
);


CREATE TABLE scores (
    score_id SERIAL PRIMARY KEY,
    student_id INTEGER,
    subject_id INTEGER,
    score INTEGER NOT NULL
);
INSERT INTO students (student_id, name, age, gender) VALUES
    (1, 'John', 20, 'Male'),
    (2, 'Alice', 22, 'Female'),
    (3, 'Bob', 21, 'Male'),
    (4, 'Emily', 23, 'Female');

-- 插入科目数据
INSERT INTO subjects (subject_id, name) VALUES
    (1, 'Math'),
    (2, 'English'),
    (3, 'Science');

-- 插入成绩数据
INSERT INTO scores (score_id, student_id, subject_id, score) VALUES
    (1, 1, 1, 95),
    (2, 1, 2, 88),
    (3, 1, 3, 92),
    (4, 2, 1, 80),
    (5, 2, 2, 85),
    (6, 2, 3, 90),
    (7, 3, 1, 92),
    (8, 3, 2, 90),
    (9, 3, 3, 94),
    (10, 4, 1, 85),
    (11, 4, 2, 88),
    (12, 4, 3, 82),
    (13, 1, 1, 90),
    (14, 1, 2, 85),
    (15, 1, 3, 88),
    (16, 2, 1, 92),
    (17, 2, 2, 80),
    (18, 2, 3, 85),
    (19, 3, 1, 85),
    (20, 3, 2, 92),
    (21, 3, 3, 90),
    (22, 4, 1, 88),
    (23, 4, 2, 85),
    (24, 4, 3, 86);

练习一:

请撰写当前学生分数中排名前两位的学生成绩(数学成绩),并且按照排名顺序进行展示。

在SQL中这个需求是比较简单的,在我们使用的开源和商业数据库都有对这样的要求有特殊的函数来进行支持,rank ,rank 函数是对表中指定的值进行排序,按照指定值的排序展示,并添加rank 列来展示排序的数字。

下面的是利用这个函数来对数学的成绩进行排序,然后取其中排名等于1 和2的进行展示。

 
 
WITH ranked_scores AS (
    SELECT
        score_id,
        student_id,
        subject_id,
        score,
        RANK() OVER (ORDER BY score DESC) AS rank
    FROM
        scores
    WHERE
        subject_id = 1
)
SELECT
    score_id,
    student_id,
    subject_id,
    score
FROM
    ranked_scores
WHERE
    rank <= 2;

76753675db7c2e43ab477739bf509d68.png

QUERY PLAN                                                                    
-------------------------------------------------------------------------------------------------------------------------------------------------
 Subquery Scan on ranked_scores  (cost=0.14..3.58 rows=1 width=16) (actual time=0.027..0.032 rows=3 loops=1)
   ->  WindowAgg  (cost=0.14..3.57 rows=1 width=24) (actual time=0.026..0.030 rows=3 loops=1)
         Run Condition: (rank() OVER (?) <= 2)
         ->  Index Scan Backward using idx_subjects_score on scores  (cost=0.14..3.56 rows=1 width=16) (actual time=0.015..0.019 rows=5 loops=1)
               Filter: (subject_id = 1)
               Rows Removed by Filter: 9
 Planning Time: 0.428 ms
 Execution Time: 0.106 ms

练习2 展示考分中,英语成绩最低,但通过数学成绩进行排名从高的人的学号,姓名和数学分数

这个语句的主要撰写的方案是,先对要进行查询的数据范围进行缩小,lowestEnglistscorestudents 就是在对最低的英语成绩进行筛选,这里的distinct on 主要是对student_id 进行去重,防止一个学生在不同的学期的英语成绩都是最低的,然后最后展示的不是两个人而是一个人两次占用的最低英语成绩的名额,基于POSTGRESQL中在order by 语句中的去重不能直接写distinct 只能使用distinct on 的表达方式。

在获得最低英语成绩的两个学生的ID后,在通过mathscoresforlowestenglish 来对要展示的数据进行组合,这里只获取英语最低成绩的两个人的ID 与整体数据进行LEFT JOIN 后只展示数学成绩,最后在获得数据后,对数据成绩进行倒序排序完成整个语句的撰写

sql_test=# WITH LowestEnglishScoreStudents AS (
    SELECT DISTINCT ON (student_id)
        student_id
    FROM
        scores
    WHERE
        subject_id = 2
    ORDER BY
        student_id, score ASC
    LIMIT 2
),
MathScoresForLowestEnglish AS (
    SELECT
        s.student_id,
        st.name,
        s.score AS math_score
    FROM
        scores s
    JOIN
        LowestEnglishScoreStudents les ON s.student_id = les.student_id
    JOIN
        students st ON s.student_id = st.student_id
    WHERE
        s.subject_id = 1
)
SELECT
    student_id,
    name,
    math_score
FROM
    MathScoresForLowestEnglish
ORDER BY
    math_score DESC;
 student_id | name  | math_score 
------------+-------+------------
          1 | John  |         95
          2 | Alice |         92
          1 | John  |         90
          2 | Alice |         80
(4 rows)
QUERY PLAN                                                                         
-----------------------------------------------------------------------------------------------------------------------------------------------------------
 Sort  (cost=5.96..5.96 rows=1 width=226) (actual time=0.116..0.118 rows=4 loops=1)
   Sort Key: s.score DESC
   Sort Method: quicksort  Memory: 25kB
   ->  Nested Loop  (cost=3.85..5.95 rows=1 width=226) (actual time=0.094..0.109 rows=4 loops=1)
         Join Filter: (scores.student_id = s.student_id)
         ->  Nested Loop  (cost=3.71..5.76 rows=1 width=226) (actual time=0.086..0.094 rows=2 loops=1)
               ->  Limit  (cost=3.57..3.57 rows=1 width=8) (actual time=0.063..0.067 rows=2 loops=1)
                     ->  Unique  (cost=3.57..3.57 rows=1 width=8) (actual time=0.061..0.064 rows=2 loops=1)
                           ->  Sort  (cost=3.57..3.57 rows=1 width=8) (actual time=0.060..0.061 rows=3 loops=1)
                                 Sort Key: scores.student_id, scores.score
                                 Sort Method: quicksort  Memory: 25kB
                                 ->  Index Scan using idx_student_id on scores  (cost=0.14..3.56 rows=1 width=8) (actual time=0.022..0.029 rows=8 loops=1)
                                       Filter: (subject_id = 2)
                                       Rows Removed by Filter: 16
               ->  Index Scan using students_pkey on students st  (cost=0.15..2.17 rows=1 width=222) (actual time=0.011..0.011 rows=1 loops=2)
                     Index Cond: (student_id = scores.student_id)
         ->  Index Scan using idx_student_id on scores s  (cost=0.14..0.17 rows=1 width=8) (actual time=0.003..0.005 rows=2 loops=2)
               Index Cond: (student_id = st.student_id)
               Filter: (subject_id = 1)
               Rows Removed by Filter: 4
 Planning Time: 0.590 ms
 Execution Time: 0.164 ms
(22 rows)

练习 3 我们对于英语最差,的同学的数学成绩进行平均值的计算并展示

下面的语句是程序员一般撰写的方式,然后后面我们用DBA对这个语句进行优化,大家对比的看

sql_test=# WITH LowestEnglishScoreStudents AS (
    SELECT DISTINCT ON (student_id)
        student_id
    FROM
        scores
    WHERE
        subject_id = 2
    ORDER BY
        student_id, score ASC
    LIMIT 2
),
MathScoresForLowestEnglish AS (
    SELECT
        s.student_id,
        st.name,
        (SELECT AVG(score) FROM scores WHERE subject_id = 1 AND student_id = s.student_id) AS avg_math_score
    FROM
        scores s
    JOIN
        LowestEnglishScoreStudents les ON s.student_id = les.student_id
    JOIN
        students st ON s.student_id = st.student_id
    WHERE
        s.subject_id = 1
)
SELECT
    distinct(student_id),name,avg_math_score
FROM
    MathScoresForLowestEnglish;
 student_id | name  |   avg_math_score    
------------+-------+---------------------
          1 | John  | 92.5000000000000000
          2 | Alice | 86.0000000000000000

我们可以得出我们要的结果,然后我们看看语句的撰写中的执行计划的分析

sql_test=# explain analyze WITH LowestEnglishScoreStudents AS (
    SELECT DISTINCT ON (student_id)
        student_id
    FROM
        scores
    WHERE
        subject_id = 2
    ORDER BY
        student_id, score ASC
    LIMIT 2
),
MathScoresForLowestEnglish AS (
    SELECT
        s.student_id,
        st.name,
        (SELECT AVG(score) FROM scores WHERE subject_id = 1 AND student_id = s.student_id) AS avg_math_score
    FROM
        scores s
    JOIN
        LowestEnglishScoreStudents les ON s.student_id = les.student_id
    JOIN
        students st ON s.student_id = st.student_id
    WHERE
        s.subject_id = 1
)
SELECT
    distinct(student_id),name,avg_math_score
FROM
    MathScoresForLowestEnglish;
                                                                           QUERY PLAN                                                                            
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
 Unique  (cost=8.13..8.14 rows=1 width=254) (actual time=0.134..0.140 rows=2 loops=1)
   ->  Sort  (cost=8.13..8.13 rows=1 width=254) (actual time=0.134..0.135 rows=4 loops=1)
         Sort Key: s.student_id, st.name, ((SubPlan 1))
         Sort Method: quicksort  Memory: 25kB
         ->  Nested Loop  (cost=3.85..8.12 rows=1 width=254) (actual time=0.088..0.122 rows=4 loops=1)
               Join Filter: (scores.student_id = s.student_id)
               ->  Nested Loop  (cost=3.71..5.76 rows=1 width=226) (actual time=0.067..0.073 rows=2 loops=1)
                     ->  Limit  (cost=3.57..3.57 rows=1 width=8) (actual time=0.055..0.058 rows=2 loops=1)
                           ->  Unique  (cost=3.57..3.57 rows=1 width=8) (actual time=0.053..0.055 rows=2 loops=1)
                                 ->  Sort  (cost=3.57..3.57 rows=1 width=8) (actual time=0.052..0.053 rows=3 loops=1)
                                       Sort Key: scores.student_id, scores.score
                                       Sort Method: quicksort  Memory: 25kB
                                       ->  Index Scan using idx_student_id on scores  (cost=0.14..3.56 rows=1 width=8) (actual time=0.036..0.043 rows=8 loops=1)
                                             Filter: (subject_id = 2)
                                             Rows Removed by Filter: 16
                     ->  Index Scan using students_pkey on students st  (cost=0.15..2.17 rows=1 width=222) (actual time=0.005..0.005 rows=1 loops=2)
                           Index Cond: (student_id = scores.student_id)
               ->  Index Scan using idx_student_id on scores s  (cost=0.14..0.17 rows=1 width=4) (actual time=0.002..0.004 rows=2 loops=2)
                     Index Cond: (student_id = st.student_id)
                     Filter: (subject_id = 1)
                     Rows Removed by Filter: 4
               SubPlan 1
                 ->  Aggregate  (cost=2.16..2.17 rows=1 width=32) (actual time=0.007..0.007 rows=1 loops=4)
                       ->  Index Scan using idx_student_id on scores scores_1  (cost=0.14..2.16 rows=1 width=4) (actual time=0.002..0.003 rows=2 loops=4)
                             Index Cond: (student_id = s.student_id)
                             Filter: (subject_id = 1)
                             Rows Removed by Filter: 4
 Planning Time: 0.657 ms
 Execution Time: 0.214 ms
(29 rows)

上面的执行计划,有一些复杂,有没有方式将语句进行改变,降低执行计划的复杂性。

sql_test=# explain analyze WITH LowestEnglishScoreStudents AS (
    SELECT DISTINCT ON (student_id)
        student_id
    FROM
        scores
    WHERE
        subject_id = 2
    ORDER BY
        student_id, score ASC
    LIMIT 2
),
MathScoresForLowestEnglish AS (
    SELECT
        s.student_id,
        AVG(s.score) AS avg_math_score
    FROM
        scores s
    JOIN
        LowestEnglishScoreStudents les ON s.student_id = les.student_id
    WHERE
        s.subject_id = 1
    GROUP BY
        s.student_id
)
SELECT
    m.student_id,
    st.name,
    m.avg_math_score
FROM
    MathScoresForLowestEnglish m
JOIN
    students st ON m.student_id = st.student_id;
                                                                        QUERY PLAN                                                                         
-----------------------------------------------------------------------------------------------------------------------------------------------------------
 Nested Loop  (cost=3.85..7.96 rows=1 width=254) (actual time=0.080..0.091 rows=2 loops=1)
   ->  GroupAggregate  (cost=3.71..5.77 rows=1 width=36) (actual time=0.072..0.079 rows=2 loops=1)
         Group Key: s.student_id
         ->  Nested Loop  (cost=3.71..5.75 rows=1 width=8) (actual time=0.054..0.066 rows=4 loops=1)
               ->  Limit  (cost=3.57..3.57 rows=1 width=8) (actual time=0.045..0.049 rows=2 loops=1)
                     ->  Unique  (cost=3.57..3.57 rows=1 width=8) (actual time=0.044..0.047 rows=2 loops=1)
                           ->  Sort  (cost=3.57..3.57 rows=1 width=8) (actual time=0.043..0.044 rows=3 loops=1)
                                 Sort Key: scores.student_id, scores.score
                                 Sort Method: quicksort  Memory: 25kB
                                 ->  Index Scan using idx_student_id on scores  (cost=0.14..3.56 rows=1 width=8) (actual time=0.024..0.032 rows=8 loops=1)
                                       Filter: (subject_id = 2)
                                       Rows Removed by Filter: 16
               ->  Index Scan using idx_student_id on scores s  (cost=0.14..2.16 rows=1 width=8) (actual time=0.004..0.006 rows=2 loops=2)
                     Index Cond: (student_id = scores.student_id)
                     Filter: (subject_id = 1)
                     Rows Removed by Filter: 4
   ->  Index Scan using students_pkey on students st  (cost=0.15..2.17 rows=1 width=222) (actual time=0.004..0.004 rows=1 loops=2)
         Index Cond: (student_id = s.student_id)
 Planning Time: 0.339 ms
 Execution Time: 0.147 ms
(20 rows)

这两个语句的结果都是一样的,但是下面的语句明显从执行的时间,和计划的产生都要好过上面开发人员的撰写的语句。

DBA 撰写

WITH LowestEnglishScoreStudents AS (    SELECT DISTINCT ON (student_id)        student_id    FROM        scores    WHERE        subject_id = 2    ORDER BY        student_id, score ASC    LIMIT 2 ), MathScoresForLowestEnglish AS (    SELECT        s.student_id,        AVG(s.score) AS avg_math_score    FROM        scores s    JOIN        LowestEnglishScoreStudents les ON s.student_id = les.student_id    WHERE        s.subject_id = 1    GROUP BY        s.student_id ) SELECT    m.student_id,    st.name,    m.avg_math_score FROM    MathScoresForLowestEnglish m JOIN    students st ON m.student_id = st.student_id;  

程序员撰写

 WITH LowestEnglishScoreStudents AS (    SELECT DISTINCT ON (student_id)        student_id    FROM        scores    WHERE        subject_id = 2    ORDER BY        student_id, score ASC    LIMIT 2 ), MathScoresForLowestEnglish AS (    SELECT        s.student_id,        st.name,        (SELECT AVG(score) FROM scores WHERE subject_id = 1 AND student_id = s.student_id) AS avg_math_score    FROM        scores s    JOIN        LowestEnglishScoreStudents les ON s.student_id = les.student_id    JOIN        students st ON s.student_id = st.student_id    WHERE        s.subject_id = 1 ) SELECT    distinct(student_id),name,avg_math_score FROM    MathScoresForLowestEnglish;  

那么这两条语句最大的不同在于在平均成绩的计算上,DBA 撰写的方式是,条件过滤合并的方式,而程序员的撰写方式明显是在使用内部子查询来完成运算,相当于每一条数据都要进行计算,导致在执行的过程中多次访问和扫描 score 表。

通过上面的语句来进行演示,明显DBA的撰写的方式从语句的理解和执行的效率都高于开发人员撰写的语句,那么为什么开发人员一般撰写语句都撰写成第二种方式,主要还是由于思维的方式,对象,方法,类的思维方式导致程序员写不出高效率的SQL。

这里没有贬低程序员的意思,只是职业不同思维的角度不同导致的差异。

___________________________________________________________

近期文章预告

大龄程序员被裁员,我很平凡但我也有生存的权利,请给我一条活路

c1ff14af59f9ecbde68d4605835fe844.png

置顶文章:

临时工说:DBA 新职业,善于发现工作的人有工作---云数据库成本精算师

MongoDB 不是软柿子,想替换就替换

PolarDB  Serverless POC测试中有没有坑与发现的疑问 (大妖复仇记前传)

MongoDB  挑战传统数据库聚合查询,干不死他们的

临时工说: 快速识别 “海洋贝壳类” 数据库方法速递

临时工说:国产 数据库 销售人员  图鉴

往期热门文章:

PostgreSQL  熊灿灿一句话够学半个月 之 KILL -9

临时工访谈:PolarDB Serverless  发现“大”问题了  之 灭妖记 续集

临时工说:国内数据库企业存活   “三板斧”

临时工访谈:庙小妖风大-PolarDB 组团镇妖 之 他们是第一  (阿里云组团PK笔者实录

临时工访谈:金牌 “女” 销售从ORACLE 转到另类国产数据库 到底  为什么?

临时工访谈:无名氏意外到访-- 也祝你好运(管理者PUA DBA现场直播)

临时工说:搞数据库 光凭的是技术,那DBA的死多少次?

PostgreSQL  分组查询可以不进行全表扫描吗?速度提高上千倍?

临时工说:分析当前经济形势下 DBA 被裁员的根因

PostgreSQL PG_DUMP 工作失败了怎么回事及如何处理

MySQL 八怪(高老师)现场解决问题实录

PostgreSQL 为什么也不建议 RR隔离级别,MySQL别笑

临时工访谈:OceanBase上海开大会,我们四个开小会 OB 国产数据库破局者

临时工说:OceanBase 到访,果然数据库的世界很卷,没边

临时工访谈:恶意裁员后,一个国产数据库企业程序员的心声

临时工说:上云后给 我一个 不裁 DBA的理由

PolarDB for PostgreSQL  有意思吗?有意思呀

PostgreSQL   玩PG我们是认真的,vacuum 稳定性平台我们有了

临时工说:裁员裁到 DBA 咋办  临时工教你 套路1 2 3

PolarDB  搞那么多复杂磁盘计费的东西,抽筋了吗?

临时工说:OceanBase 到访,果然数据库的世界很卷,没边

MONGODB  ---- Austindatabases  历年文章合集

MYSQL  --Austindatabases 历年文章合集

POSTGRESQL --Austindatabaes 历年文章整理

POLARDB  -- Ausitndatabases 历年的文章集合

PostgreSQL  查询语句开发写不好是必然,不是PG的锅

SQL SERVER 如何实现UNDO REDO  和PostgreSQL 有近亲关系吗

MongoDB 2023纽约 MongoDB 大会 -- 我们怎么做的新一代引擎 SBE Mongodb 7.0双擎力量(译)

MongoDB 2023年度纽约 MongoDB 年度大会话题 -- MongoDB 数据模式与建模

MongoDB  双机热备那篇文章是  “毒”

MongoDB   会丢数据吗?在次补刀MongoDB  双机热备

临时工说:从人性的角度来分析为什么公司内MySQL 成为少数派,PolarDB 占领高处

POLARDB  到底打倒了谁  PPT 分享 (文字版)

PostgreSQL  字符集乌龙导致数据查询排序的问题,与 MySQL 稳定 "PG不稳定"

PostgreSQL  Patroni 3.0 新功能规划 2023年 纽约PG 大会 (音译)

Austindatabases 公众号,主要围绕数据库技术(PostgreSQL, MySQL, Mongodb, Redis, SqlServer,PolarDB, Oceanbase 等)和职业发展,国外数据库大会音译,国外大型IT信息类网站文章翻译,等,希望能和您共同发展。

cc12fc7a2ad16d6a581e1e652d736d7e.png

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值