一个SQL优化技巧

一个SQL优化技巧

我们系统中存在大量类似如下查询:

SELECT p.*, di.name AS district, q.name AS scene_name, (
    SELECT user_name
    FROM fw_user
    WHERE fw_user_id = u.parent_id
) AS parent_phone, split_part(p.location, ',', 1) AS province
, split_part(p.location, ',', 2) AS city
FROM jd_patient p
    LEFT JOIN fw_user u ON u.fw_user_id = p.fw_user_id
    LEFT JOIN jd_district di ON p.jd_district_id = di.jd_district_id
    LEFT JOIN jd_qrcode q ON p.jd_qrcode_id = q.jd_qrcode_id
WHERE 1 = 1
AND p.fw_user_id IS NOT NULL
ORDER BY p.create_date DESC, p.location
LIMIT 10 OFFSET 0;

这些查询在数据量小的时候,不会出现效率瓶颈,但是数据量一旦增大,就会显著地降低查询速度。

使用explain查看其执行速度:

                                                QUERY PLAN

------------------------------------------------------------------------------------------
-----------------
 Limit  (cost=17975306.40..17975306.43 rows=10 width=2008)
   ->  Sort  (cost=17975306.40..17980273.18 rows=1986709 width=2008)
         Sort Key: p.create_date, p.location
         ->  Hash Left Join  (cost=570.27..17932374.34 rows=1986709 width=2008)
               Hash Cond: (p.jd_qrcode_id = q.jd_qrcode_id)
               ->  Hash Left Join  (cost=2.26..1120474.10 rows=1986709 width=1994)
                     Hash Cond: (p.jd_district_id = di.jd_district_id)
                     ->  Nested Loop Left Join  (cost=0.42..1093343.76 rows=1986709 width=
1856)
                           ->  Seq Scan on jd_patient p  (cost=0.00..107821.94 rows=198670
9 width=1840)
                                 Filter: (fw_user_id IS NOT NULL)
                           ->  Index Scan using pk_fw_user on fw_user u  (cost=0.42..0.49
rows=1 width=32)
                                 Index Cond: (fw_user_id = p.fw_user_id)
                     ->  Hash  (cost=1.37..1.37 rows=37 width=154)
                           ->  Seq Scan on jd_district di  (cost=0.00..1.37 rows=37 width=
154)
               ->  Hash  (cost=427.45..427.45 rows=11245 width=30)
                     ->  Seq Scan on jd_qrcode q  (cost=0.00..427.45 rows=11245 width=30)
               SubPlan 1
                 ->  Index Scan using pk_fw_user on fw_user  (cost=0.42..8.44 rows=1 width
=12)
                       Index Cond: (fw_user_id = u.parent_id)
(19 rows)

优化方法

上面的语句查询了大量数据(包括数据条数、以及p.* ),然后使用临时表order by,但最终又只返回了10条数据。
在结果集大的时候,这个临时表会非常大,内存放不下,于是全部拷贝到磁盘,导致IO飙升,效率急剧下降。

优化的基本策略是尽早缩小结果范围,思路是拆分sql,将排序操作和查询所有数据的操作分开。

具体来看,考虑到实际上需要的数据只有10条,我们可以先找到这10条数据的jd_patient_id:

SELECT p.jd_patient_id
FROM jd_patient p
    LEFT JOIN fw_user u ON u.fw_user_id = p.fw_user_id
    LEFT JOIN jd_district di ON p.jd_district_id = di.jd_district_id
    LEFT JOIN jd_qrcode q ON p.jd_qrcode_id = q.jd_qrcode_id
WHERE 1 = 1
AND p.fw_user_id IS NOT NULL
ORDER BY p.create_date DESC, p.location
LIMIT 10 OFFSET 0;

然后在这10条数据中联合查询各字段的值:

SELECT p.name, di.name AS district, q.name AS scene_name, (
    SELECT user_name
    FROM fw_user
    WHERE fw_user_id = u.parent_id
) AS parent_phone, split_part(p.location, ',', 1) AS province
, split_part(p.location, ',', 2) AS city
FROM jd_patient p
    LEFT JOIN fw_user u ON u.fw_user_id = p.fw_user_id
    LEFT JOIN jd_district di ON p.jd_district_id = di.jd_district_id
    LEFT JOIN jd_qrcode q ON p.jd_qrcode_id = q.jd_qrcode_id
