這篇文章主要講解了“如何解決千萬級(jí)數(shù)據(jù)表選錯(cuò)索引導(dǎo)致的線上慢查詢事故”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“如何解決千萬級(jí)數(shù)據(jù)表選錯(cuò)索引導(dǎo)致的線上慢查詢事故”吧!
發(fā)展壯大離不開廣大客戶長(zhǎng)期以來的信賴與支持,我們將始終秉承“誠(chéng)信為本、服務(wù)至上”的服務(wù)理念,堅(jiān)持“二合一”的優(yōu)良服務(wù)模式,真誠(chéng)服務(wù)每家企業(yè),認(rèn)真做好每個(gè)細(xì)節(jié),不斷完善自我,成就企業(yè),實(shí)現(xiàn)共贏。行業(yè)涉及成都木托盤等,在網(wǎng)站建設(shè)公司、營(yíng)銷型網(wǎng)站建設(shè)、WAP手機(jī)網(wǎng)站、VI設(shè)計(jì)、軟件開發(fā)等項(xiàng)目上具有豐富的設(shè)計(jì)經(jīng)驗(yàn)。
故障描述
在7月24日11點(diǎn)線上某數(shù)據(jù)庫突然收到大量告警,慢查詢數(shù)超標(biāo),并且引發(fā)了連接數(shù)暴增,導(dǎo)致數(shù)據(jù)庫響應(yīng)緩慢,影響業(yè)務(wù)。看圖表慢查詢?cè)诟叻暹_(dá)到了每分鐘14w次,在平時(shí)正常情況下慢查詢數(shù)僅在兩位數(shù)以下,如下圖:

趕緊查看慢SQL記錄,發(fā)現(xiàn)都是同一類語句導(dǎo)致的慢查詢(隱私數(shù)據(jù)例如表名,我已經(jīng)隱去):
select * from sample_table where 1 = 1 and (city_id = 565) and (type = 13) order by id desc limit 0, 1
看起來語句很簡(jiǎn)單,沒什么特別的。但是每個(gè)執(zhí)行的查詢時(shí)間達(dá)到了驚人的44s。

簡(jiǎn)直聳人聽聞,這已經(jīng)不是“慢”能形容的了...
接下來查看表數(shù)據(jù)信息,如下圖:

可以看到表數(shù)據(jù)量較大,預(yù)估行數(shù)在83683240,也就是8000w左右,「千萬數(shù)據(jù)量的表」。
大致情況就是這樣,下面進(jìn)入排查問題的環(huán)節(jié)。
問題原因排查
首先當(dāng)然要懷疑會(huì)不會(huì)該語句沒走索引,查看建表DML中的索引:
KEY `idx_1` (`city_id`,`type`,`rank`), KEY `idx_log_dt_city_id_rank` (`log_dt`,`city_id`,`rank`), KEY `idx_city_id_type` (`city_id`,`type`)
請(qǐng)忽略idx_1和idx_city_id_type兩個(gè)索引的重復(fù),這都是歷史遺留問題了。
「可以看到是有idx_city_id_type和idx_1索引的」,我們的查詢條件是city_id和type,這兩個(gè)索引都是能走到的。
但是,我們的查詢條件真的只要考慮city_id和type嗎?(機(jī)智的小伙伴應(yīng)該注意到問題所在了,先往下講,留給大家思考)
既然有索引,接下來就該看該語句實(shí)際有沒有走到索引了,MySQL提供了Explain可以分析SQL語句。Explain 用來分析 SELECT 查詢語句。
Explain比較重要的字段有:
select_type : 查詢類型,有簡(jiǎn)單查詢、聯(lián)合查詢、子查詢等
key : 使用的索引
rows : 預(yù)計(jì)需要掃描的行數(shù)
更多詳細(xì)Explain介紹可以參考:MySQL 性能優(yōu)化神器 Explain 使用分析
我們使用Explain分析該語句:
select * from sample_table where city_id = 565 and type = 13 order by id desc limit 0,1
得到結(jié)果:

可以看出,雖然possiblekey有我們的索引,但是最后走了主鍵索引。而表是千萬級(jí)別,「并且該查詢條件最后實(shí)際是返回的空數(shù)據(jù)」,也就是MySQL在主鍵索引上實(shí)際檢索時(shí)間很長(zhǎng),導(dǎo)致了慢查詢。
我們可以使用force index(idx_city_id_type)讓該語句選擇我們?cè)O(shè)置的聯(lián)合索引:
select * from sample_table force index(idx_city_id_type) where ( ( (1 = 1) and (city_id = 565) ) and (type = 13) ) order by id desc limit 0, 1
這次明顯執(zhí)行的飛快,分析語句:

