優化千萬級mysql 震驚 原來5.5k月薪的你也能

前言

■ 任務

慢sql優化改造,大表數據分頁機制性能改造 ,提升大數據查詢速度

■ 第一波操作

接到這個任務的第一想法是:有點意思,終於能有個mysql調優方面的機會。然後就是提取sql,盡情的擺弄它,接着就是發現了調整left join的順序,速度快了3分之1,再慢慢演變成面對搜索引擎優化,結果顯然是失望的。搜索後你會發現關於大數據mysql優化方面的資料、帖子如出一撇 ——加硬件、改配置、分區、分庫、分表、修改字段類型、用緩存分擔數據庫壓力、讀寫分離、冷熱數據分離、歷史數據清理。越搜越氣,越氣越select,越select越慢。。。第一次優化宣告失敗

■ 第二波操作

大佬們的帖子分區分庫分表其實也沒有毛病,但顯然這不是我想要的,一時之間我竟然也默認了mysql數據只能支持起幾百萬的數據,趕緊停住這個雪球的滾動吧。然後就是
報告經理:“mysql不行,數據太大了,優化不了,得清理歷史數據。”
報告經理:“分頁優化不了,limit越後面越慢我也沒辦法。”
報告經理:“其實limit第一頁也還可以,就是查詢count耗時太長。”
無奈,經理回覆:“肯定有辦法的,不然那些大公司怎麼處理。”
經理提出了一個方案:新增字段自增主鍵id,每次查詢都傳入上次查詢最後一個id,以縮小查詢範圍。 以我敏銳的程序嗅覺,這會給自己挖大坑,不假思索——
報告經理:“這個有好多where條件,那樣limit就不準了。”
其實就是亂七八糟胡塞了一個藉口,但是一時也唬住了經理。O(∩_∩)O哈哈~ 這個方案的實現就流落到另一個同事的手上。他給一個近千萬級的表加自增字段,果不其然數據庫崩。。。一下午的緊急處理。我到目前都沒去想這種方案可不可行,1、給大表新增字段造成的卡頓、2、傳參數需前端安卓協助。單單這兩點就讓我放棄這種思路。。。。第二次優化宣告失敗

■ 沉澱

我有一個習慣,就是有需求、問題一定要按已有的知識體系去解決,憑藉着不錯的邏輯思維,通常能憑藉着‘小刀’闖天下,‘切菜’也罷、‘宰牛’也罷。這也造成了不會及時的去更新自己的知識儲量。當然,遇到了棘手的問題後,我便會選擇沉澱一段,而後再嘗試能否解決。在b乎前輩的推薦下,我選擇了《高性能mysql第三版》。看着是有點頭疼,要在短短時間內消化完這麼一本經典的奇書壓根不現實,所以我選擇性的翻閱了優化部分,區區幾頁,如獲一師。

■ 第三波操作

以下爲一個5.5k的碼畜 對mysql近千萬級大表的優化操作 下文皆爲innodb引擎

優化招數

■ count查詢和數據查詢 分離

用分頁插件的話,查詢count是select count(1) from (原sql),分頁插件僅適用於一些不耗時的sql查詢既不耗時又節省開發時間。後端第一次查詢count,count()爲0直接返回空分頁,不執行具體查詢,讓無數據返回達到最快。

■ 延遲關聯

一般sql查詢有大量的left join,笛卡爾積,非常損耗時間。例如1000000* 100* 100,就非常恐怖,那爲什麼我們不能先查出所需要的10,再去* 100* 100呢?也就是10* 100* 100。哇!爽爆了~ 這個就是延遲關聯。例如

SELECT
	a.*, b.w,
	c.o,
	d.r,
	e.i
FROM
	a
LEFT JOIN b ON a.q = b.q
LEFT JOIN c ON a.q = c.q
LEFT JOIN d ON a.q = d.q
LEFT JOIN e ON a.q = e.q
WHERE
	e.kk = 1
LIMIT 1,10

延遲關聯後

SELECT
	a.*, b.w,
	c.o,
	d.r,
	e.i
FROM
	a
INNER JOIN (
	SELECT
		id
	FROM
		a
	LEFT JOIN e ON a.q = e.q
	WHERE
		e.kk = 1
	LIMIT 1,
	10
) pp USING (id)
LEFT JOIN b ON a.q = b.q
LEFT JOIN c ON a.q = c.q
LEFT JOIN d ON a.q = d.q

此處id作爲主鍵,有覆蓋索引不用回表查詢,先篩選所需要數據的id,再去關聯其它表顯然 比 先關聯再篩選妙出一個維度,優化效果也是肉眼可見。

■ 妙用索引(*)

