女人自慰AV免费观看内涵网,日韩国产剧情在线观看网址,神马电影网特片网,最新一级电影欧美,在线观看亚洲欧美日韩,黄色视频在线播放免费观看,ABO涨奶期羡澄,第一导航fulione,美女主播操b

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

幾個寫SQL時常見的“壞毛病”及優化技巧

程序員cxuan ? 來源:程序員cxuan ? 2023-01-17 09:34 ? 次閱讀

今天給大家分享幾個寫 SQL 時常見的“壞毛病”及優化技巧。

1、LIMIT 語句

分頁查詢是最常用的場景之一,但也通常也是最容易出問題的地方。比如對于下面簡單的語句,一般 DBA 想到的辦法是在 type、 name、 create_time 字段上加組合索引。這樣條件排序都能有效的利用到索引,性能迅速提升。

SELECT*
FROMoperation
WHEREtype='SQLStats'
ANDname='SlowLog'
ORDERBYcreate_time
LIMIT1000,10;

好吧,可能90%以上的 DBA 解決該問題就到此為止。但當 LIMIT 子句變成 “LIMIT 1000000,10” 時,程序員仍然會抱怨:我只取10條記錄為什么還是慢?

要知道數據庫也并不知道第1000000條記錄從什么地方開始,即使有索引也需要從頭計算一次。出現這種性能問題,多數情形下是程序員偷懶了。

在前端數據瀏覽翻頁,或者大數據分批導出等場景下,是可以將上一頁的最大值當成參數作為查詢條件的。SQL 重新設計如下:

SELECT*
FROMoperation
WHEREtype='SQLStats'
ANDname='SlowLog'
ANDcreate_time>'2017-03-161400'
ORDERBYcreate_timelimit10;

2、隱式轉換

SQL語句中查詢變量和字段定義類型不匹配是另一個常見的錯誤。比如下面的語句:

mysql>explainextendedSELECT*
>FROMmy_balanceb
>WHEREb.bpn=14000000123
>ANDb.isverifiedISNULL;
mysql>showwarnings;
|Warning|1739|Cannotuserefaccessonindex'bpn'duetotypeorcollationconversiononfield'bpn'

其中字段 bpn 的定義為 varchar(20),MySQL 的策略是將字符串轉換為數字之后再比較。函數作用于表字段,索引失效。

上述情況可能是應用程序框架自動填入的參數,而不是程序員的原意。現在應用框架很多很繁雜,使用方便的同時也小心它可能給自己挖坑。

3、關聯更新、刪除

雖然 MySQL5.6 引入了物化特性,但需要特別注意它目前僅僅針對查詢語句的優化。對于更新或刪除需要手工重寫成 JOIN。

比如下面 UPDATE 語句,MySQL 實際執行的是循環/嵌套子查詢(DEPENDENT SUBQUERY),其執行時間可想而知。

UPDATEoperationo
SETstatus='applying'
WHEREo.idIN(SELECTid
FROM(SELECTo.id,
o.status
FROMoperationo
WHEREo.group=123
ANDo.statusNOTIN('done')
ORDERBYo.parent,
o.id
LIMIT1)t);

執行計劃:

+----+--------------------+-------+-------+---------------+---------+---------+-------+------+-----------------------------------------------------+
|id|select_type|table|type|possible_keys|key|key_len|ref|rows|Extra|
+----+--------------------+-------+-------+---------------+---------+---------+-------+------+-----------------------------------------------------+
|1|PRIMARY|o|index||PRIMARY|8||24|Usingwhere;Usingtemporary|
|2|DEPENDENTSUBQUERY||||||||ImpossibleWHEREnoticedafterreadingconsttables|
|3|DERIVED|o|ref|idx_2,idx_5|idx_5|8|const|1|Usingwhere;Usingfilesort|
+----+--------------------+-------+-------+---------------+---------+---------+-------+------+-----------------------------------------------------+

重寫為 JOIN 之后,子查詢的選擇模式從 DEPENDENT SUBQUERY 變成 DERIVED,執行速度大大加快,從7秒降低到2毫秒。

UPDATEoperationo
JOIN(SELECTo.id,
o.status
FROMoperationo
WHEREo.group=123
ANDo.statusNOTIN('done')
ORDERBYo.parent,
o.id
LIMIT1)t
ONo.id=t.id
SETstatus='applying'

執行計劃簡化為:

