日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 运维知识 > 数据库 >内容正文

数据库

数据库优化:SQL高性能优化指南,助你成就大神之路!

發(fā)布時(shí)間:2023/12/10 数据库 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 数据库优化:SQL高性能优化指南,助你成就大神之路! 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

1、參數(shù)是子查詢時(shí),使用 EXISTS 代替 IN

如果 IN 的參數(shù)是(1,2,3)這樣的值列表時(shí),沒啥問題,但如果參數(shù)是子查詢時(shí),就需要注意了。比如,現(xiàn)在有如下兩個(gè)表:

現(xiàn)在我們要查出同時(shí)存在于兩個(gè)表的員工,即田中和鈴木,則以下用 IN 和 EXISTS 返回的結(jié)果是一樣,但是用 EXISTS 的 SQL 會(huì)更快:

--?慢 SELECT?*?FROM?Class_A WHERE?id?IN?(SELECT?id?FROM??CLASS_B);--?快 SELECT?*FROM?Class_A?A?WHERE?EXISTS (SELECT?*?FROM?Class_B??BWHERE?A.id?=?B.id);

為啥使用 EXISTS 的 SQL 運(yùn)行更快呢,有兩個(gè)原因

  • 可以`用到索引,如果連接列 (id) 上建立了索引,那么查詢 Class_B 時(shí)不用查實(shí)際的表,只需查索引就可以了。

  • 如果使用 EXISTS,那么只要查到一行數(shù)據(jù)滿足條件就會(huì)終止查詢, 不用像使用 IN 時(shí)一樣掃描全表。在這一點(diǎn)上 NOT EXISTS 也一樣

  • 另外如果 IN 后面如果跟著的是子查詢,由于 SQL 會(huì)先執(zhí)行 IN 后面的子查詢,會(huì)將子查詢的結(jié)果保存在一張臨時(shí)的工作表里(內(nèi)聯(lián)視圖),然后掃描整個(gè)視圖,顯然掃描整個(gè)視圖這個(gè)工作很多時(shí)候是非常耗時(shí)的,而用 EXISTS 不會(huì)生成臨時(shí)表。

    當(dāng)然了,如果 IN 的參數(shù)是子查詢時(shí),也可以用連接來(lái)代替,如下:

    --?使用連接代替?IN?SELECT?A.id,?A.name FROM?Class_A?A?INNER?JOIN?Class_B?B?ON?A.id?=?B.id;

    用到了 「id」列上的索引,而且由于沒有子查詢,也不會(huì)生成臨時(shí)表

    2、避免排序

    SQL 是聲明式語(yǔ)言,即對(duì)用戶來(lái)說(shuō),只關(guān)心它能做什么,不用關(guān)心它怎么做。這樣可能會(huì)產(chǎn)生潛在的性能問題:排序,會(huì)產(chǎn)生排序的代表性運(yùn)算有下面這些

    • GROUP BY 子句

    • ORDER BY 子句

    • 聚合函數(shù)(SUM、COUNT、AVG、MAX、MIN)

    • DISTINCT

    • 集合運(yùn)算符(UNION、INTERSECT、EXCEPT)

    • 窗口函數(shù)(RANK、ROW_NUMBER 等)

    如果在內(nèi)存中排序還好,但如果內(nèi)存不夠?qū)е滦枰谟脖P上排序上的話,性能就會(huì)急劇下降,所以我們需要減少不必要的排序。怎樣做可以減少排序呢。

    1、 使用集合運(yùn)算符的 ALL 可選項(xiàng)

    SQL 中有 UNION,INTERSECT,EXCEPT 三個(gè)集合運(yùn)算符,默認(rèn)情況下,這些運(yùn)算符會(huì)為了避免重復(fù)數(shù)據(jù)而進(jìn)行排序,對(duì)比一下使用 UNION 運(yùn)算符加和不加 ALL 的情況:

    注意:加 ALL 是優(yōu)化性能非常有效的手段,不過前提是不在乎結(jié)果是否有重復(fù)數(shù)據(jù)。

    2、使用 EXISTS 代表 DISTINCT

    為了排除重復(fù)數(shù)據(jù), DISTINCT 也會(huì)對(duì)結(jié)果進(jìn)行排序,如果需要對(duì)兩張表的連接結(jié)果進(jìn)行去重,可以考慮用 EXISTS 代替 DISTINCT,這樣可以避免排序。

    如何找出有銷售記錄的商品,使用如下 DISTINCT 可以:

    SELECT?DISTINCT?I.item_no FROM?Items?I?INNER?JOIN?SalesHistory?SH ON?I.?item_no?=?SH.?item_no;

    不過更好的方式是使用 EXISTS:

    SELECT?item_no?FROM?Items?I WHERE?EXISTS?(SELECT?*FROM?SalesHistory?SHWHERE?I.item_no?=?SH.item_no);

    既用到了索引,又避免了排序?qū)π阅艿膿p耗。

    2、在極值函數(shù)中使用索引(MAX/MIN)

    使用 MAX/ MIN 都會(huì)對(duì)進(jìn)行排序,如果參數(shù)字段上沒加索引會(huì)導(dǎo)致全表掃描,如果建有索引,則只需要掃描索引即可,對(duì)比如下

    --?這樣寫需要掃描全表? SELECT?MAX(item)FROM?Items;--?這樣寫能用到索引? SELECT?MAX(item_no)FROM?Items;

    注意:極值函數(shù)參數(shù)推薦為索引列中并不是不需要排序,而是優(yōu)化了排序前的查找速度(畢竟索引本身就是有序排列的)。

    3、能寫在 WHERE 子句里的條件不要寫在 HAVING 子句里

    下列 SQL 語(yǔ)句返回的結(jié)果是一樣的:

    --?聚合后使用?HAVING?子句過濾 SELECT?sale_date,?SUM(quantity)FROM?SalesHistory?GROUP?BY?sale_date HAVING?sale_date?=?'2007-10-01';--?聚合前使用?WHERE?子句過濾 SELECT?sale_date,?SUM(quantity)FROM?SalesHistoryWHERE?sale_date?=?'2007-10-01'?GROUP?BY?sale_date;

    使用第二條語(yǔ)句效率更高,原因主要有兩點(diǎn)

  • 使用 GROUP BY 子句進(jìn)行聚合時(shí)會(huì)進(jìn)行排序,如果事先通過 WHERE 子句能篩選出一部分行,能減輕排序的負(fù)擔(dān)

  • 在 WHERE 子句中可以使用索引,而 HAVING 子句是針對(duì)聚合后生成的視頻進(jìn)行篩選的,但很多時(shí)候聚合后生成的視圖并沒有保留原表的索引結(jié)構(gòu)

  • 4、在 GROUP BY 子句和 ORDER BY 子句中使用索引

    GROUP BY 子句和 ORDER BY 子句一般都會(huì)進(jìn)行排序,以對(duì)行進(jìn)行排列和替換,不過如果指定帶有索引的列作為這兩者的參數(shù)列,由于用到了索引,可以實(shí)現(xiàn)高速查詢,由于索引是有序的,排序本身都會(huì)被省略掉

    5、使用索引時(shí),條件表達(dá)式的左側(cè)應(yīng)該是原始字段

    假設(shè)我們?cè)?col 列上建立了索引,則下面這些 SQL 語(yǔ)句無(wú)法用到索引

    SELECT?*FROM?SomeTableWHERE?col?*?1.1?>?100;SELECT?*FROM?SomeTableWHERE?SUBSTR(col,?1,?1)?=?'a';

    以上第一個(gè) SQL 在索引列上進(jìn)行了運(yùn)算, 第二個(gè) SQL 對(duì)索引列使用了函數(shù),均無(wú)法用到索引,正確方式是把列單獨(dú)放在左側(cè),如下:

    SELECT?*FROM?SomeTableWHERE?col_1?>?100?/?1.1;

    當(dāng)然如果需要對(duì)此列使用函數(shù),則無(wú)法避免在左側(cè)運(yùn)算,可以考慮使用函數(shù)索引,不過一般不推薦隨意這么做。

    6、盡量避免使用否定形式

    如下的幾種否定形式不能用到索引:

    • <>

    • !=

    • NOT IN

    所以以下 了SQL 語(yǔ)句會(huì)導(dǎo)致全表掃描

    SELECT?*FROM?SomeTableWHERE?col_1?<>?100;

    可以改成以下形式

    SELECT?*FROM?SomeTableWHERE?col_1?>?100?or?col_1?<?100;

    7、進(jìn)行默認(rèn)的類型轉(zhuǎn)換

    假設(shè) col 是 char 類型,則推薦使用以下第二,三條 SQL 的寫法,不推薦第一條 SQL 的寫法

    ×?SELECT?*?FROM?SomeTable?WHERE?col_1?=?10; ○?SELECT?*?FROM?SomeTable?WHERE?col_1?=?'10'; ○?SELECT?*?FROM?SomeTable?WHERE?col_1?=?CAST(10,?AS?CHAR(2));

    雖然第一條 SQL 會(huì)默認(rèn)把 10 轉(zhuǎn)成 '10',但這種默認(rèn)類型轉(zhuǎn)換不僅會(huì)增加額外的性能開銷,還會(huì)導(dǎo)致索引不可用,所以建議使用的時(shí)候進(jìn)行類型轉(zhuǎn)換。

    8、減少中間表

    在 SQL 中,子查詢的結(jié)果會(huì)產(chǎn)生一張新表,不過如果不加限制大量使用中間表的話,會(huì)帶來(lái)兩個(gè)問題,一是展示數(shù)據(jù)需要消耗內(nèi)存資源,二是原始表中的索引不容易用到,所以盡量減少中間表也可以提升性能。

    9、靈活使用 HAVING 子句

    這一點(diǎn)與上面第八條相呼應(yīng),對(duì)聚合結(jié)果指定篩選條件時(shí),使用 HAVING 是基本的原則,可能一些工程師會(huì)傾向于使用下面這樣的寫法:

    SELECT?*FROM?(SELECT?sale_date,?MAX(quantity)?AS?max_qtyFROM?SalesHistory?GROUP?BY?sale_date)?TMPWHERE?max_qty?>=?10;

    雖然上面這樣的寫法能達(dá)到目的,但會(huì)生成 TMP 這張臨時(shí)表,所以應(yīng)該使用下面這樣的寫法:

    SELECT?sale_date,?MAX(quantity)?FROM?SalesHistoryGROUP?BY?sale_date HAVING?MAX(quantity)?>=?10;

    HAVING 子句和聚合操作是同時(shí)執(zhí)行的,所以比起生成中間表后再執(zhí)行 HAVING 子句,效率會(huì)更高,代碼也更簡(jiǎn)潔

    10、需要對(duì)多個(gè)字段使用 IN 謂詞時(shí),將它們匯總到一處

    一個(gè)表的多個(gè)字段可能都使用了 IN 謂詞,如下:

    SELECT?id,?state,?city?FROM?Addresses1?A1WHERE?state?IN?(SELECT?stateFROM?Addresses2?A2WHERE?A1.id?=?A2.id)?AND?city?IN?(SELECT?cityFROM?Addresses2?A2?WHERE?A1.id?=?A2.id);

    這段代碼用到了兩個(gè)子查詢,也就產(chǎn)生了兩個(gè)中間表,可以像下面這樣寫

    SELECT?*FROM?Addresses1?A1WHERE?id?||?state?||?cityIN?(SELECT?id?||?state||?cityFROM?Addresses2?A2);

    這樣子查詢不用考慮關(guān)聯(lián)性,沒有中間表產(chǎn)生,而且只執(zhí)行一次即可。

    11、 使用延遲查詢優(yōu)化 limit [offset], [rows]

    經(jīng)常出現(xiàn)類似以下的 SQL 語(yǔ)句:

    SELECT?*?FROM?film?LIMIT?100000,?10

    offset 特別大!

    這是我司出現(xiàn)很多慢 SQL 的主要原因之一,尤其是在跑任務(wù)需要分頁(yè)執(zhí)行時(shí),經(jīng)常跑著跑著 offset 就跑到幾十萬(wàn)了,導(dǎo)致任務(wù)越跑越慢。

    LIMIT 能很好地解決分頁(yè)問題,但如果 offset 過大的話,會(huì)造成嚴(yán)重的性能問題,原因主要是因?yàn)?MySQL 每次會(huì)把一整行都掃描出來(lái),掃描 offset 遍,找到 offset 之后會(huì)拋棄 offset 之前的數(shù)據(jù),再?gòu)?offset 開始讀取 10 條數(shù)據(jù),顯然,這樣的讀取方式問題。

    可以通過延遲查詢的方式來(lái)優(yōu)化

    假設(shè)有以下 SQL,有組合索引(sex, rating)

    SELECT?<cols>?FROM?profiles?where?sex='M'?order?by?rating?limit?100000,?10;

    則上述寫法可以改成如下寫法

    SELECT?<cols>?FROM?profiles? inner?join (SELECT?id?form?FROM?profiles?where?x.sex='M'?order?by?rating?limit?100000,?10) as?x?using(id);

    這里利用了覆蓋索引的特性,先從覆蓋索引中獲取 100010 個(gè) id,在丟充掉前 100000 條 id,保留最后 10 個(gè) id 即可,丟掉 100000 條 id 不是什么大的開銷,所以這樣可以顯著提升性能

    12、 利用 LIMIT 1 取得唯一行

    數(shù)據(jù)庫(kù)引擎只要發(fā)現(xiàn)滿足條件的一行數(shù)據(jù)則立即停止掃描,,這種情況適用于只需查找一條滿足條件的數(shù)據(jù)的情況

    13、 注意組合索引,要符合最左匹配原則才能生效

    假設(shè)存在這樣順序的一個(gè)聯(lián)合索引“col_1, col_2, col_3”。這時(shí),指定條件的順序就很重要。

    ○?SELECT?*?FROM?SomeTable?WHERE?col_1?=?10?AND?col_2?=?100?AND?col_3?=?500; ○?SELECT?*?FROM?SomeTable?WHERE?col_1?=?10?AND?col_2?=?100?; ×?SELECT?*?FROM?SomeTable?WHERE?col_2?=?100?AND?col_3?=?500?;

    前面兩條會(huì)命中索引,第三條由于沒有先匹配 col_1,導(dǎo)致無(wú)法命中索引, 另外如果無(wú)法保證查詢條件里列的順序與索引一致,可以考慮將聯(lián)合索引 拆分為多個(gè)索引。

    14、使用 LIKE 謂詞時(shí),只有前方一致的匹配才能用到索引(最左匹配原則)

    ×?SELECT?*?FROM?SomeTable?WHERE?col_1?LIKE?'%a'; ×?SELECT?*?FROM?SomeTable?WHERE?col_1?LIKE?'%a%'; ○?SELECT?*?FROM?SomeTable?WHERE?col_1?LIKE?'a%';

    上例中,只有第三條會(huì)命中索引,前面兩條進(jìn)行后方一致或中間一致的匹配無(wú)法命中索引

    15、 簡(jiǎn)單字符串表達(dá)式

    模型字符串可以使用 _ 時(shí), 盡可能避免使用 %, 假設(shè)某一列上為 char(5)

    不推薦

    SELECT?first_name,?last_name,homeroom_nbrFROM?StudentsWHERE?homeroom_nbr?LIKE?'A-1%';

    推薦

    SELECT?first_name,?last_name homeroom_nbrFROM?StudentsWHERE?homeroom_nbr?LIKE?'A-1__';?--模式字符串中包含了兩個(gè)下劃線

    16、盡量使用自增 id 作為主鍵

    比如現(xiàn)在有一個(gè)用戶表,有人說(shuō)身份證是唯一的,也可以用作主鍵,理論上確實(shí)可以,不過用身份證作主鍵的話,一是占用空間相對(duì)于自增主鍵大了很多,二是很容易引起頻繁的頁(yè)分裂,造成性能問題(什么是頁(yè)分裂,請(qǐng)參考這篇文章)

    主鍵選擇的幾個(gè)原則:自增,盡量小,不要對(duì)主鍵進(jìn)行修改

    17、在無(wú) WHERE 條件下要計(jì)算表的行數(shù),優(yōu)先使用 count(*)

    優(yōu)先使用以下語(yǔ)句來(lái)統(tǒng)計(jì)行數(shù), innoDB 5.6之后已經(jīng)對(duì)此語(yǔ)句進(jìn)行了優(yōu)化

    SELECT?COUNT(*)?FROM?SomeTable

    按照效率排序的話,count(字段)<count(主鍵 id)<count(1)≈count(*),count(*) 會(huì)選用性能最好的索引來(lái)進(jìn)行排序

    18、避免使用 SELECT * ,盡量利用覆蓋索引來(lái)優(yōu)化性能

    SELECT *?會(huì)提取出一整行的數(shù)據(jù),如果查詢條件中用的是組合索引進(jìn)行查找,還會(huì)導(dǎo)致回表(先根據(jù)組合索引找到葉子節(jié)點(diǎn),再根據(jù)葉子節(jié)點(diǎn)上的主鍵回表查詢一整行),降低性能,而如果我們所要的數(shù)據(jù)就在組合索引里,只需讀取組合索引列,這樣網(wǎng)絡(luò)帶寬將大大減少,假設(shè)有組合索引列 (col_1, col_2)

    推薦用

    SELECT?col_1,?col_2?FROM?SomeTable?WHERE?col_1?=?xxx?AND?col_2?=?xxx

    不推薦用

    SELECT?*FROM?SomeTable?WHERE?col_1?=?xxx?AND??col_2?=?xxx

    19、 如有必要,使用 force index() 強(qiáng)制走某個(gè)索引

    業(yè)務(wù)團(tuán)隊(duì)曾經(jīng)出現(xiàn)類似以下的慢 SQL 查詢

    SELECT?*FROM??SomeTableWHERE?`status`?=?0AND?`gmt_create`?>?1490025600AND?`gmt_create`?<?1490630400AND?`id`?>?0AND?`post_id`?IN?('67778',?'67811',?'67833',?'67834',?'67839',?'67852',?'67861',?'67868',?'67870',?'67878',?'67909',?'67948',?'67951',?'67963',?'67977',?'67983',?'67985',?'67991',?'68032',?'68038'/*...?omitted?480?items?...*/) order?by?id?asc?limit?200;

    post_id 也加了索引,理論上走 post_id 索引會(huì)很快查詢出來(lái),但實(shí)現(xiàn)了通過 EXPLAIN 發(fā)現(xiàn)走的卻是 id 的索引(這里隱含了一個(gè)常見考點(diǎn),在多個(gè)索引的情況下, MySQL 會(huì)如何選擇索引),而 id > 0 這個(gè)查詢條件沒啥用,直接導(dǎo)致了全表掃描, 所以在有多個(gè)索引的情況下一定要慎用,可以使用 force index 來(lái)強(qiáng)制走某個(gè)索引,以這個(gè)例子為例,可以強(qiáng)制走 post_id 索引,效果立竿見影。

    這種由于表中有多個(gè)索引導(dǎo)致 MySQL 誤選索引造成慢查詢的情況在業(yè)務(wù)中也是非常常見,一方面是表索引太多,另一方面也是由于 SQL 語(yǔ)句本身太過復(fù)雜導(dǎo)致, 針對(duì)本例這種復(fù)雜的 SQL 查詢,其實(shí)用 ElasticSearch 搜索引擎來(lái)查找更合適,有機(jī)會(huì)到時(shí)出一篇文章說(shuō)說(shuō)。

    20、 使用 EXPLAIN 來(lái)查看 SQL 執(zhí)行計(jì)劃

    上個(gè)點(diǎn)說(shuō)了,可以使用 EXPLAIN 來(lái)分析 SQL 的執(zhí)行情況,如怎么發(fā)現(xiàn)上文中的最左匹配原則不生效呢,執(zhí)行 「EXPLAIN + SQL 語(yǔ)句」可以發(fā)現(xiàn) key 為 None ,說(shuō)明確實(shí)沒有命中索引

    我司在提供 SQL 查詢的同時(shí),也貼心地加了一個(gè) EXPLAIN 功能及 sql 的優(yōu)化建議,建議各大公司效仿 ^_^,如圖示

    21、 批量插入,速度更快

    當(dāng)需要插入數(shù)據(jù)時(shí),批量插入比逐條插入性能更高

    推薦用

    --?批量插入 INSERT?INTO?TABLE?(id,?user_id,?title)?VALUES?(1,?2,?'a'),(2,3,'b');

    不推薦用

    INSERT?INTO?TABLE?(id,?user_id,?title)?VALUES?(1,?2,?'a'); INSERT?INTO?TABLE?(id,?user_id,?title)?VALUES?(2,3,'b');

    批量插入 SQL 執(zhí)行效率高的主要原因是合并后日志量 MySQL 的 binlog 和 innodb 的事務(wù)讓日志減少了,降低日志刷盤的數(shù)據(jù)量和頻率,從而提高了效率

    22、 慢日志 SQL 定位

    前面我們多次說(shuō)了 SQL 的慢查詢,那么該怎么定位這些慢查詢 SQL 呢,主要用到了以下幾個(gè)參數(shù)

    這幾個(gè)參數(shù)一定要配好,再根據(jù)每條慢查詢對(duì)癥下藥,像我司每天都會(huì)把這些慢查詢提取出來(lái)通過郵件給形式發(fā)送給各個(gè)業(yè)務(wù)團(tuán)隊(duì),以幫忙定位解決

    IT技術(shù)分享社區(qū)

    個(gè)人博客網(wǎng)站:https://programmerblog.xyz

    文章推薦程序員效率:畫流程圖常用的工具程序員效率:整理常用的在線筆記軟件遠(yuǎn)程辦公:常用的遠(yuǎn)程協(xié)助軟件,你都知道嗎?51單片機(jī)程序下載、ISP及串口基礎(chǔ)知識(shí)硬件:斷路器、接觸器、繼電器基礎(chǔ)知識(shí)

    總結(jié)

    以上是生活随笔為你收集整理的数据库优化:SQL高性能优化指南,助你成就大神之路!的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。