Mysql 慢查詢優化實踐

Mysql 慢查詢優化實踐

目標:

提升mysql運行效率,增長併發,提升響應速度php

方案:

經過阿里雲給的慢查詢日誌excel,對耗時長,開銷大的sql語句進行優化,提高訪問速度服務器運行效率html

實踐:

分析 阿里雲給的數據庫單日報表有如下字段mysql

  • Create Time DBName
  • MySQL Total Execution Counts
  • MySQL Total Execution Times
  • Max Execution Time
  • Max Lock Time
  • Parse Total Row Counts
  • Parse Max Row Count
  • Return Total Row Counts
  • Return Max Row Count
  • SQL Text
  • hard ware- latencies

分別是算法

  • 建立時間
  • 數據庫名
  • mysql總執行數目
  • muysql總執行耗時
  • 最大執行耗時
  • 最大鎖耗時
  • 解析總行數統計
  • 解析最大行數
  • 返回總計行數
  • 返回最大行數
  • sql語句
  • 硬件延遲

根據阿里雲提供的慢查詢記錄,本次採用的優化策略以下:sql

查詢次數超過100次/日的高頻需求,按照最大查詢/總查詢用時最大,依次優化取得的優化收益最高.數據庫

第一條語句:

執行次數: 1114 最大耗時: 7 解析最大行數: 348325 返回最大行數 4 #執行次數: 1114 最大耗時: 7 解析最大行數: 348325 返回最大行數 4bash

select id from appname_m_members where yiku_id = :1
複製代碼

能夠看出,這個簡單的sql不該該有這麼大的解析行數,甚至最高要七秒鐘.
初步判斷沒有在yiku_id這個字段加索引的可能性最大.如今咱們須要尋求各類辦法來驗證下咱們的猜想服務器

分析
explain select id from appname_m_members where yiku_id = 1;
複製代碼

能夠看到的確是沒有給yiku_id增長索引.微信

索引的特色
對於查詢操做能迅速縮小查詢範圍,減小row的數量,指數級提升查詢速度點
對於寫操做,由於須要維護索引的變動,有必定開銷.若是遇到大量併發寫入,會有負面影響.
在這個表用來記錄咱們微信用戶和應用id的關係,因此讀的操做較之寫操做更多,因此可以增長索引.
網絡

#增長索引

ALTER TABLE `appname_m_members` 
ADD INDEX `yiku_id` (`yiku_id`) ;
複製代碼

嘗試增長索引以後,再次分析語句的執行

結果:

匹配範圍 rows 從32w 下降到1

能夠看到type從all的全表掃描變成ref的單個行的索引訪問,rows從全表32w降爲1,說明添加索引對這條語句產生了巨大效果.

第二條語句:

執行次數: 482 最大耗時: 15 解析最大行數: 764383 返回最大行數: 482

#執行次數: 482 最大耗時: 15 解析最大行數: 764383 返回最大行數: 482
select fullname as username , linkphone as userphone , 
`userimage` , `nickname` , `hospitalname` , `partmentname` , 
`doctortitle` , `iscertification` , `fullname` 
from `users` 
where `useruuid` = '597_f66e1cb79341cedf6f24aaf01fde8611' limit 1;
複製代碼
分析:

對其增長索引:
#增長索引
ALTER TABLE `users` 
ADD INDEX `useruuid` (`useruuid`);
複製代碼

直接將掃描範圍(rows)從72w降到了1,提高明顯

結果:

匹配範圍 rows 從72w 下降到1

第三條語句:

執行次數: 820 最大耗時: 10 解析最大行數: 167214 返回最大行數 1

#執行次數: 820 最大耗時: 10 解析最大行數: 167214 返回最大行數 1
select count ( postingid ) as postnum from mediposting 
where isaudit != :1
and isgoodcase = :2
and postsection = :3
複製代碼
分析:

改變sql語句的順序,按照最左原則修改以下

select count(postingid) as postnum from mediposting 
where postsection = 1
and isgoodcase = 1
and isaudit != 1
複製代碼
結果:

主要使用的是 postsection 做爲索引來統計總數,這部分無需優化.

##### 第四條語句:
執行次數: 482 最大耗時: 15 解析最大行數: 764383 返回最大行數: 482
##執行次數: 410 最大耗時: 10 解析最大行數:348325 返回最大行數 1
........
複製代碼
結果: 語句過長(2017個字符),嵌套了邏輯,暫不優化
第五條語句:

