MySQL的查询性能优化——《深究MySQL》
更新中。。。。。。。
#寫在前面
mysql查詢緩慢的原因有哪些?
1.查詢語句是否請(qǐng)求了不必要的多余數(shù)據(jù)
2.
總結(jié)以上原因之后,優(yōu)化數(shù)據(jù)庫性能,就需從以下幾個(gè)方面著手:
1.
說明:下面的知識(shí)為零散的記錄,后期需要整理
一、Mysql優(yōu)化
1.優(yōu)化insert和update
1.1 批量插入:將多條要插入的數(shù)據(jù)合并成一條
- 合并后日志量(MySQL的binlog和innodb的事務(wù)讓日志)減少了,降低日志刷盤的數(shù)據(jù)量和頻率,從而提高效率;
- 通過合并SQL語句,減少網(wǎng)絡(luò)傳輸?shù)腎O;
- 通過合并SQL語句,減少SQL語句解析的次數(shù);
注意事項(xiàng):數(shù)據(jù)庫sql長(zhǎng)度是有限制,sql長(zhǎng)度過大會(huì)溢出報(bào)錯(cuò)。可通過max_allowed_packet配置修改,默認(rèn)是1M
1.2 事務(wù)插入:在開始插入前開啟事務(wù),插入完的提交事務(wù)
- 進(jìn)行一個(gè)INSERT操作時(shí),MySQL內(nèi)部會(huì)建立一個(gè)事務(wù),在事務(wù)內(nèi)才進(jìn)行真正插入處理操作。通過使用事務(wù)可以減少創(chuàng)建事務(wù)的消耗;
注意事項(xiàng):事務(wù)日志大小也有限制,過大會(huì)刷磁盤,導(dǎo)致性能下降。可通過innodb_log_buffer_size配置修改
1.3 主鍵順序插入:前面插入數(shù)據(jù)和主鍵和后插入數(shù)據(jù)的主鍵是有序的
-
如果有序,新插入的記錄直接定位在索引的最后面,索引的定位效率會(huì)很高,也且對(duì)索引調(diào)整也較小。
-
如果無序,新插入的記錄可能要定位到索引中間,索引定位效率下降,而這時(shí)還需要B+tree進(jìn)行分裂合并等處理,會(huì)消耗比較多計(jì)算資源,數(shù)據(jù)量較大時(shí)會(huì)有頻繁的磁盤操作。
參考:https://www.2cto.com/database/201707/654686.html
2. in 和 exsits的區(qū)別和效率
參考:https://www.cnblogs.com/emilyyoucan/p/7833769.html
總結(jié)如下:
select a.* from A a where exists(select 1 from B b where a.id=b.id)
exists()適合B表比A表數(shù)據(jù)大的情況
select * from A where id in(select id from B)
in()適合B表比A表數(shù)據(jù)小的情況
2.not in 和not exists
如果查詢語句使用了not in ,那么內(nèi)外表都進(jìn)行全表掃描,沒有用到索引;而not extsts 的子查詢依然能用到表上的索引。所以無論那個(gè)表大,用not exists都比not in要快。
3. 要建索引的MySQL字段盡量設(shè)置為NOT NULL
含有空值的列很難進(jìn)行查詢優(yōu)化,而且表索引是不會(huì)存儲(chǔ)NULL值的,所以如果索引的字段可以為NULL,索引的效率會(huì)下降很多。
因?yàn)樗鼈兪沟盟饕⑺饕慕y(tǒng)計(jì)信息以及比較運(yùn)算更加復(fù)雜。
你應(yīng)該用0、一個(gè)特殊的值或者一個(gè)空串代替空值。
4.負(fù)向查詢不能使用索引
反例:select name from user where id not in (1,3,5);
正例:select name from user where id in (2,4,6);
5. 前導(dǎo)模糊查詢不能使用索引
反例:select name from user where name like '%zhang ’
正例:select name from user where name like ‘zhang%’
6. 不對(duì)索引字段進(jìn)行計(jì)算(不然索引會(huì)失效)
反例:select name from user where FROM_UNIXTIME(create_time) < CURDATE();
正例:select name from user where create_time < FROM_UNIXTIME(CURDATE());
7.保證參數(shù)的數(shù)據(jù)類型與庫一致(不然會(huì)作強(qiáng)制轉(zhuǎn)換)
庫里 phone字段為varchar
反例:select name from user where phone=18810503732;
正例:select name from user where telno=‘18810503732’
8. 避免復(fù)合索引失效
說明:user表的username 和phone 字段創(chuàng)建了復(fù)合索引,且username在前面。
能利用到索引的SQL:
select username from user where username=‘zhangsan’ and phone =‘18810503732’
select username from user where phone =‘18810503732’ and username=‘zhangsan’
select username from user where username=‘zhangsan’
不能利用到索引的SQL:
select username from user where phone =‘18810503732’
這個(gè)類似于電話本,你先以用戶名字作了目錄,再按電話號(hào)作的目錄。如果你先查電話的目錄的話,肯定是不行的。你只能先安用戶名字去搜索。
非同步方法會(huì)不會(huì)受同步方法的影響?
SQL語句中or的使用方法注意事項(xiàng)
二、優(yōu)化特定類型的查詢
1. 優(yōu)化count查詢
1.盡量使用count(*)。當(dāng)然也可以嘗試索引覆蓋,此時(shí)最好是主鍵。
count(columnName) 雖然也可以求出行數(shù),但是它有一個(gè)致命的問題,那就是不統(tǒng)計(jì)為NULL的列。所以如果用列名代替*求行數(shù)的方式可能會(huì)出現(xiàn)統(tǒng)計(jì)錯(cuò)誤。
2.優(yōu)化關(guān)聯(lián)查詢
1.確保在ON或USING的關(guān)聯(lián)順序中第二個(gè)表的關(guān)聯(lián)列有索引
例如,當(dāng)A表和B表的關(guān)聯(lián)順序是A、B,即SQL語句為select * from A left join B on A.cid = B.cid 時(shí),需要保證在B表的cid列表創(chuàng)建索引,而A表的cid列有無索引對(duì)性能沒有影響。
2. 盡量讓group by 和order by 的表達(dá)式中只涉及到一個(gè)表中的列
分組的字段在一個(gè)表,排序的字段又在另外一個(gè)表中,這樣就很難利用到索引。
3.優(yōu)化子查詢
1. 盡量使用關(guān)聯(lián)查詢代替子查詢
子查詢會(huì)創(chuàng)建臨時(shí)表,而臨時(shí)表又沒有任何索引
2. 子查詢優(yōu)化的基本思路,就是消除嵌套層次,把子查詢與父查詢放在同一個(gè)層次去執(zhí)行。
實(shí)現(xiàn)步驟:
把子查詢的FROM子句中的表對(duì)象,與上層的父FROM子句中的表對(duì)象做JOIN
再把子查詢的WHERE子句中的條件表達(dá)式,與上層的父WHERE子句中的條件表達(dá)式用“AND”操作符連接
4.優(yōu)化group by
1. 不關(guān)心順序時(shí),盡量用order by NULL 避免group by的默認(rèn)排序(從而避免了文件排序)
group by子句如果沒有指定排序列,則會(huì)自動(dòng)根據(jù)分組列進(jìn)行默認(rèn)升序排序,從而導(dǎo)致了文件排序。
有個(gè)一般人不會(huì)用的方式,那就是直接在 group by 字段A 后面加上ASC或DESC關(guān)鍵字,使得分組結(jié)果按分組列(字段A)進(jìn)行升序或降序排序。
2. group by要求返回的所有字段,要么出現(xiàn)在聚合函數(shù)(avg、sum、count、max、min等)中,要么出現(xiàn)Group By后面作為分組依據(jù),不然高版本的mysql會(huì)報(bào)錯(cuò)。這是因?yàn)榉纸M后,沒有通過聚合函數(shù)聚合的多行數(shù)據(jù)不知道怎么存放在一行了,高版本時(shí)可通過配置文件設(shè)置忽略,從而只選擇一行來存。
3. 注意:group by是先排序再分組,且是分組后再聚合。
4.1優(yōu)化group by with rollup
可通過explain查看執(zhí)行計(jì)劃,決定是否加with rollup
with rollup表示需要做超級(jí)聚合,即可統(tǒng)計(jì)分組結(jié)果中每組數(shù)據(jù)分組前的某些值,如我們需要統(tǒng)計(jì)各部門員工工資的平均值
select depId, avg(money) from user group by depId with rollup;
需要注意的是,用了with rollup后不能再使用order by關(guān)鍵字,但可以直接在分組字段后加asc或desc
參考:https://blog.csdn.net/id19870510/article/details/6254358
5.優(yōu)化limit
1.盡量利用索引覆蓋做“延遲關(guān)聯(lián)"(延遲關(guān)聯(lián)也可以優(yōu)化關(guān)聯(lián)查詢的limit子句)
在做分頁查詢時(shí),如果遇到像limit 2000,20這樣偏移量大的查詢時(shí),mysql會(huì)實(shí)際查詢2020條數(shù)據(jù),然后拋棄前面2000條數(shù)據(jù),只返回最后20條。這樣做的代價(jià)非常高。
limit 2000,20 這樣的分而之所以會(huì)慢,是因?yàn)閛ffset。offset會(huì)導(dǎo)致mysql掃描大量不需要的行然后拋棄掉。所以平時(shí)我們看到 limit 1 這種寫法是與offset無關(guān)的,也就是說這種寫法不會(huì)很慢。順便提一下,limit 2000,20 是等價(jià)于 limit 20 offset 2000的。
6.優(yōu)化union查詢
1.如果不需要對(duì)查詢結(jié)果去重的話,盡量使用union all 而不是union。
union后沒跟all 時(shí),會(huì)自動(dòng)給臨時(shí)表加上distinct做唯一性檢查,從而降低性能。
2.巧用 limit
(SELECT * from sys_user where id < 2000 order by office_id ) UNION (SELECT * from sys_user WHERE id >2000 order by office_id ) order by office_id desc limit 20;
兩個(gè)括號(hào)里的子查詢都會(huì)將各自的數(shù)據(jù)全部放在一個(gè)臨時(shí)表中,然后從臨時(shí)表中取出20條數(shù)據(jù),這時(shí)可以將每個(gè)子查詢都用上limit 20 :
(SELECT * from sys_user where id < 2000 order by office_id limit 20) UNION (SELECT * from sys_user WHERE id >2000 order by office_id limit 20 ) order by office_id desc limit 20;
這樣一來,臨時(shí)表中就只有40條數(shù)據(jù)了。需要注意的是,從臨時(shí)表取數(shù)據(jù)的順序是不確定的,所以要想按自定義的順序取,必須在全局加上order by ,如上面SQL上的紅色字。
7.優(yōu)化 in 子查詢
1.將in子查詢用innor join改寫成關(guān)聯(lián)查詢
IN的子查詢?cè)趯?shí)際執(zhí)行的時(shí)候會(huì)被mysql自動(dòng)改寫成較緩慢的查詢(mysql有時(shí)候也會(huì)自以為是的,哈哈),如下查詢
select * from film where film_id IN(select film_id from film_actor where actor_id = 1)
會(huì)被改成寫:
select * from film where film_id exists (select film_id from film_actor where actor_id = 1 and film_actor.film_id = film.film_id )
從改寫后的語句可以看到,子查詢里面涉及到外部表 film 的film_id字段了,所以mysql會(huì)先對(duì)外部表film進(jìn)行全表掃描,通過explain執(zhí)行計(jì)劃也可以看到。
優(yōu)化后的寫法為:
select * from film inner join film_actor ON film.film_id = film_actor.film_id WHERE film_actor.actor_id = 1;
8.優(yōu)化 MAX()、MIN()
1. 通過order by 和limit來優(yōu)化
如下,我們需要獲取最大值和最小值(前提是id值有索引)
優(yōu)化前:
最小值:select min(id) from actor;
最大值:select max(id) from actor;
優(yōu)化后:
最小值:select id from actor order by id asc limit 1;
最大值:select id from actor order by id desc limit 1;
從explain可以看出,雖然優(yōu)化前和優(yōu)化后都用到了索引,但是min和max函數(shù)進(jìn)行了大量的掃描,而優(yōu)化后只掃描了一行。
9.優(yōu)化OR查詢
用UNION替換OR (僅適用于索引列)
對(duì)索引列使用OR將造成全表掃描。注意,該規(guī)則只針對(duì)多個(gè)索引列有效. 如果有column沒有被索引, 查詢效率可能會(huì)因?yàn)槟銢]有選擇OR而降低。
在下面的例子中, LOC_ID 和REGION上都建有索引:
高效: select log_id , log_desc , region from log where log_id = 10 UNION
select log_id , log_desc , region from log where region= “update”
低效: select log_id , log_desc , region from log where log_id = 10 OR region = “update”
如果你堅(jiān)持要用OR, 那就建議你將返回記錄最少的索引列寫在最前面。
總結(jié)
以上是生活随笔為你收集整理的MySQL的查询性能优化——《深究MySQL》的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 补充spring事务传播性没有考虑的几种
- 下一篇: 三分钟了解Mysql的表级锁——《深究M