Mysql中的sql優(yōu)化實踐詳解
1、order by優(yōu)化
MySql中有2種排序方式Using filesort和Using index
Using index:通過有序索引順序掃描直接返回有序數據,這種情況即為using index,不需要額外排序,操作效率高。
Using filesort:通過表的索引或全表掃描,讀取滿足條件的數據行,然后在排序緩沖區(qū)sort bufferr中完成排序操作。它低層分為單路排序和雙路排序
- 單路排序:是一次性取出滿足條件行的所有字段,然后在sort buffer中進行排序;用trace工具可 以看到sort_mode信息里顯示 < sort_key, additional_fields >或者< sort_key, packed_additional_fields >
- 雙路排序:又叫回表排序模式,是首先根據相應的條件取出相應的排序字段和可以直接定位行數據的行 ID,然后在 sort buffer 中進行排序,排序完后需要再次取回其它需要的字段;用trace工具 可以看到sort_mode信息里顯示< sort_key, rowid >
- MySQL 通過比較系統(tǒng)變量 max_length_for_sort_data(默認1024字節(jié)) 的大小和需要查詢的字段總大小來 判斷哪種排序模式。 如果 字段的總長度小于max_length_for_sort_data ,那么使用 單路排序模式; 如果 字段的總長度大于max_length_for_sort_data ,那么使用 雙路排序模?式。
create index n_a_p_index on person (name, age, phone);
根據排序字段建立合適的索引,多字段排序時,也遵循最左前綴法則。
explain select name from person order by age
注意:判斷order by性能,我們主要根據Extra中的只來進行判斷,雖然key顯示我們使用了索引,但是他是因為我們查詢時使用了索引,而在Extra中,出現Using filesort,這說明我們排序沒有使用到索引。
優(yōu)化如下
explain select name from person order by name,age
盡量使用覆蓋索引。
explain select * from person order by name,age
多字段排序,一個升序一個降序,此時需要注意聯合索引在創(chuàng)建時的規(guī)則(ASC/DESC)。
explain select name,age from person order by name,age desc
重新創(chuàng)建索引
create index n_a_p_index on person (name asc , age DESC , phone asc); explain select name,age from person order by name,age desc
如果不可避免的出現filesort,大數據量排序時,可以適當增大排序緩沖區(qū)大小sort buffer size(默認256k)。
2、group by優(yōu)化
在分組操作時,可以通過索引來提高效率。分組操作時,索引的使用也是滿足最左前綴法則的。
create index n_a_p_index on person (name, age, phone); explain select age ,count(*) from person group by age
Using temporary,可以看到group by時,使用到了臨時表,性能不好,可以做如下優(yōu)化
explain select age ,count(*) from person where name = '小明' group by age
3、limit 優(yōu)化
一個常見又非常頭疼的問題就是limit2000000,10,此時需要MySQL排序前2000010記錄,僅僅返回2000000-2000010 的記錄,其他記錄丟棄,查詢排序的代價非常大。
select * from person limit 10000,10;
這段sql可以優(yōu)化成這樣
select * from person p inner join(select id from person limit 10000,10) temp on temp.id = p.id
4、count(*)優(yōu)化
EXPLAIN select count(1) from employees; EXPLAIN select count(id) from employees; EXPLAIN select count(name) from employees; EXPLAIN select count(*) from employees;
注意:根據某個字段count不會統(tǒng)計字段為null值的數據行
四個sql的執(zhí)行計劃一樣,說明這四個sql執(zhí)行效率應該差不多
- 字段有索引:count(*)≈count(1)>count(字段)>count(主鍵 id) //字段有索引,count(字段)統(tǒng)計走二級索引,二 級索引存儲數據比主鍵索引少,所以count(字段)>count(主鍵 id)
- 字段無索引:count(*)≈count(1)>count(主鍵 id)>count(字段) //字段沒有索引count(字段)統(tǒng)計走不了索引, count(主鍵 id)還可以走主鍵索引,所以count(主鍵 id)>count(字段)
count(1)跟count(字段)執(zhí)行過程類似,不過count(1)不需要取出字段統(tǒng)計,就用常量1做統(tǒng)計,count(字段)還需要取出 字段,所以理論上count(1)比count(字段)會快一點。
count() 是例外,mysql并不會把全部字段取出來,而是專門做了優(yōu)化,不取值,按行累加,效率很高,所以不需要用 count(列名)或count(常量)來替代 count()。 為什么對于count(id),mysql最終選擇輔助索引而不是主鍵聚集索引?因為二級索引相對主鍵索引存儲數據更少,檢索 性能應該更高,mysql內部做了點優(yōu)化(應該是在5.7版本才優(yōu)化)。
常見優(yōu)化方法
1、查詢mysql自己維護的總行數 對于myisam存儲引擎的表做不帶where條件的count查詢性能是很高的,因為myisam存儲引擎的表的總行數會被 mysql存儲在磁盤上,查詢不需要計算 對于innodb存儲引擎的表mysql不會存儲表的總記錄行數(因為有MVCC機制,后面會講),查詢count需要實時計算
2、show table status 如果只需要知道表總行數的估計值可以用如下sql查詢,性能很高
show table status like 'person'
3、將總數維護到Redis里 插入或刪除表數據行的時候同時維護redis里的表總行數key的計數值(用incr或decr命令),但是這種方式可能不準,很難 保證表操作和redis操作的事務一致性
4、增加數據庫計數表 插入或刪除表數據行的時候同時維護計數表,讓他們在同一個事務里操作
5、Join關聯優(yōu)化
mysql的表關聯常見有兩種算法 :Nested-Loop Join和 Block Nested-Loop Join 算法
嵌套循環(huán)連接
Nested-Loop Join(NLJ) 算法 一次一行循環(huán)地從第一張表(稱為驅動表)中讀取行,在這行數據中取到關聯字段,根據關聯字段在另一張表(被驅動 表)里取出滿足條件的行,然后取出兩張表的結果合集。
select * from t1 inner join t2 on t1.a= t2.a;
從執(zhí)行計劃中可以看到這些信息:
- 驅動表是 t2,被驅動表是 t1。先執(zhí)行的就是驅動表(執(zhí)行計劃結果的id如果一樣則按從上到下順序執(zhí)行sql);優(yōu)化器一般會優(yōu)先選擇小表做驅動表。所以使用 inner join 時,排在前面的表并不一定就是驅動表。
- 當使用left join時,左表是驅動表,右表是被驅動表,當使用right join時,右表時驅動表,左表是被驅動表, 當使用join時,mysql會選擇數據量比較小的表作為驅動表,大表作為被驅動表。
- 使用了 NLJ算法。一般 join 語句中,如果執(zhí)行計劃 Extra 中未出現 Using join buffer 則表示使用的 join 算法是 NLJ。
上面sql的大致流程如下:
- 從表 t2 中讀取一行數據(如果t2表有查詢過濾條件的,會從過濾結果里取出一行數據);
- 從第 1 步的數據中,取出關聯字段 a,到表 t1 中查找;
- 取出表 t1 中滿足條件的行,跟 t2 中獲取到的結果合并,作為結果返回給客戶端;
- 重復上面 3 步。
整個過程會讀取 t2 表的所有數據(掃描100行),然后遍歷這每行數據中字段 a 的值,根據 t2 表中 a 的值索引掃描 t1 表 中的對應行(掃描100次 t1 表的索引,1次掃描可以認為最終只掃描 t1 表一行完整數據,也就是總共 t1 表也掃描了100 行)。因此整個過程掃描了 200 行。
如果被驅動表的關聯字段沒索引,使用NLJ算法性能會比較低(下面有詳細解釋),mysql會選擇Block Nested-Loop Join 算法。
基于塊的嵌套循環(huán)連接
Block Nested-Loop Join(BNL)算法
把驅動表的數據讀入到 join_buffer 中,然后掃描被驅動表,把被驅動表每一行取出來跟 join_buffer 中的數據做對比。
select * from t1 inner join t2 on t1.b= t2.b;
Extra 中 的Using join buffer (Block Nested Loop)說明該關聯查詢使用的是 BNL 算法。 上面sql的大致流程如下:
- 把 t2 的所有數據放入到 join_buffer 中
- 把表 t1 中每一行取出來,跟 join_buffer 中的數據做對比
- 返回滿足 join 條件的數據
整個過程對表 t1 和 t2 都做了一次全表掃描,因此掃描的總行數為10000(表 t1 的數據總量) + 100(表 t2 的數據總量) = 10100。并且 join_buffer 里的數據是無序的,因此對表 t1 中的每一行,都要做 100 次判斷,所以內存中的判斷次數是 100 * 10000= 100 萬次。
這個例子里表 t2 才 100 行,要是表 t2 是一個大表,join_buffer 放不下怎么辦呢? join_buffer 的大小是由參數 join_buffer_size 設定的,默認值是 256k。如果放不下表 t2 的所有數據話,策略很簡單, 就是分段放。
比如 t2 表有1000行記錄, join_buffer 一次只能放800行數據,那么執(zhí)行過程就是先往 join_buffer 里放800行記錄,然 后從 t1 表里取數據跟 join_buffer 中數據對比得到部分結果,然后清空 join_buffer ,再放入 t2 表剩余200行記錄,再 次從 t1 表里取數據跟 join_buffer 中數據對比。所以就多掃了一次 t1 表。
被驅動表的關聯字段沒索引為什么要選擇使用 BNL 算法而不使用 Nested-Loop Join 呢? 如果上面第二條sql使用 Nested-Loop Join,那么掃描行數為 100 * 10000 = 100萬次,這個是磁盤掃描。 很顯然,用BNL磁盤掃描次數少很多,相比于磁盤掃描,BNL的內存計算會快得多。 因此MySQL對于被驅動表的關聯字段沒索引的關聯查詢,一般都會使用 BNL 算法。如果有索引一般選擇 NLJ 算法,有 索引的情況下 NLJ 算法比 BNL算法性能更高
對于關聯sql的優(yōu)化
- 關聯字段加索引,讓mysql做join操作時盡量選擇NLJ算法
- 小表驅動大表,寫多表連接sql時如果明確知道哪張表是小表可以用straight_join寫法固定連接驅動方式,省去 mysql優(yōu)化器自己判斷的時間
straight_join解釋:straight_join功能同join類似,但能讓左邊的表來驅動右邊的表,能改表優(yōu)化器對于聯表查詢的執(zhí) 行順序。
比如:select * from t2 straight_join t1 on t2.a = t1.a; 代表指定mysql選著 t2 表作為驅動表。
- straight_join只適用于inner join,并不適用于left join,right join。(因為left join,right join已經代表指 定了表的執(zhí)行順序)
- 盡可能讓優(yōu)化器去判斷,因為大部分情況下mysql優(yōu)化器是比人要聰明的。使用straight_join一定要慎重,因為部分情況下人為指定的執(zhí)行順序并不一定會比優(yōu)化引擎要靠譜。
對于小表定義的明確 在決定哪個表做驅動表的時候,應該是兩個表按照各自的條件過濾,過濾完成之后,計算參與 join 的各個字段的總數據 量,數據量小的那個表,就是“小表”,應該作為驅動表。
到此這篇關于Mysql中的sql優(yōu)化實踐詳解的文章就介紹到這了,更多相關sql優(yōu)化實踐內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
MYSQL根據分組獲取組內多條數據中符合條件的一條(實例詳解)
這篇文章主要介紹了MYSQL根據分組獲取組內多條數據中符合條件的一條,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-06-06