+----+-------------+-------+------+---------------+-------+---------+-------+------+-----------------------------------------------------+
|id|select_type|table|type|possible_keys|key|key_len|ref|rows|Extra|
+----+-------------+-------+------+---------------+-------+---------+-------+------+-----------------------------------------------------+
|1|PRIMARY||||||||ImpossibleWHEREnoticedafterreadingconsttables|
|2|DERIVED|o|ref|idx_2,idx_5|idx_5|8|const|1|Usingwhere;Usingfilesort|
+----+-------------+-------+------+---------------+-------+---------+-------+------+-----------------------------------------------------+

4、混合排序

MySQL 不能利用索引進行混合排序。但在某些場景,還是有機會使用特殊方法提升性能的。

SELECT* 
FROMmy_ordero
INNERJOINmy_appraiseaONa.orderid=o.id
ORDERBYa.is_replyASC,
a.appraise_timeDESC
LIMIT0,20

執行計劃顯示為全表掃描:

+----+-------------+-------+--------+-------------+---------+---------+---------------+---------+-+
|id|select_type|table|type|possible_keys|key|key_len|ref|rows|Extra
+----+-------------+-------+--------+-------------+---------+---------+---------------+---------+-+
|1|SIMPLE|a|ALL|idx_orderid|NULL|NULL|NULL|1967647|Usingfilesort|
|1|SIMPLE|o|eq_ref|PRIMARY|PRIMARY|122|a.orderid|1|NULL|
+----+-------------+-------+--------+---------+---------+---------+-----------------+---------+-+

由于 is_reply 只有0和1兩種狀態,我們按照下面的方法重寫后,執行時間從1.58秒降低到2毫秒。

SELECT*
FROM((SELECT*
FROMmy_ordero
INNERJOINmy_appraisea
ONa.orderid=o.id
ANDis_reply=0
ORDERBYappraise_timeDESC
LIMIT0,20)
UNIONALL
(SELECT*
FROMmy_ordero
INNERJOINmy_appraisea
ONa.orderid=o.id
ANDis_reply=1
ORDERBYappraise_timeDESC
LIMIT0,20))t
ORDERBYis_replyASC,
appraisetimeDESC
LIMIT20;

5、EXISTS語句

MySQL 對待 EXISTS 子句時,仍然采用嵌套子查詢的執行方式。如下面的 SQL 語句:

SELECT*
FROMmy_neighborn
LEFTJOINmy_neighbor_applysra
ONn.id=sra.neighbor_id
ANDsra.user_id='xxx'
WHEREn.topic_status'xxx')
ANDn.topic_type<>5

執行計劃為:

+----+--------------------+-------+------+-----+------------------------------------------+---------+-------+---------+-----+
|id|select_type|table|type|possible_keys|key|key_len|ref|rows|Extra|
+----+--------------------+-------+------+-----+------------------------------------------+---------+-------+---------+-----+
|1|PRIMARY|n|ALL||NULL|NULL|NULL|1086041|Usingwhere|
|1|PRIMARY|sra|ref||idx_user_id|123|const|1|Usingwhere|
|2|DEPENDENTSUBQUERY|m|ref||idx_message_info|122|const|1|Usingindexcondition;Usingwhere|
+----+--------------------+-------+------+-----+------------------------------------------+---------+-------+---------+-----+

去掉 exists 更改為 join,能夠避免嵌套子查詢,將執行時間從1.93秒降低為1毫秒。

SELECT*
FROMmy_neighborn
INNERJOINmessage_infom
ONn.id=m.neighbor_id
ANDm.inuser='xxx'
LEFTJOINmy_neighbor_applysra
ONn.id=sra.neighbor_id
ANDsra.user_id='xxx'
WHEREn.topic_status5

新的執行計劃:

+----+-------------+-------+--------+-----+------------------------------------------+---------+-----+------+-----+
|id|select_type|table|type|possible_keys|key|key_len|ref|rows|Extra|
+----+-------------+-------+--------+-----+------------------------------------------+---------+-----+------+-----+
|1|SIMPLE|m|ref||idx_message_info|122|const|1|Usingindexcondition|
|1|SIMPLE|n|eq_ref||PRIMARY|122|ighbor_id|1|Usingwhere|
|1|SIMPLE|sra|ref||idx_user_id|123|const|1|Usingwhere|
+----+-------------+-------+--------+-----+------------------------------------------+---------+-----+------+-----+

6、條件下推

外部查詢條件不能夠下推到復雜的視圖或子查詢的情況有:

  • 聚合子查詢;
  • 含有 LIMIT 的子查詢;
  • UNION 或 UNION ALL 子查詢;
  • 輸出字段中的子查詢;