執行次數: 659 最大耗時: 6 解析最大行數:215115 返回最大行數 659

## 執行次數: 659 最大耗時: 6 解析最大行數:215115 返回最大行數 659
select `medigooddoc`.`docid` , `medigooddoc`.`docname` ,
 `medigooddoc`.`doctitle` , `medigooddoc`.`docimgurl` ,
  `medigooddoc`.`docdep` , `medigooddoc`.`dochospital` ,
   ( initalscore+effectevaladd ) as `effectval` 
   from `medigooddoc` 
   where ( ( initalscore+effectevaladd ) > 80 ) 
   order by rand ( ) limit 1 ;
複製代碼
分析:

rand()函數放在order by後面會被執行屢次,優化方式: 求出隨機id後,取得對應記錄

select `medigooddoc`.`docid` , `medigooddoc`.`docname` ,
 `medigooddoc`.`doctitle` , `medigooddoc`.`docimgurl` ,
 `medigooddoc`.`docdep` , `medigooddoc`.`dochospital` ,
 ( initalscore+effectevaladd ) as `effectval` 
 from `medigooddoc` 
 where (initalscore+effectevaladd)  > 80
 and docid > ( 
     RAND() * (
         (SELECT MAX(docid) FROM `medigooddoc`) 
         -
         (SELECT MIN(docid) FROM `medigooddoc`)
    )
    +
    (SELECT MIN(docid) FROM `medigooddoc`) 
 )
 order by `docid` limit 1;
複製代碼

優化前語句:

能夠看到掃描範圍很大(rows) 120 770行.

能夠看到

  • 查詢範圍最小(rows) 1
  • 獲取最大值/最小值是直接從mysql查詢優化器返回數據(extra).mysql文檔中有如下解釋:

The query contained only aggregate functions (MIN(), MAX()) that were all resolved using an index, or COUNT(*) for MyISAM, and no GROUP BY clause. The optimizer determined that only one row should be returned.

測試執行效率:

  • 執行10次 a: 2 941 ms b: 168 ms
  • 執行50次 a: 14 441 ms b: 828 ms
  • 執行100次a: 29 126 ms b: 1 645 ms

能夠看到每百次運行時間已經從30s縮短到不到2秒,大大提升查詢mysql響應速度. 可是還有個問題,總共100 000的id,原來的語句查詢出的結果比較平衡,有過萬也有幾千,可是用這個語句後,老是出現小於一萬的id,結果在咱們預期以外.

修正機率誤差

方案1:

增長一次對數據庫消耗不大的表查詢

# php
$round = select max(docid) as max,min(docid) as min from medigooddoc;
$rand = rand($round['min'],$round['max']);
複製代碼
# sql
select `medigooddoc`.`docid` , `medigooddoc`.`docname` ,
 `medigooddoc`.`doctitle` , `medigooddoc`.`docimgurl` ,
 `medigooddoc`.`docdep` , `medigooddoc`.`dochospital` ,
 ( initalscore+effectevaladd ) as `effectval` 
 from `medigooddoc` 
 where (initalscore+effectevaladd)  > 80
 and docid > $rand
 order by `docid` limit 1;
複製代碼

這樣的問題是:會多產生一個sql交互,數據庫

方案2:

使用內鏈接 join 優化

#可用一
select `docid` ,`docname`,
 `doctitle` , `docimgurl` ,
 `docdep` , `dochospital` ,
 ( initalscore+effectevaladd ) as `effectval` 
 from `medigooddoc` as t1 
 join (
     select rand() * (select max(docid) from `medigooddoc`) 
     as rand
 ) as t2 
 where (t1.initalscore+t1.effectevaladd)  > 80
 and `t1`.`docid` >= t2.rand
 order by `docid` limit 1;
複製代碼

可是這樣有一個問題:並非徹底平均落到每條記錄上,由於記錄並非連續的

修正機率 rand * 數量範圍,這樣機率平均到整張表存在的記錄中.

select `docid` ,`docname`,
 `doctitle` , `docimgurl` ,
 `docdep` , `dochospital` ,
 ( initalscore+effectevaladd ) as `effectval` 
 from `medigooddoc` as t1 
 join (
     select rand() * 
     (
         (select max(docid) from `medigooddoc`)
         -
         (select min(docid) from `medigooddoc`)
     )
     +
     (select min(docid) from `medigooddoc`)  
     as rand
 ) as t2 
 where (t1.initalscore+t1.effectevaladd)  > 80
 and `t1`.`docid` >= t2.rand
 order by `docid` limit 1;
