如何優(yōu)化慢SQL?

前言

前幾天幫公司解決線上慢SQL告警問題优烧,遇到了幾個case炕舵。

下面我會結合case案例分析自己這段時間在工作上遇到的慢查詢談談數(shù)據(jù)庫如何優(yōu)化慢查詢逻锐。

一般我們遇到的慢sql都是索引沒有正確使用導致的猜谚,所以我先介紹下索引相關知識

索引介紹

索引概念

排好序的快速查找的數(shù)據(jù)結構(我們平時說的索引,如果沒有特別指明呼巷,都是指B樹,其中聚集索引赎瑰、次要索引、覆蓋索引破镰、復合索引餐曼、前綴索引、唯一索引默認使用的都是B+樹索引鲜漩,除B+樹這種類型的索引外還有哈希索引等)

索引優(yōu)缺點

優(yōu)點:

  • 查找 :提高數(shù)據(jù)檢索效率源譬,降低IO成本。

  • 排序:通過索引對數(shù)據(jù)進行排序孕似,降低排序成本踩娘,降低cpu消耗

缺點:

  • 實際上索引也是一張表,該表保存了主鍵與索引字段喉祭,并指向索引的記錄养渴,所以索引列也需要占空間。

  • 更新表時(insert泛烙、update理卑、delete)不僅要保存數(shù)據(jù)還要更新保存索引文件新添加的索引列。

索引分類

  • 單值索引(單列索引):一個索引只包含單個列蔽氨,一個表中可以有多個單列索引藐唠。

  • 唯一索引:索引列必須唯一,但可以允許有空值

  • 復合索引:一個索引包含多個列

索引結構

  • BTree索引

  • Hash索引

  • full-text全文檢索

  • R-Tree索引

哪些情況要建索引

  • 主鍵自動建主鍵索引

  • 頻繁作為查詢條件的字段應該創(chuàng)建索引

  • 查詢中與其他表關聯(lián)的字段鹉究,外鍵關系建立索引

  • 在高并發(fā)下傾向建立組合索引

  • 查詢中的排序字段宇立,排序字段若通過索引去訪問將大大提高排序速度

  • 查詢中統(tǒng)計或者分組的數(shù)據(jù)

哪些情況不適合建索引

  • 頻繁更新的字段

  • where條件用不到的字段不創(chuàng)建索引

  • 表記錄太少

  • 經(jīng)常增刪改的表

  • 數(shù)據(jù)重復太多的字段,為它建索引意義不大(假如一個表有10萬自赔,有一個字段只有T和F兩種值妈嘹,每個值的分布概率大約只有50%,那么對這個字段的建索引一般不會提高查詢效率匿级,索引的選擇性是指索引列的不同值數(shù)據(jù)與表中索引記錄的比蟋滴,,如果一個表中有2000條記錄痘绎,表中索引列的不同值記錄有1980個津函,這個索引的選擇性為1980/2000=0.99,如果索引項越接近1孤页,這個索引效率越高)

explain字段分析

explain是排查慢sql的一種最常用的手段

mysql> EXPLAIN SELECT 1;

[圖片上傳失敗...(image-5d524b-1700187059535)]

id:表示select子句或者操作的順序

  • id相同:執(zhí)行順序自上而下

  • id不同:id值越大優(yōu)先級越高尔苦,越先被執(zhí)行

  • id相同不同:id越大越先執(zhí)行,相同的自上而下執(zhí)行

select_type:主要是區(qū)分普通查詢、聯(lián)合查詢允坚、子查詢等魂那。

  • SIMPLE:簡單的select查詢,不包含子查詢與union

  • PRIMARY:查詢中包含復雜的子部分稠项,最外層會被標記為primary

  • SUBQUERY:在select或者where列表中包含了子查詢

  • DERIVED:在from列表中包含的子查詢衍生表

  • UNION:若第二個select出現(xiàn)在union之后涯雅,則被標記為union

  • UNION RESESULT:從union表獲取結果的select

table:這一行數(shù)據(jù)是哪個表的數(shù)據(jù)

type:查詢中使用了何種類型

結果值從最好到最壞:system>const>eq_ref>ref>fulltext>ref_or_null>index_merge>unique_subquery>index_subquery>range>index>all

  • 一般來說,得保證查詢至少達到range級別展运,最好能到達ref

  • system:表只有一行記錄(等于系統(tǒng)表)活逆,這是const類型的特例,平時不會出現(xiàn)

  • const:表示通過索引一次就能夠找到

  • eq_ref:唯一性索引掃描拗胜,對于每個索引鍵蔗候,表示只有一條記錄與之匹配,常見于主鍵或唯一索引掃描

  • ref:非唯一性索引掃描埂软,返回匹配某個單獨值的所有行

  • range:只檢索給定范圍的行锈遥,使用一個索引來選擇行,一般就是在where語句中出現(xiàn)了between勘畔、<所灸、>、in等的查詢

  • index:index比all快咖杂,因為index是從索引中讀取庆寺,all是從硬盤中讀取

  • all:遍歷全表才能找到