WHERE p.jd_patient_id IN ( xxxxx )
)
ORDER BY p.create_date DESC, p.location

于是优化后的sql如下:

SELECT p.name, di.name AS district, q.name AS scene_name, (
    SELECT user_name
    FROM fw_user
    WHERE fw_user_id = u.parent_id
) AS parent_phone, split_part(p.location, ',', 1) AS province
, split_part(p.location, ',', 2) AS city
FROM jd_patient p
    LEFT JOIN fw_user u ON u.fw_user_id = p.fw_user_id
    LEFT JOIN jd_district di ON p.jd_district_id = di.jd_district_id
    LEFT JOIN jd_qrcode q ON p.jd_qrcode_id = q.jd_qrcode_id
WHERE p.jd_patient_id IN (
    SELECT p.jd_patient_id
    FROM jd_patient p
        LEFT JOIN fw_user u ON u.fw_user_id = p.fw_user_id
        LEFT JOIN jd_district di ON p.jd_district_id = di.jd_district_id
        LEFT JOIN jd_qrcode q ON p.jd_qrcode_id = q.jd_qrcode_id
    WHERE 1 = 1
    AND p.fw_user_id IS NOT NULL
    ORDER BY p.create_date DESC, p.location
    LIMIT 10 OFFSET 0
)
ORDER BY p.create_date DESC, p.location

这样便避免了产生大临时表。
最终性能提高了两个数量级(17975306.40 --> 150933.10):

                                                       QUERY PLAN

------------------------------------------------------------------------------------------
--------------------------------
 Sort  (cost=150933.10..150933.12 rows=10 width=201)
   Sort Key: p.create_date, p.location
   ->  Nested Loop Left Join  (cost=150755.44..150932.93 rows=10 width=201)
         ->  Nested Loop Left Join  (cost=150755.15..150845.33 rows=10 width=203)
               ->  Nested Loop Left Join  (cost=150755.01..150843.65 rows=10 width=81)
                     ->  Nested Loop  (cost=150754.59..150838.86 rows=10 width=81)
                           ->  HashAggregate  (cost=150754.16..150754.26 rows=10 width=16)
                                 Group Key: p_1.jd_patient_id
                                 ->  Limit  (cost=150754.01..150754.03 rows=10 width=86)
                                       ->  Sort  (cost=150754.01..155720.78 rows=1986709 w
idth=86)
                                             Sort Key: p_1.create_date, p_1.location
                                             ->  Seq Scan on jd_patient p_1  (cost=0.00..1
07821.94 rows=1986709 width=86)
                                                   Filter: (fw_user_id IS NOT NULL)
                           ->  Index Scan using pk_jd_patient on jd_patient p  (cost=0.43.
.8.45 rows=1 width=97)
                                 Index Cond: (jd_patient_id = p_1.jd_patient_id)
                     ->  Index Scan using pk_fw_user on fw_user u  (cost=0.42..0.47 rows=1
 width=32)
                           Index Cond: (fw_user_id = p.fw_user_id)
               ->  Index Scan using pk_jd_district on jd_district di  (cost=0.14..0.16 row
s=1 width=154)
                     Index Cond: (p.jd_district_id = jd_district_id)
         ->  Index Scan using pk_jd_qrcode on jd_qrcode q  (cost=0.29..0.30 rows=1 width=3
0)
               Index Cond: (p.jd_qrcode_id = jd_qrcode_id)
         SubPlan 1
           ->  Index Scan using pk_fw_user on fw_user  (cost=0.42..8.44 rows=1 width=12)
                 Index Cond: (fw_user_id = u.parent_id)
(24 rows)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,657评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,662评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,143评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,732评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,837评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,036评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,126评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,868评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,315评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,641评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,773评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,859评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,584评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,676评论 2 351

推荐阅读更多精彩内容