複製代碼

綜合來講,由於方案1 產生了更多的數據庫交互,由於咱們的數據庫是另外一臺服務器,網絡鏈接開銷是比較大的,額外的查詢也會在高併發的時刻對數據庫產生更大壓力.

而方案2採用內鏈接的方式,僅須要一次數據庫交互就能完成,最大最小值也是直接由mysql查詢器返回,減小了種種數據庫性能開銷.故採用爲最佳方案..

結果:

使用mysql保存的表結構信息替代了order rand()的低效率查詢.

深刻理解:

第六條語句:

執行次數: 729 最大耗時: 4秒 解析最大行數:130898 返回最大行數 2

select `medigooddoc`.`docid` , `medigooddoc`.`yikuid` 
from `medigooddoc` 
where ( yikuid = 597725 or yikuid = -597725 );
複製代碼
分析:

優化方案:

字段yikuid加索引

ALTER TABLE `medigooddoc`
ADD INDEX `YiKuID` (`YiKuID`);
複製代碼

再次執行explain分析

結果:

匹配範圍 rows 從8.3w 下降到1

第七條語句

執行次數: 474 最大耗時: 5秒 解析最大行數:261797 返回最大行數 1

select `medigooddoc`.`docid` , `medigooddoc`.`docname` ,
 `medigooddoc`.`doctitle` , `medigooddoc`.`docimgurl` 
 from `medigooddoc` order by rand ( ) limit 1;
複製代碼
分析

方案

將獲取一條隨機記錄 由order by rand() limit 1 改成 內鏈接方式

select `docid`, `docname`,
  `doctitle` , `docimgurl` 
 from `medigooddoc` as  t1 
 inner join 
 (
     select rand()  *
     (
         (select MAX(docid) from `medigooddoc`)
         -
         (select MIN(docid) from `medigooddoc`)
     )
     +
     (select MIN(docid) from `medigooddoc`)
     as rand
 ) as t2 
 on t1.docid >= t2.rand
 order by docid limit 1;
複製代碼

再次執行explain分析

結果

用mysql存儲的表信息替代了效率低下的order by rand()

第八條語句

執行次數: 136 最大耗時: 7秒 解析最大行數:301880 返回最大行數 1

select `searchrecords`.`searchid` , `searchrecords`.`searchnum` 
from `searchrecords` 
where ( searchtype = 0 ) and ( userid = 14 ) 
and ( searchmsg = '碳酸鈣D3' );
複製代碼
分析

方案

索引的目的是爲了縮小查詢範圍,經過文字內容的前三個字區分,經過userid進行區分,能夠獲得範圍更精確的語句執行

ALTER TABLE searchrecords ADD INDEX searchmsg (searchmsg(5));
ALTER TABLE searchrecords ADD INDEX userid (userid);
複製代碼

經過文本前5個字創建索引來區分範圍後,範圍縮小到28個記錄

再經過用戶ID創建索引,進一步縮小範圍,僅須要查找1條記錄

分析索引對寫入的影響 表主要用來記錄用戶搜索的高頻詞,主要的寫操做時更新統計字段,這兩個新增索引的字段並不會頻繁更新,故索引開銷不大.

結果

匹配範圍從 29w 縮小到 1

第九條語句
select `projects`.`id` , `projects`.`guid` , 
`projects`.`getittime` , `projects`.`keywords` ,
 `projects`.`barcode` as `num` , `projects`.`goodcasedep` ,
  `projects`.`bingshi` , `pictures`.* 
  from `projects` 
  inner join `pictures` on projects.guid = pictures.projectid 
  and pictures.filetype = :1 
  where ( islock != :2 ) and ( isgoodcase = :3 ) 
  and ( ( goodcasedep like :4 or goodcasedep like :5 
  or goodcasedep like :6 or goodcasedep like :7 
  or goodcasedep like :8 or goodcasedep like :9 
  or goodcasedep like :10 or goodcasedep like :11 
  or goodcasedep like :12 or goodcasedep like :13 
  or goodcasedep like :14 or goodcasedep like :15 
  or goodcasedep like :16 or goodcasedep like :17 
  or goodcasedep like :18 or goodcasedep like :19 
  or goodcasedep like :20 or goodcasedep like :21 
  or goodcasedep like :22 or goodcasedep like :23 
  or goodcasedep like :24 or goodcasedep like :25 
  or goodcasedep like :26 or goodcasedep like :27 
  or goodcasedep like :28 or goodcasedep like :29 
  or goodcasedep like :30 or goodcasedep like :31 
  or goodcasedep like :32 or goodcasedep like :33 
  or goodcasedep like :34 or goodcasedep like :35 
  or goodcasedep like :36 or goodcasedep like :37 
  or goodcasedep like :38 or goodcasedep like :39 
  or goodcasedep like :40 or goodcasedep like :41 ) ) 
  order by rand ( ) limit :42