如下面的語句,從執行計劃可以看出其條件作用于聚合子查詢之后

SELECT*
FROM(SELECTtarget,
Count(*)
FROMoperation
GROUPBYtarget)t
WHEREtarget='rm-xxxx'
+----+-------------+------------+-------+---------------+-------------+---------+-------+------+-------------+
|id|select_type|table|type|possible_keys|key|key_len|ref|rows|Extra|
+----+-------------+------------+-------+---------------+-------------+---------+-------+------+-------------+
|1|PRIMARY||ref|||514|const|2|Usingwhere|
|2|DERIVED|operation|index|idx_4|idx_4|519|NULL|20|Usingindex|
+----+-------------+------------+-------+---------------+-------------+---------+-------+------+-------------+

確定從語義上查詢條件可以直接下推后,重寫如下:

SELECTtarget,
Count(*)
FROMoperation
WHEREtarget='rm-xxxx'
GROUPBYtarget

執行計劃變為:

+----+-------------+-----------+------+---------------+-------+---------+-------+------+--------------------+
|id|select_type|table|type|possible_keys|key|key_len|ref|rows|Extra|
+----+-------------+-----------+------+---------------+-------+---------+-------+------+--------------------+
|1|SIMPLE|operation|ref|idx_4|idx_4|514|const|1|Usingwhere;Usingindex|
+----+-------------+-----------+------+---------------+-------+---------+-------+------+--------------------+

7、提前縮小范圍

先上初始 SQL 語句:

SELECT*
FROMmy_ordero
LEFTJOINmy_userinfou
ONo.uid=u.uid
LEFTJOINmy_productinfop
ONo.pid=p.pid
WHERE(o.display=0)
AND(o.ostaus=1)
ORDERBYo.selltimeDESC
LIMIT0,15

該SQL語句原意是:先做一系列的左連接,然后排序取前15條記錄。從執行計劃也可以看出,最后一步估算排序記錄數為90萬,時間消耗為12秒。

+----+-------------+-------+--------+---------------+---------+---------+-----------------+--------+----------------------------------------------------+
|id|select_type|table|type|possible_keys|key|key_len|ref|rows|Extra|
+----+-------------+-------+--------+---------------+---------+---------+-----------------+--------+----------------------------------------------------+
|1|SIMPLE|o|ALL|NULL|NULL|NULL|NULL|909119|Usingwhere;Usingtemporary;Usingfilesort|
|1|SIMPLE|u|eq_ref|PRIMARY|PRIMARY|4|o.uid|1|NULL|
|1|SIMPLE|p|ALL|PRIMARY|NULL|NULL|NULL|6|Usingwhere;Usingjoinbuffer(BlockNestedLoop)|
+----+-------------+-------+--------+---------------+---------+---------+-----------------+--------+----------------------------------------------------+

由于最后 WHERE 條件以及排序均針對最左主表,因此可以先對 my_order 排序提前縮小數據量再做左連接。SQL 重寫后如下,執行時間縮小為1毫秒左右。

SELECT*
FROM(
SELECT*
FROMmy_ordero
WHERE(o.display=0)
AND(o.ostaus=1)
ORDERBYo.selltimeDESC
LIMIT0,15
)o
LEFTJOINmy_userinfou
ONo.uid=u.uid
LEFTJOINmy_productinfop
ONo.pid=p.pid
ORDERBYo.selltimeDESC
limit0,15

再檢查執行計劃:子查詢物化后(select_type=DERIVED)參與 JOIN。雖然估算行掃描仍然為90萬,但是利用了索引以及 LIMIT 子句后,實際執行時間變得很小。

+----+-------------+------------+--------+---------------+---------+---------+-------+--------+----------------------------------------------------+
|id|select_type|table|type|possible_keys|key|key_len|ref|rows|Extra|
+----+-------------+------------+--------+---------------+---------+---------+-------+--------+----------------------------------------------------+
|1|PRIMARY||ALL|NULL|NULL|NULL|NULL|15|Usingtemporary;Usingfilesort|
|1|PRIMARY|u|eq_ref|PRIMARY|PRIMARY|4|o.uid|1|NULL|
|1|PRIMARY|p|ALL|PRIMARY|NULL|NULL|NULL|6|Usingwhere;Usingjoinbuffer(BlockNestedLoop)|
|2|DERIVED|o|index|NULL|idx_1|5|NULL|909112|Usingwhere|
+----+-------------+------------+--------+---------------+---------+---------+-------+--------+----------------------------------------------------+

8、中間結果集下推

