提升mysql運行效率,增長併發,提升響應速度php
經過阿里雲給的慢查詢日誌excel,對耗時長,開銷大的sql語句進行優化,提高訪問速度服務器運行效率html
分析
阿里雲給的數據庫單日報表有如下字段mysql
分別是算法
根據阿里雲提供的慢查詢記錄,本次採用的優化策略以下:sql
查詢次數超過100次/日的高頻需求,按照最大查詢/總查詢用時最大,依次優化取得的優化收益最高.數據庫
執行次數: 1114 最大耗時: 7 解析最大行數: 348325 返回最大行數 4服務器
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 ........
執行次數: 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行.
能夠看到
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.測試執行效率:
能夠看到每百次運行時間已經從30s縮短到不到2秒,大大提升查詢mysql響應速度.
可是還有個問題,總共100 000的id,原來的語句查詢出的結果比較平衡,有過萬也有幾千,可是用這個語句後,老是出現小於一萬的id,結果在咱們預期以外.
修正機率誤差
增長一次對數據庫消耗不大的表查詢
# 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交互,數據庫
使用內鏈接 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」這個單詞,咱們確定須要定位到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毫秒的時間,顯然是個災難。下圖是計算機硬件延遲的對比圖,供你們參考:
硬件處理延遲