複製代碼
結果:

暫不修改:超過字節限制

第十條語句

執行次數: 145 最大耗時: 2秒 解析最大行數:130898 返回最大行數 1

select `medigooddoc`.`isfollow` , `medigooddoc`.`isconsult` ,
 `medigooddoc`.`isphone` , `medigooddoc`.`isprivate` 
 from `medigooddoc` where ( yikuid =  694 );
複製代碼
分析:

方案

增長索引

ALTER TABLE `medigooddoc` ADD INDEX YiKuID(`YiKuID`);
複製代碼

再次執行explain分析

結果

匹配範圍從12w縮小到1

第十一條

執行次數: 148 最大耗時: 3秒 解析最大行數:74616 返回最大行數 30

select `magazinearticle`.`articleid` , 
`magazinearticle`.`articletitle` , 
`magazinearticle`.`article_publishtime` ,
 `magazinearticle`.`articlepicpath` ,
  `magazinearticle`.`articleurl` ,
   `magazinearticle`.`articlenum` ,
    `magazinearticle`.`perid` ,
     `magazinearticle`.`article_originallink` ,
      `magazinearticle`.`islink` from `magazinearticle` 
      where ( logicdel = 0 ) and ( perid != 60 ) 
      order by `article_publishtime` desc limit 1,30;
複製代碼
分析:

方案:

因爲是讀多寫少的文章表,增長索引適用這類場景,提升查詢響應速度.

ALTER TABLE `magazinearticle` ADD INDEX 
article_publishtime(`article_publishtime`);
複製代碼

再次執行explain分析

結果:

匹配範圍 rows 從2w縮小到59

深刻理解:

explain type的不一樣種類

類型 含義
類型 含義
system 表只有一行
const 表最多隻有一行匹配,通用用於主鍵或者惟一索引比較時
eq_ref 每次與以前的表合併行都只在該表讀取一行,這是除了system,const以外最好的一種,特色是使用=,並且索引的全部部分都參與join且索引是主鍵或非空惟一鍵的索引
ref 若是每次只匹配少數行,那就是比較好的一種,使用=或<=>,能夠是左覆蓋索引或非主鍵或非惟一鍵
fulltext 全文搜索
ref_or_null 與ref相似,但包括NULL
index_merge 表示出現了索引合併優化(包括交集,並集以及交集之間的並集),但不包括跨表和全文索引。
這個比較複雜,目前的理解是合併單表的範圍索引掃描(若是成本估算比普通的range要更優的話)
unique_subquery 在in子查詢中,就是value in (select...)把形如「select unique_key_column」的子查詢替換。
PS:因此不必定in子句中使用子查詢就是低效的!
index_subquery 同上,但把形如」select non_unique_key_column「的子查詢替換
range 常數值的範圍
index a.當查詢是索引覆蓋的,即全部數據都可從索引樹獲取的時候(Extra中有Using Index)
b.以索引順序從索引中查找數據行的全表掃描(無 Using Index);
c.若是Extra中Using Index與Using Where同時出現的話,則是利用索引查找鍵值的意思;
d.如單獨出現,則是用讀索引來代替讀行,但不用於查找
all 全表掃描
第十二條

執行次數: 135 最大耗時: 3秒 解析最大行數:78395 返回最大行數 0

select distinct userid from weekhosnominate 
where userid = 351211 and datatype = 4
複製代碼
分析

方案
ALTER TABLE `weekhosnominate` ADD INDEX UserID(`UserID`);
複製代碼

再次執行explain分析

結果

匹配範圍 rows 從1w縮小到288

第十三條

執行次數: 110 最大耗時: 2秒 解析最大行數:87693 返回最大行數 1