再來看下面這個已經初步優化過的例子(左連接中的主表優先作用查詢條件):

SELECTa.*,
c.allocated
FROM(
SELECTresourceid
FROMmy_distributed
WHEREisdelete=0
ANDcusmanagercode='1234567'
ORDERBYsalecodelimit20)a
LEFTJOIN
(
SELECTresourcesid,sum(ifnull(allocation,0)*12345)allocated
FROMmy_resources
GROUPBYresourcesid)c
ONa.resourceid=c.resourcesid

那么該語句還存在其它問題嗎?不難看出子查詢 c 是全表聚合查詢,在表數量特別大的情況下會導致整個語句的性能下降。

其實對于子查詢 c,左連接最后結果集只關心能和主表 resourceid 能匹配的數據。因此我們可以重寫語句如下,執行時間從原來的2秒下降到2毫秒。

SELECTa.*,
c.allocated
FROM(
SELECTresourceid
FROMmy_distributed
WHEREisdelete=0
ANDcusmanagercode='1234567'
ORDERBYsalecodelimit20)a
LEFTJOIN
(
SELECTresourcesid,sum(ifnull(allocation,0)*12345)allocated
FROMmy_resourcesr,
(
SELECTresourceid
FROMmy_distributed
WHEREisdelete=0
ANDcusmanagercode='1234567'
ORDERBYsalecodelimit20)a
WHEREr.resourcesid=a.resourcesid
GROUPBYresourcesid)c
ONa.resourceid=c.resourcesid

但是子查詢 a 在我們的SQL語句中出現了多次。這種寫法不僅存在額外的開銷,還使得整個語句顯的繁雜。使用 WITH 語句再次重寫:

WITHaAS
(
SELECTresourceid
FROMmy_distributed
WHEREisdelete=0
ANDcusmanagercode='1234567'
ORDERBYsalecodelimit20)
SELECTa.*,
c.allocated
FROMa
LEFTJOIN
(
SELECTresourcesid,sum(ifnull(allocation,0)*12345)allocated
FROMmy_resourcesr,
a
WHEREr.resourcesid=a.resourcesid
GROUPBYresourcesid)c
ONa.resourceid=c.resourcesid

總結

數據庫編譯器產生執行計劃,決定著SQL的實際執行方式。但是編譯器只是盡力服務,所有數據庫的編譯器都不是盡善盡美的。

上述提到的多數場景,在其它數據庫中也存在性能問題。了解數據庫編譯器的特性,才能避規其短處,寫出高性能的SQL語句。

程序員在設計數據模型以及編寫SQL語句時,要把算法的思想或意識帶進來。

編寫復雜SQL語句要養成使用 WITH 語句的習慣。簡潔且思路清晰的SQL語句也能減小數據庫的負擔 。

審核編輯 :李倩


聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • SQL
    SQL
    +關注

    關注

    1

    文章

    780

    瀏覽量

    44816
  • 數據庫
    +關注

    關注

    7

    文章

    3901

    瀏覽量

    65783
  • 編譯器
    +關注

    關注

    1

    文章

    1656

    瀏覽量

    49898

原文標題:這樣寫 SQL,差點人沒了