possible_key:顯示可能應用在這張表中的索引,但實際上不一定用到

key:實際上使用的索引诉字,如果沒有則為null

key_len:表示索引中使用的字節(jié)數(shù)(可能使用的懦尝,不是實際的),可通過該列查詢中使用的索引的長度壤圃,在不損失精確性的情況下陵霉,長度越短越好

ref:顯示索引的哪一列被用到,如果可能的話是一個常數(shù)伍绳,哪些常量被用于查找索引列上的值

rows:大致估算找出所需的記錄要讀取的行數(shù)

Extra:包含不適合在其他列中顯示踊挠,但十分重要的的額外信息

  • Using filesort 說明mysql會對數(shù)據(jù)使用一個外部的索引排序,而不是按照表內的索引順序進行讀取冲杀,mysql中無法利用索引完成的排序成為文件排序

  • Using temporary 使了用臨時表保存中間結果效床,mysql在對查詢結果排序時使用了臨時表,常見于排序order by 和分組查詢group by

  • Using index 表示相應的select操作中使用了覆蓋索引权谁,避免訪問了表的數(shù)據(jù)行剩檀,效率高

  • Using where 表明使用了where進行過濾

  • Using join buffer 使用了連接緩存

  • impossible where 如果where子句的值總是false,不能用來獲取任何元組

  • select table optimized away 在沒有group by子句的情況下旺芽,基于索引優(yōu)化min/max操作或者對于myisam存儲引擎優(yōu)化count(*)操作沪猴,不必等到執(zhí)行階段再進行計算

更詳細的內容辐啄,請看我之前的文章:

最完整的Explain總結,SQL優(yōu)化不再困難