實(shí)際執(zhí)行時(shí)間0.00175714s,走了聯(lián)合索引后,不再是慢查詢了。
問題找到了,總結(jié)下來就是:「MySQL優(yōu)化器認(rèn)為在limit 1的情況下,走主鍵索引能夠更快的找到那一條數(shù)據(jù),并且如果走聯(lián)合索引需要掃描索引后進(jìn)行排序,而主鍵索引天生有序,所以優(yōu)化器綜合考慮,走了主鍵索引。實(shí)際上,MySQL遍歷了8000w條數(shù)據(jù)也沒找到那個(gè)天選之人(符合條件的數(shù)據(jù)),所以浪費(fèi)了很多時(shí)間。」
MySQL索引選擇原理
優(yōu)化器索引選擇的準(zhǔn)則
MySQL一條語句的執(zhí)行流程大致如下圖,而「查詢優(yōu)化器」則是選擇索引的地方:

引用參考文獻(xiàn)一段解釋:
首先要知道,選擇索引是MySQL優(yōu)化器的工作。
而優(yōu)化器選擇索引的目的,是找到一個(gè)最優(yōu)的執(zhí)行方案,并用最小的代價(jià)去執(zhí)行語句。在數(shù)據(jù)庫里面,掃描行數(shù)是影響執(zhí)行代價(jià)的因素之一。掃描的行數(shù)越少,意味著訪問磁盤數(shù)據(jù)的次數(shù)越少,消耗的CPU資源越少。
「當(dāng)然,掃描行數(shù)并不是唯一的判斷標(biāo)準(zhǔn),優(yōu)化器還會(huì)結(jié)合是否使用臨時(shí)表、是否排序等因素進(jìn)行綜合判斷。」
總結(jié)下來,優(yōu)化器選擇有許多考慮的因素:「掃描行數(shù)、是否使用臨時(shí)表、是否排序等等」
我們回頭看剛才的兩個(gè)explain截圖:


走了「主鍵索引」的查詢語句,rows預(yù)估行數(shù)1833,而強(qiáng)制走「聯(lián)合索引」行數(shù)是45640,并且Extra信息中,顯示需要Using filesort進(jìn)行額外的排序。所以在不加強(qiáng)制索引的情況下,「優(yōu)化器選擇了主鍵索引,因?yàn)樗X得主鍵索引掃描行數(shù)少,而且不需要額外的排序操作,主鍵索引天生有序。」
rows是怎么預(yù)估出來的
同學(xué)們就要問了,為什么rows只有1833,明明實(shí)際掃描了整個(gè)主鍵索引啊,行數(shù)遠(yuǎn)遠(yuǎn)不止幾千行。實(shí)際上explain的rows是MySQL「預(yù)估」的行數(shù),「是根據(jù)查詢條件、索引和limit綜合考慮出來的預(yù)估行數(shù)。」
MySQL是怎樣得到索引的基數(shù)的呢?這里,我給你簡(jiǎn)單介紹一下MySQL采樣統(tǒng)計(jì)的方法。
為什么要采樣統(tǒng)計(jì)呢?因?yàn)榘颜麖埍砣〕鰜硪恍行薪y(tǒng)計(jì),雖然可以得到精確的結(jié)果,但是代價(jià)太高了,所以只能選擇“采樣統(tǒng)計(jì)”。
采樣統(tǒng)計(jì)的時(shí)候,InnoDB默認(rèn)會(huì)選擇N個(gè)數(shù)據(jù)頁,統(tǒng)計(jì)這些頁面上的不同值,得到一個(gè)平均值,然后乘以這個(gè)索引的頁面數(shù),就得到了這個(gè)索引的基數(shù)。
而數(shù)據(jù)表是會(huì)持續(xù)更新的,索引統(tǒng)計(jì)信息也不會(huì)固定不變。所以,當(dāng)變更的數(shù)據(jù)行數(shù)超過1/M的時(shí)候,會(huì)自動(dòng)觸發(fā)重新做一次索引統(tǒng)計(jì)。
在MySQL中,有兩種存儲(chǔ)索引統(tǒng)計(jì)的方式,可以通過設(shè)置參數(shù)innodb_stats_persistent的值來選擇:
設(shè)置為on的時(shí)候,表示統(tǒng)計(jì)信息會(huì)持久化存儲(chǔ)。這時(shí),默認(rèn)的N是20,M是10。
設(shè)置為off的時(shí)候,表示統(tǒng)計(jì)信息只存儲(chǔ)在內(nèi)存中。這時(shí),默認(rèn)的N是8,M是16。
由于是采樣統(tǒng)計(jì),所以不管N是20還是8,這個(gè)基數(shù)都是很容易不準(zhǔn)的。
我們可以使用analyze table t命令,可以用來重新統(tǒng)計(jì)索引信息。但是這條命令生產(chǎn)環(huán)境需要聯(lián)系DBA,所以我就不做實(shí)驗(yàn)了,大家可以自行實(shí)驗(yàn)。
索引要考慮 order by 的字段
為什么這么說?因?yàn)槿绻疫@個(gè)表中的索引是city_id,type和id的聯(lián)合索引,那優(yōu)化器就會(huì)走這個(gè)聯(lián)合索引,因?yàn)樗饕呀?jīng)做好了排序。
更改limit大小能解決問題?
把limit數(shù)量調(diào)大會(huì)影響預(yù)估行數(shù)rows,進(jìn)而影響優(yōu)化器索引的選擇嗎?
答案是會(huì)。
我們執(zhí)行l(wèi)imit 10
select * from sample_table where city_id = 565 and type = 13 order by id desc limit 0,10