select `inspectioninfo`.`itemmsg` from `inspectioninfo` 
where ( itemid in ( 30 ,31 ) and itemtype = 0
and inspectionid = 109 ) limit 1 ;
複製代碼
分析

方案:

增長索引

ALTER TABLE `inspectioninfo` ADD INDEX 
InspectionID(`InspectionID`);
複製代碼

再次執行explain分析

結果

匹配範圍 rows 從 5w 縮小到 13

第十四條語句

執行次數: 103 最大耗時: 2秒 解析最大行數:78395 返回最大行數 0

select `weekhosnominate`.`id` from `weekhosnominate` 
where ( userid = 351211 );
複製代碼
分析:

方案:

經過給字段 userid 創建索引來區分,縮小範圍

ALTER TABLE `weekhosnominate` ADD INDEX UserID(UserID) ;
複製代碼

再次執行explain分析能夠發現, 經過索引 userid 將範圍由全表掃描的近萬到索引指向的數十條記錄.

結果:

匹配範圍 rows 從 9k 縮小到 288

深刻理解:

mysql結構

mysql索引原理

索引目的
索引的目的在於提升查詢效率,能夠類比字典,若是要查「mysql」這個單詞,咱們確定須要定位到m字母,而後從下往下找到y字母,再找到剩下的sql。若是沒有索引,那麼你可能須要把全部單詞看一遍才能找到你想要的,若是我想找到m開頭的單詞呢?或者ze開頭的單詞呢?是否是以爲若是沒有索引,這個事情根本沒法完成?

索引原理
除了詞典,生活中隨處可見索引的例子,如火車站的車次表、圖書的目錄等。它們的原理都是同樣的,經過不斷的縮小想要得到數據的範圍來篩選出最終想要的結果,同時把隨機的事件變成順序的事件,也就是咱們老是經過同一種查找方式來鎖定數據。

數據庫也是同樣,但顯然要複雜許多,由於不只面臨着等值查詢,還有範圍查詢(>、<、between、in)、模糊查詢(like)、並集查詢(or)等等。數據庫應該選擇怎麼樣的方式來應對全部的問題呢?咱們回想字典的例子,能不能把數據分紅段,而後分段查詢呢?最簡單的若是1000條數據,1到100分紅第一段,101到200分紅第二段,201到300分紅第三段……這樣查第250條數據,只要找第三段就能夠了,一會兒去除了90%的無效數據。但若是是1千萬的記錄呢,分紅幾段比較好?稍有算法基礎的同窗會想到搜索樹,其平均複雜度是lgN,具備不錯的查詢性能。但這裏咱們忽略了一個關鍵的問題,複雜度模型是基於每次相同的操做成原本考慮的,數據庫實現比較複雜,數據保存在磁盤上,而爲了提升性能,每次又能夠把部分數據讀入內存來計算,由於咱們知道訪問磁盤的成本大概是訪問內存的十萬倍左右,因此簡單的搜索樹難以知足複雜的應用場景。

磁盤IO與預讀
前面提到了訪問磁盤,那麼這裏先簡單介紹一下磁盤IO和預讀,磁盤讀取數據靠的是機械運動,每次讀取數據花費的時間能夠分爲尋道時間、旋轉延遲、傳輸時間三個部分,尋道時間指的是磁臂移動到指定磁道所須要的時間,主流磁盤通常在5ms如下;旋轉延遲就是咱們常常據說的磁盤轉速,好比一個磁盤7200轉,表示每分鐘能轉7200次,也就是說1秒鐘能轉120次,旋轉延遲就是1/120/2 = 4.17ms;傳輸時間指的是從磁盤讀出或將數據寫入磁盤的時間,通常在零點幾毫秒,相對於前兩個時間能夠忽略不計。那麼訪問一次磁盤的時間,即一次磁盤IO的時間約等於5+4.17 = 9ms左右,聽起來還挺不錯的,但要知道一臺500 -MIPS的機器每秒能夠執行5億條指令,由於指令依靠的是電的性質,換句話說執行一次IO的時間能夠執行40萬條指令,數據庫動輒十萬百萬乃至千萬級數據,每次9毫秒的時間,顯然是個災難。下圖是計算機硬件延遲的對比圖,供你們參考:

硬件處理延遲

REFER:

MySQL索引原理及慢查詢優化

相關文章
相關標籤/搜索