索引失效

  • 應該盡量全值匹配

  • 復合最佳左前綴法則(第一個索引不能掉运嗜,中間不能斷開

  • 不在索引列上做任何操作(計算壶辜、函數(shù)、類型轉換)會導致索引失效而轉向全表掃描

  • 存儲存引擎不能使用索引中范圍條件右邊的列

  • 盡量使用覆蓋索引(只訪問索引的查詢(索引列和查詢列一致))担租,減少select*

  • mysql在使用不等于(!=或者<>)的時候無法使用索引會導致全表掃描

  • is null砸民,is not null也會無法使用索引

  • like以統(tǒng)配符開頭

  • 字符串不加單引號

  • 少用or

order by優(yōu)化

  • 避免filesort,盡量在索引上進行排序奋救,遵照最佳左前綴原則

filesort有兩種排序:

  • 雙路排序:兩次磁盤掃描

  • 單路排序:一次性讀取保存在內存中阱洪,沒拉完的數(shù)據(jù)再次拉

  • 單路排序總體好于雙路排序

  • 優(yōu)化策略:1、增大sort_buffer_size參數(shù)的設置菠镇,2、增大max_length_for_sort_data參數(shù)的設置承璃,盡可能一次拿到內存

Case分析

案例一

in中參數(shù)太多

select * from goods_info where goods_status = ? and id in(11,22,33......)

in中id數(shù)據(jù)量比較多利耍,導致查詢的數(shù)據(jù)量比較大,這是一個比較常見的慢查詢類型盔粹,并且往往在業(yè)務數(shù)據(jù)量比較少的時候這條語句不是慢查隘梨;

因為參數(shù)傳進一個List集合,當參數(shù)比較多的時候舷嗡,可以采用在業(yè)務層把List集合拆分為多個長度較小的集合轴猎,分多次查詢,具體每一次拆長度為多少进萄,可能需要具體根據(jù)業(yè)務及數(shù)據(jù)量進行評估

我的解決辦法:業(yè)務代碼增加拆分集合操作捻脖,LIMIT_SIZE設置為1000

List<List<Integer>> partitionGoodsIdList = Lists.partition(goodsIdList, LIMIT_SIZE);

當SQL的查詢參數(shù)過多,我覺得可以考慮使用上述拆分的方式

案例二

返回的查詢結果過多

select from goods where goods_status = ? and poi_id = ?

解決辦法:將SQL修改為分頁查詢中鼠,并在業(yè)務代碼上修改為分頁查詢可婶,修改后的SQL語句如下:

select from goods where goods_status = 1 and poi_id = 11 and goods_id > 22 order by goods_id limit 2000

通過分頁的方式可以降低數(shù)據(jù)量,避免慢查詢援雇,但是會從而導致一次查詢請求矛渴,增加為多次查詢請求,對于limit的大小需要謹慎評估

案例三

order by慢查詢

SELECT * FROM order FORCE INDEX (orderid)  WHERE orderId = 11 AND status IN (0,22) ORDER BY id ASC ;

該SQL由于強制指定了使用orderId索引惫搏,但條件中并沒有orderId具温,導致產(chǎn)生全表掃描(type: ALL);

如下為問題SQL的執(zhí)行計劃:

556730e61e3b623d64b217ecf9d1ea2b.png

直接原因是最終傳給SQL查詢函數(shù)的參數(shù)筐赔,orderId沒有加入where子句铣猩,但forceindex一直生效

案例四

join慢查詢

select * from useract join userinfo order by useracct.id desc limit 11;

對sql進行explain可以發(fā)現(xiàn),因為忘寫了join的on條件川陆,這是掃全表sql剂习,如下圖:

c403c23d78f7201a3ae6bce2ad643b41.png

我們首先看type級別兩個表的級別都是ALL蛮位,說明該條語句沒有用到索引,做了全表掃描是最差的情況

優(yōu)化:

0f6c0ed499fc53549de49459a67f5501.png

案例五

不同索引嘗試

select id from goods_info where id > ? and activity_id = ? and goods_switch in(?+) limit ?
select id from goods_info where id > 123991510 and activity_id = 0 and goods_switch in (2,3) limit 1000

通過執(zhí)行計劃可知鳞绕,該語句走的是activity_id和主鍵的索引失仁,但是這種命中率比較低,大量的數(shù)據(jù)被goods_switch篩掉

解決辦法:在不確定最優(yōu)的索引的情況下们何,可以在測試環(huán)境下萄焦,分別添加不同的索引,觀察執(zhí)行計劃及語句的執(zhí)行時間冤竹。

嘗試強制走主鍵索引拂封,效果不佳;嘗試添加activity_id_id的聯(lián)合索引鹦蠕,效果不佳冒签;嘗試添加activity_id,goods_switch的聯(lián)合索引,問題解決钟病!

所以在不確定哪種索引是最優(yōu)時萧恕,可以嘗試建立不同的索引,觀察語句在不同索引情況下的執(zhí)行情況進行權衡肠阱。

案例六

MySQL選錯索引

select * from goods_info
where goods_source = ? and goods_switch != ? and id > ? order by id limit ?

select * from goods_info  
where goods_source = 2 and goods_switch != 8 and id > 12395070 order by id limit 1000

這條語句從語句本身猜測使用的是主鍵索引票唆,但是查看該語句的執(zhí)行計劃,發(fā)現(xiàn)走的索引是idx_goods_source屹徘,即走了goods_source的單列索引走趋!

解決辦法:修改SQL語句,強制走主鍵索引噪伊,查看執(zhí)行計劃簿煌,走了主鍵索引,查詢時間大大降低鉴吹。

正常情況下MySQL會選擇最優(yōu)的索引啦吧,但是有時候也會選錯,MySQL的優(yōu)化器會依據(jù)掃描行數(shù)拙寡、是否排序授滓,索引區(qū)分度來選擇最優(yōu)的索引,并且掃描行數(shù)不一定完成準確肆糕,只是MySQL的一個預估值

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末般堆,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子诚啃,更是在濱河造成了極大的恐慌淮摔,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件始赎,死亡現(xiàn)場離奇詭異和橙,居然都是意外死亡仔燕,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進店門魔招,熙熙樓的掌柜王于貴愁眉苦臉地迎上來晰搀,“玉大人,你說我怎么就攤上這事办斑⊥馑。” “怎么了?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵乡翅,是天一觀的道長鳞疲。 經(jīng)常有香客問我,道長蠕蚜,這世上最難降的妖魔是什么尚洽? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮靶累,結果婚禮上翎朱,老公的妹妹穿的比我還像新娘。我一直安慰自己尺铣,他們只是感情好,可當我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布争舞。 她就那樣靜靜地躺著凛忿,像睡著了一般。 火紅的嫁衣襯著肌膚如雪竞川。 梳的紋絲不亂的頭發(fā)上店溢,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天,我揣著相機與錄音委乌,去河邊找鬼床牧。 笑死,一個胖子當著我的面吹牛遭贸,可吹牛的內容都是我干的戈咳。 我是一名探鬼主播,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼壕吹,長吁一口氣:“原來是場噩夢啊……” “哼著蛙!你這毒婦竟也來了?” 一聲冷哼從身側響起耳贬,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤踏堡,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后咒劲,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體顷蟆,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡诫隅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了帐偎。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片逐纬。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖肮街,靈堂內的尸體忽然破棺而出风题,到底是詐尸還是另有隱情,我是刑警寧澤嫉父,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布沛硅,位于F島的核電站,受9級特大地震影響绕辖,放射性物質發(fā)生泄漏摇肌。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一仪际、第九天 我趴在偏房一處隱蔽的房頂上張望围小。 院中可真熱鬧,春花似錦树碱、人聲如沸肯适。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽框舔。三九已至,卻和暖如春赎婚,著一層夾襖步出監(jiān)牢的瞬間刘绣,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工挣输, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留纬凤,地道東北人。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓撩嚼,卻偏偏與公主長得像停士,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子完丽,可洞房花燭夜當晚...
    茶點故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內容