一提到大表,第一想法就是建立索引,但是很多人建了索引後,索引生沒生效都不知道。索引纔是優化的重中之重,沒有索引爲基礎,什麼神操作都是白搭,當索引難以再調整後,纔會另尋他路。索引是啥?索引生效沒?索引怎麼建?

1.本人對索引的理解

《高性能mysql》圖5-2
《高性能mysql》圖5-2
看了上面這張圖,大致就對B-Tree索引有了一個瞭解。水平有限,怕誤人子弟,就不講這個概念了,講講我對B-Tree索引的感受。
例如:
組合索引(sex,age,time)

組合索引(sex,age,time)
sex age time name ...
1	20	2019 ...  ...
1	20	2020 ...  ...
1	20	2020 ...  ...
1	21	2018 ...  ...
2	20	2019 ...  ...
2	21  2017 ...  ...

第一層 1  										2
第二層 1(20) 1(21)								2(20) 	  2(21)
第三層 1(20)2019 1(20)2020 1(20)2020 1(21)2018	2(20)2019 2(21)2017

當我們
1、where sex=1的時候 (sexagetime
注:索引會定位到1的那一大堆
2、where sex=1 and age >20 and time=2019的時候 (sexagetime
3、where sex=1 and age in(21) and time=2019的時候 (sexagetime
4、where sex=1 and time=2019的時候 (sexagetime
5、where time=2019的時候 (sexagetime
注:索引最左原則指的是組合索引定義(sexagetime)按從左到右順序走,而不是sql語句where條件的先後順序。網上經常說到怎麼怎麼樣,索引不生效,走全表掃描,例如上例:並不是說time不生效就完全不走索引了,它還是走(sexage)的,這是一句很有歧義的表達,應該表達爲走全表掃描或全索引掃描。

說白了索引就是個按照各種字段有序化,起到一個加速縮小數據範圍的過程,組合索引通過一個又一個索引字段不斷地縮小數據範圍直到不能再縮小爲止,所以一般情況下越前列的索引區分度儘可能的高,索引纔會越漂亮。

COUNT(DISTINCT column_name)/COUNT(*)
這是一個計算索引區分度的公式,

區分度低的字段不該給索引?不,凡事皆有例外,在這也仍然適用,如果說上例sex,男女各100w,sex索引生效後,理想情況下查詢男或者女速度可以翻個倍,效果並不顯著,如果男有199w女1w,我們查詢女,這
在這裏插入圖片描述
這就是一個非常成功的索引,它將200w的數據範圍縮小到了1w。

2.解析索引生效情況

分析sql語句執行情況,大家應該都知道就是EXPLAIN SQL,解讀又是一門學問,我的學習方法就是不停的理解網上的explain例子,看多了也就看懂了。
我給個例子如下:
在這裏插入圖片描述
挑重點講,
id:
執行順序:從大到小,id相同從上到下。

possible_keys:
可能會使用的索引,索引有字段能生效,將被列出,但不一定被使用。

rows:
網上一般認爲這個是預計掃描行,而我則認爲這是經過索引篩選後大致還剩下的數量,這個值可以參考,不可盡信。(我的理解是:100w數據索引過濾後還剩1w,相當於濃縮了一下,同理濃縮後limit 10很快,limit 9990,10很慢,這是一個要點,後面要用到)

key(*):
被使用的索引,不一定在possible_keys中有。
注意:此例使用了組合索引fasts(tenant_id, del_flag, customer_type_id, create_time),即便索引只有tenant_id生效,key列顯示的也是fasts。所以看到key用到的是自己想要使用的索引名不要太高興,它有可能並沒有讓所有字段生效。
如果你對組合索引是否使用了所有列有所懷疑的話,可以嘗試建立例如——fasts1(tenant_id, del_flag, customer_type_id)fasts2(tenant_id, del_flag,)fasts3(tenant_id) 並使用 force index(fasts1) 。。。等等一個一個對比看查詢速度是否有所差距,就知道你建的索引是否全部字段都生效。

小結:
我個人認爲explain非常好用也是必須要用的,但是也不能盲目的相信它。幾乎沒有人提到過explain測出來的key有可能不是真實走的索引,我的線上優化過程中就遇到了這種情況,一旦誤導了就會對優化造成一定的麻煩,具體的情況已經忘了,現在已經不可復現,所以我在本地模擬了這種情況。
特殊情況:
來看下這條sql,沒有強制走哪個索引,explain的結果是走的索引fasts
在這裏插入圖片描述
那麼執行一下,可以看到耗時5秒多。
在這裏插入圖片描述
那麼調整一下sql,讓它強制走fasts索引,結果發現不到1秒
在這裏插入圖片描述
這說明了explain的分析不一定準確,它並沒有使用fasts索引,而卻列了出來。
然後,我發現我還有一個單列索引create_time。顯然這條sql語句存在問題,對create_time字段做了格式化,create_time索引不會生效,並且possible_keys裏也不存在這個索引,但是當我把create_time索引從數據庫中刪除後發現速度降回了1秒內,也就是說刪除後才真正的走了fasts索引。而沒刪除之前和強制走create_time索引效果一樣,走了索引,卻又沒有索引字段生效。
explain下強制走create_time
在這裏插入圖片描述
當然,我在線上所遇到的情況就沒這個清晰明瞭,而是更加複雜,當時好像是多個索引互相串味了,這個涉及到mysql查詢優化器的選擇,我。。。。。。也不太瞭解。

3.建立高性能、可複用的索引

上面所說的索引串用,而執行計劃又沒有找出來,就是由於線上遺留了前輩們建的很多無效的、重複的、不合理的複合索引,沒有使用,卻還保留在上面,索引太多了讓mysql的查詢優化器的選擇不再那麼靠譜。
經常有人說建儘可能少的索引,是因爲太多的索引影響了表的插入、刪除與更新的速度,這一點不可否認,不過還要加上非常重要的一點——避免對執行計劃也就是索引的選擇造成負面影響。
① 範圍索引必須留在最後一個,因爲範圍索引的後面索引字段不會生效。

② 儘可能的複用索引,我在數據庫經常看到有(a,b,c)索引,竟然還存在(a)(a,b)之類的索引,要知道(a,b,c)是包含這兩個的。

③ 用in可以巧妙的少建立索引。例如你有一個搜索條件性別 ,男爲1,女爲2,全部爲不查,有的人可能會建立這麼兩個索引(…,sex,…)和(…,…)來應對有性別搜索條件,和無性別搜索條件。然後你非要給性別設索引的話,可以只建(…,sex,…)索引,然後修改sql語句,當不過濾男女的時候把男女全列出來也就是

 select ... from a where ... and sex in(1,2)and...

這樣就可以巧妙的少建立索引。
④ 我列一些索引不生效的情況。
1、查詢(is null)(like ‘%…’)
2、字符串型字段例如:sex用字符串型,sex=1[索引不生效] sex=‘1’[生效]
3、對字段做了函數處理例如上例:date_format(a.create_time, ‘%Y-%m-%d’)=
4、不走索引更快時,查詢優化器選擇不走索引。時間範圍就是一個例子,有說選擇性小於17%時就不走了,具體未驗證,我是有試過時間範圍大到一定時不走索引,但我強制讓它走索引時,速度還會快挺多,所以我這個表述也不是很準確。
索引不生效的情況,這些可以網上查,非常多

改變分頁機制

索引很難調整了,還是不行就看看其它方法了。傳統的分頁就是需要查詢count,然後再查具體一頁的數據。我們公司的項目pc移動都是用同一套分頁,而pc那邊的查詢都有一個日期範圍篩選,只要日期範圍索引生效,查詢速度是非常快的,移動端就不一樣了,沒有日期篩選,速度非常的慢,而且移動端查詢count也是沒有必要的,省略掉查詢count,對性能提升效果最爲顯著。
保持一個優良的優化原則:後端改動,前端不動。
例子:移動端需要第3頁,5條數據。傳統我們是去除limit先查一遍count,有可能是100w,然後查詢具體數據limit 10,5,查詢count耗時好幾秒,limit具體數據是秒查,這樣如何優化呢?
我稱之爲——動態追加下一頁法
limit 10,6 爲 resultList ;返回count=(3-1)*5+resultList.size ;如果siz爲6返回的resultList romove掉最後一條數據
如果resultList.size返回1~5,說明這個查詢就是最後一頁了,這麼計算的count和傳統count一致,如果resultList.size返回6,說明查詢還可以有下一頁,移動端也能判斷是否有下一頁
,就這麼簡單的一個小改動,移動端本來查詢是卡頓住的變成了只剩下握手放手所損耗的時間。
當時間範圍索引沒生效的情況下,limit前面一些數據非常快,到後面就不太行了。可是在真實使用場景中,使用者只需要查看limit前面的那些數據,真要看歷史數據是可以通過時間範圍來定位的。PC端的Web其實也可以採用類似這種的動態追加的分頁查詢法,只提供用戶查詢當前頁附近的幾頁,這樣無論你有多大的數據,我都不怕了。

最後

路漫漫其修遠兮,mysql優化和分頁的優化還有很長的路要走,優化是要針對具體的業務而定的,所謂的優化招數都不會是萬金油,要想成爲強大的優化者,還是要繼續不斷的學習。過早的優化是萬惡之源,儘量地合理建立數據庫,減少被幾年後接手你的項目的人罵聲纔是硬道理。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章