文章出處:【微信號:cxuangoodjob,微信公眾號:程序員cxuan】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦
    熱點推薦

    如何一眼定位SQL的代碼來源:一款SQL染色標記的簡易MyBatis插件

    作者:京東物流 郭忠強 導語 本文分析了后端研發和運維在日常工作中所面臨的線上SQL定位排查痛點,基于姓名貼的靈感,設計和開發了一款SQL染色標記的MyBatis插件。該插件輕量高效,對業務代碼無
    的頭像 發表于 03-05 11:36 ?351次閱讀
    如何一眼定位<b class='flag-5'>SQL</b>的代碼來源:一款<b class='flag-5'>SQL</b>染色標記的簡易MyBatis插件

    Devart: dbForge Compare Bundle for SQL Server—比較SQL數據庫最簡單、最準確的方法

    ? dbForge Compare Bundle For SQL Server:包含兩個工具,可幫助您節省用于手動數據庫比較的 70% 的時間 dbForge數據比較 幫助檢測和分析實時SQL數據庫
    的頭像 發表于 01-17 11:35 ?463次閱讀

    dbForge Studio For SQL Server:用于有效開發的最佳SQL Server集成開發環境

    dbForge Studio For SQL Server:用于有效開發的最佳SQL Server集成開發環境 SQL編碼助手 SQL代碼分析 查詢分析器 可視化查詢生成器 數據和模式
    的頭像 發表于 01-16 10:36 ?627次閱讀

    通過Skyvia Connect SQL終端節點訪問任何數據

    通過 Skyvia Connect SQL 終端節點訪問任何數據 ? 通過 Skyvia Connect SQL 終端節點訪問任何數據ADO.NET 數據網關 使用 Skyvia Connect
    的頭像 發表于 01-02 09:31 ?310次閱讀
    通過Skyvia Connect <b class='flag-5'>SQL</b>終端節點訪問任何數據

    淺談SQL優化小技巧

    作者:京東零售 王軍 回顧:MySQL的執行過程回顧 MySQL的執行過程,幫助 介紹 如何進行sql優化。 (1)客戶端發送一條查詢語句到服務器; (2)服務器先查詢緩存,如果命中緩存,則立即返回
    的頭像 發表于 12-25 09:59 ?792次閱讀

    SSM框架的性能優化技巧 SSM框架中RESTful API的實現

    緩存操作。 優化SQL查詢 : SQL查詢是數據庫操作中的瓶頸之一。 使用索引來加速查詢,避免全表掃描,盡量使用索引掃描。
    的頭像 發表于 12-17 09:10 ?702次閱讀

    SQL錯誤代碼及解決方案

    SQL數據庫開發和管理中,常見的錯誤代碼及其解決方案可以歸納如下: 一、語法錯誤(Syntax Errors) 錯誤代碼 :無特定代碼,但通常會在錯誤消息中明確指出是語法錯誤。 原因 :SQL語句
    的頭像 發表于 11-19 10:21 ?6110次閱讀

    常用SQL函數及其用法

    SQL(Structured Query Language)是一種用于管理和操作關系數據庫的編程語言。SQL 提供了豐富的函數庫,用于數據檢索、數據更新、數據刪除以及數據聚合等操作。以下是一些常用
    的頭像 發表于 11-19 10:18 ?1235次閱讀

    SQL與NoSQL的區別

    在信息技術領域,數據庫是存儲和管理數據的核心組件。隨著互聯網的發展和大數據時代的到來,對數據庫的需求也在不斷變化。SQL和NoSQL作為兩種主流的數據庫管理系統,各自有著獨特的優勢和應用場
    的頭像 發表于 11-19 10:15 ?514次閱讀

    MySQL性能優化淺析及線上案例

    手段則會對用戶的使用體驗造成影響,嚴重的則會直接導致訂單、金額直接受損,因而就需要時刻關注數據庫的性能問題。 2、 性能優化幾個常見措施 數據庫性能優化
    的頭像 發表于 10-22 15:17 ?964次閱讀
    MySQL性能<b class='flag-5'>優化</b>淺析及線上案例

    大數據從業者必知必會的Hive SQL調優技巧

    不盡人意。本文針對Hive SQL的性能優化進行深入研究,提出了一系列可行的調優方案,并給出了相應的優化案例和優化前后的SQL代碼。通過合理
    的頭像 發表于 09-24 13:30 ?613次閱讀

    QPS提升10倍的sql優化

    本次慢sql優化是大促準備時的一個優化優化4c16g單實例mysql支持QPS從437到4610,今天發文時618大促已經順利結束,該mysql庫和應用在整個大促期間運行也非常穩定。
    的頭像 發表于 08-21 11:12 ?568次閱讀
    QPS提升10倍的<b class='flag-5'>sql</b><b class='flag-5'>優化</b>

    IP 地址在 SQL 注入攻擊中的作用及防范策略

    數據庫在各個領域的逐步應用,其安全性也備受關注。SQL 注入攻擊作為一種常見的數據庫攻擊手段,給網絡安全帶來了巨大威脅。今天我們來聊一聊SQL 注入攻擊的基本知識。 SQL 注入攻擊的
    的頭像 發表于 08-05 17:36 ?573次閱讀

    恒訊科技分析:sql數據庫怎么用?

    SQL數據庫的使用通常包括以下幾個基本步驟: 1、選擇數據庫系統: 選擇適合您需求的SQL數據庫系統,如MySQL、PostgreSQL、Microsoft SQL Server、SQ
    的頭像 發表于 07-15 14:40 ?564次閱讀

    什么是 Flink SQL 解決不了的問題?

    簡介 在實時數據開發過程中,大家經常會用 Flink SQL 或者 Flink DataStream API 來做數據加工。通常情況下選用2者都能加工出想要的數據,但是總會有 Flink SQL
    的頭像 發表于 07-09 20:50 ?538次閱讀