圖中rows變?yōu)榱?8211,增長(zhǎng)了10倍。如果使用limit 100,會(huì)發(fā)生什么?

優(yōu)化器選擇了聯(lián)合索引。初步估計(jì)是rows還會(huì)翻倍,所以優(yōu)化器放棄了主鍵索引。寧愿用聯(lián)合索引后排序,也不愿意用主鍵索引了。
為何突然出現(xiàn)異常慢查詢
問:這個(gè)查詢語句已經(jīng)在線上穩(wěn)定運(yùn)行了非常長(zhǎng)的時(shí)間,為何這次突然出現(xiàn)了慢查詢?
答:以前的語句查詢條件返回結(jié)果都不為空,limit1很快就能找到那條數(shù)據(jù),返回結(jié)果。而這次代碼中查詢條件實(shí)際結(jié)果為空,導(dǎo)致了掃描了全部的主鍵索引。
解決方案
知道了MySQL為何選擇這個(gè)索引的原因后,我們就可以根據(jù)上面的思路來列舉出解決辦法了。
主要有兩個(gè)大方向:
鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)
強(qiáng)制指定索引
干涉優(yōu)化器選擇
強(qiáng)制選擇索引:force index
就像上面我最開始的操作那樣,我們直接使用force index,讓語句走我們想要走的索引。
select * from sample_table force index(idx_city_id_type) where ( ( (1 = 1) and (city_id = 565) ) and (type = 13) ) order by id desc limit 0, 1
這樣做的優(yōu)點(diǎn)是見效快,問題馬上就能解決。
缺點(diǎn)也很明顯:
高耦合,這種語句寫在代碼里,會(huì)變得難以維護(hù),如果索引名變化了,或者沒有這個(gè)索引了,代碼就要反復(fù)修改。屬于硬編碼。
很多代碼用框架封裝了SQL,force index()并不容易加進(jìn)去。
「我們換一種辦法,我們?nèi)ヒ龑?dǎo)優(yōu)化器選擇聯(lián)合索引。」
干涉優(yōu)化器選擇:增大limit
通過增大limit,我們可以讓預(yù)估掃描行數(shù)快速增加,比如改成下面的limit 0, 1000
SELECT * FROM sample_table where city_id = 565 and type = 13 order by id desc LIMIT 0,1000
這樣就會(huì)走上聯(lián)合索引,然后排序,但是這樣強(qiáng)行增長(zhǎng)limit,其實(shí)總有種面向黑盒調(diào)參的感覺。我們還有更優(yōu)美的解決方案嗎?
干涉優(yōu)化器選擇:增加包含order by id字段的聯(lián)合索引
我們這句慢查詢使用的是order by id,但是我們卻沒有在聯(lián)合索引中加入id字段,導(dǎo)致了優(yōu)化器認(rèn)為聯(lián)合索引后還要排序,干脆就不太想走這個(gè)聯(lián)合索引了。
我們可以新建city_id,type和id的聯(lián)合索引,來解決這個(gè)問題。
這樣也有一定的弊端,比如我這個(gè)表到了8000w數(shù)據(jù),建立索引非常耗時(shí),而且通常索引就有3.4個(gè)g,如果無限制的用索引解決問題,可能會(huì)帶來新的問題。表中的索引不宜過多。
干涉優(yōu)化器選擇:寫成子查詢
還有什么辦法?我們可以用子查詢,在子查詢里先走city_id和type的聯(lián)合索引,得到結(jié)果集后在limit1選出第一條。
但是子查詢使用有風(fēng)險(xiǎn),一版DBA也不建議使用子查詢,會(huì)建議大家在代碼邏輯中完成復(fù)雜的查詢。當(dāng)然我們這句并不復(fù)雜啦~
Select * From sample_table Where id in (Select id From `newhome_db`.`af_hot_price_region` where (city_id = 565 and type = 13)) limit 0, 1
還有很多解決辦法...
SQL優(yōu)化是個(gè)很大的工程,我們還有非常多的辦法能夠解決這句慢查詢問題,這里就不一一展開了。留給大家做為思考題了。
感謝各位的閱讀,以上就是“如何解決千萬級(jí)數(shù)據(jù)表選錯(cuò)索引導(dǎo)致的線上慢查詢事故”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對(duì)如何解決千萬級(jí)數(shù)據(jù)表選錯(cuò)索引導(dǎo)致的線上慢查詢事故這一問題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是創(chuàng)新互聯(lián),小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!
分享題目:如何解決千萬級(jí)數(shù)據(jù)表選錯(cuò)索引導(dǎo)致的線上慢查詢事故
URL地址:http://chinadenli.net/article18/jigjdp.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站制作、品牌網(wǎng)站制作、品牌網(wǎng)站建設(shè)、微信公眾號(hào)、定制開發(fā)、網(wǎng)頁設(shè)計(jì)公司
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)