雖然DBA給咱們建了不少索引,但沒有經驗的開發人員每每只看錶結構,不太關注索引和如何利用索引提升SQL執行速度,下面羅列一些經驗,讓你寫的SQL更加高效的利用索引mysql
在作實驗以前最好先想想索引的數據結構與排序,以及索引工做的方式,才能更快的理解與記住這些點,不然在工做中遇到更復雜的狀況,極可能就不會處理了正則表達式
查詢字段與某一個索引的結構徹底一致,包括字段和順序(徹底匹配)sql
create index idx_tv_v_user_u_a_g on tb_v_user(user_name,age,gendor); select * from tb_v_user where user_name = '1F7sJ' and age = 44 and gendor = 1;
查詢條件與索引的列與順序徹底一致,執行計劃掃描類型爲ref。有興趣的同窗能夠試試分別將user_name、age、gendor查詢條件刪除,看看執行計劃是什麼狀況數據庫
不在列上作任何附加操做,好比加函數數據結構
select * from tb_v_user where left(user_name,5) = '1F7sJ' and age=44 and gendor = 1;
這裏的SQL和上面的SQL執行結果是如出一轍,寫法上區別是在user_name上使用了left函數,看看執行計劃oracle
變全表掃描了socket
若是在實際生產中,確實須要經過函數來處理列再查詢怎麼辦?函數
1.若是你的MySQL是5.7以上版本,能夠建立一個虛擬的列,而後在該列上建立函數索引。oracle能夠直接建立函數索引,比這個簡單一些工具
alter table tb_v_user add column user_name_t varchar as (left(user_name,5)) stored; 或者 alter table tb_v_user add column user_name_t varchar as (left(user_name,5)) virtual; alter table tb_v_user add key idx_tb_v_user_ut(user_name_t);
2.若是你的MySQL是5.7如下版本oop
現有表上冗餘一個字段,浪費存儲,最重要的是還得修改之前的代碼
用觸發器往另一張表同步數據,使整個事務的流程更長,增長了不穩定性
查詢時索引中的某一列已是range掃描了,那麼索引中後面列的掃描就沒法再使用索引了
select * from tb_v_user where user_name = '1F7sJ' and age > 30 and gendor = 1;
雖然查詢條件和索引的列徹底一致,可是由於在age使用了range掃描,後面gendor的條件是沒法再使用索引的
儘可能使用覆蓋索引
select user_name,age,gendor from tb_v_user where user_name = '1F7sJ' and age = 44 and gendor = 1;
SQL語句只是將*替換成了指定的列,並且這些列都在索引中存在。像這種狀況,及時gendor查詢條件不能再走索引的掃描,可是數據的讀取仍是能夠走索引的,這樣能夠減小IO的次數
減小使用不等於、is null 、is not null、or,會觸發全表掃描
這個應該很明確吧,都屬於範圍類型的查詢,固然無法走索引掃描咯
like 使用時優先考慮右則使用通配符,若是最左側也須要通配符,儘可能走索引全掃描(index)
select user_id,user_name,age,gendor from tb_v_user where user_name like '%1F7sJ%';
select user_id,user_name,age,gendor from tb_v_user where user_name like '1F7sJ%';
全表(all)掃描變成了範圍(range)掃描
注:雖然like也是範圍掃描,可是like畢竟可以最終確認出一個指定的結果集,所以跟在like後面的查詢條件是能夠用到索引的。這一點和'>'出現的範圍掃描不同
若是需求必需要根據列中間的部分作模糊查詢,那麼儘可能使用索引覆蓋,減小IO次數
select user_name,age,gendor from tb_v_user where user_name like '%1F7sJ%';
就是SQL查詢的列在索引列中存在
若是列是字符串,必定以及千萬要加單引號,若是這個都不記得的話,估計你的實習期就過不了
表某些列的值雖然都是阿拉伯數字,可是列類型倒是varchar,此時就很容易在寫SQL的時候當成數值類型,這樣寫會致使沒辦法使用到索引,好比給值加上單引號
查詢條件+排序+分組的時候,也儘可能符合徹底匹配原則,特別是排序要緊隨查詢條件後面的列,不然會出現Using filesort
總結:上面雖然羅列了很多內容,最終概況起來是:利用現有索引,再改變需求的狀況下,儘可能使用索引覆蓋,保持列的類型不變(不加函數、指明類型),減小模糊查詢。若是現有索引知足不了,而需求又必須實現,那麼就把DBA和需求負責人一塊兒拉過來討論討論吧,找一個折中的辦法,工做中通常碼農和維護人員都會屈服於需求人員的淫威
顧名思義就是用數據量小的表與數據量大的表作匹配,減少IO和比較次數。比較常見的三種場景以下,他們三者功能很相似,均可以用來作過濾,可是join經過豐富的關聯能實現不少集合操做。詳情能夠掉頭看看我以前的帖子
先準備點數據
create table tb_v_s_user select user_name,count(1) as cnt from tb_v_user group by user_name having COUNT(1)>5; #由於後面的測試是基於user_name作的關聯,爲了減小IO次數,我使用了索引覆蓋優化方式 create index idx_tb_v_s_user_u on tb_v_s_user(user_name);
1.in
select user_name from tb_v_user where user_name in(select user_name from tb_v_s_user);
先從tb_v_s_user的索引中讀取出全部的user_name,而後在循環和tb_v_user索引中的user_name作比較(ref掃描),若是我把兩個表的順序換一下會如何
select user_name from tb_v_s_user where user_name in(select user_name from tb_v_user);
看來MySQL的優化器作了自動優化,並無傻傻的先加載大表tb_v_user。再使用in的時候,MySQL會自動以小表驅動大表的優化策略優化SQL
兩種編寫方式估算出來的rows都等於94=(93+1)
但我仍是推薦第一種寫法
2.exists
先將in的第一種寫法翻譯過來看看
select * from tb_v_user a where exists ( select 1 from tb_v_s_user b where a.user_name = b.user_name);
雖然都用到了索引,可是走了大表的索引全掃描,明顯IO的次數比in的第一種寫法會多不少不少,效率必定會查不少,這種方式就成了大表驅動小表,固然不是咱們想要的結果。接下來翻譯in的第二種寫法
select a.user_name from tb_v_s_user a where exists ( select 1 from tb_v_user b where a.user_name = b.user_name);
這下就變成了咱們想要的結果,小表驅動大表,而後估算出來的rows=94
3.join
大表寫在前:
select a.user_name from tb_v_user a join tb_v_s_user b on a.user_name = b.user_name;
小表寫在前:
select a.user_name from tb_v_s_user a join tb_v_user b on a.user_name = b.user_name;
能夠看出執行計劃並無改變,和用in同樣,MySQL仍是很智能的。
總結:in exists join三種方式均可以實現過濾,可是若是對原理掌握不是很好的話,可使用in 和 join,不過如今智能的東西不必定每次都對,所以在實際開發中,最好仍是看看執行計劃,不要太相信優化器。
調優SQL:
create index idx_tv_v_user_u_a_g on tb_v_user(user_name,age,gendor);
select user_name,age,gendor from tb_v_user where user_name = '1F7sJ' order by age,gendor;
select user_name,age,gendor from tb_v_user where user_name > '1F7sJ' order by age,gendor;
有上面的全部,先想一想這兩個誰會出現Using filesort,爲何它會出現,另一個不會出現
答案是第二種有Using filesort出現,第一種沒有,爲何喃?由於第一種最終查詢結果集user_name確定全都是1F7sJ,這一列再也不須要排序,而第二種user_name是不肯定的,且order by 子句與索引列不是前綴匹配(看下圖),可是結果集又包含了user_name,因此MySQL沒有已排好序的數據可拿,只能再作一次排序。
先來一個簡單一點的:
再來一個複雜一點的:
例子1:select user_name from tb_v_user order by user_name[,age,gendor]
列子2:select user_name from tb_v_user order where user_name = '1F7sJ' order by user_name[,age,gendor]
它們兩個都不會產生Using filesort
若是上面的還不能理解,但願下面的圖(來自於尚硅谷-周陽)能夠有用
注意:這裏查詢的列都指定爲了user_name而不是'*'。若是改爲'*'可能就演示不出來效果
調優參數:
1.若是查詢語句有order by ,千萬當心使用select *
2.聯繫DBA調整sort_buffer_size參數
3.聯繫DBA調整max_length_for_sort_data
雖然能夠調大緩衝區的大小,可是針對大表,而又不可避免對大數據量的排序,並且仍是filesort,遇到這種狀況,建議在系統負載比較悠閒的時間段作,或者乾脆去離線的大數據方式解決
首先得先明白什麼是分組,若是不懂的,出門左轉百度一下數據庫的分組是什麼意思,分組以後數據長什麼樣,能夠作哪些統計操做等。
分組其實就是先排序,而後對分組字段作統計操做,因此要優化分組,必須得先優化排序。所以排序的優化策略是徹底適用的,不一樣的是要減小寫分組過濾條件(having),能用where代替的就使用where,比較 having 後面跟的都是統計函數
雖然咱們在測試環境對每一個SQL的執行計劃都作過驗證,可是並不表明在生產環境MySQL就乖乖的按照測試環境的執行計劃來跑,由於數據量徹底不同。因此當有新需求上線、數據割接應該打開MySQL的慢查詢日誌(通常數據量大的系統還會按期作慢查詢日誌分析),收集查詢時間大於long_query_time的SQL
查看慢查詢日誌相關參數
show variables like '%slow_query_log%'; +------------------------------------+----------------------------------+ | Variable_name | Value | +------------------------------------+----------------------------------+ | slow_query_log | OFF | | slow_query_log_always_write_time | 10.000000 | | slow_query_log_file | /var/lib/mysql/hadoop00-slow.log | | slow_query_log_timestamp_always | OFF | | slow_query_log_timestamp_precision | second | | slow_query_log_use_global_control | | +------------------------------------+----------------------------------+ 6 rows in set
查看慢查詢日誌記錄數
mysql> show global status like '%Slow_queries%' +---------------+-------+ | Variable_name | Value | +---------------+-------+ | Slow_queries | 1 | +---------------+-------+ 1 row in set
因此若是執行這個命令看到value很大,那麼你的系統必定是很慢的
臨時設置:
下面的設置只對當前會話有效,時間單位是秒
set global slow_query_log=1; set long_query_time=3; select user_id,count(1) from tb_vote group by user_id;
到主機目錄下查看有沒有將SQL打印出來
[root@hadoop00 /var/lib/mysql]$ cat hadoop00-slow.log /usr/sbin/mysqld, Version: 5.6.24-72.2 (Percona Server (GPL), Release 72.2, Revision 8d0f85b). started with: Tcp port: 3306 Unix socket: /var/lib/mysql/mysql.sock Time Id Command Argument # Time: 180612 7:19:47 # User@Host: root[root] @ [192.168.245.1] Id: 10 # Schema: mydb Last_errno: 0 Killed: 0 # Query_time: 3.603351 Lock_time: 0.000180 Rows_sent: 400000 Rows_examined: 1200000 Rows_affected: 0 # Bytes_sent: 10800115 use mydb; SET timestamp=1528813187; select user_id,count(1) from tb_vote group by user_id;
確實打印出來了
永久設置:
打開/etc/my.cnf文件,添加以下配置,時間單位是秒
#還能夠slow_query_log=true slow_query_log=1 long_query_time=3 #還能夠設置日誌文件目錄和名稱 slow_query_log_file=/home/hadoop/data/mysql/mysql-slow.log
通常開發人員是沒有權限去修改這些的,須要聯繫DBA配合,並且長時間打開這對性能也是有必定影響的
DBA或者項目經理能夠藉助MySQL提供的mysqldumpslow工具對慢查詢日誌作分析
mysqldumpslow參數
s:按那種方式排序
c:訪問次數
l:鎖定時間
r:返回記錄
t:查詢時間
al:平均鎖定時間
ar:平均返回記錄數
at:平均查詢時間
t:返回前面多少條的數據
g:後半搭配一個正則表達式,忽略大小寫
舉例:
1.獲得返回記錄最多的10個SQL
mysqldumpslow -s r -t 10 /home/hadoop/data/mysql/1.log
2.獲得訪問次數最多的10個SQL
mysqldumpslow -s c -t 10 /home/hadoop/data/mysql/2.log
3.獲得按照時間排序的前10條含有左鏈接的SQL
mysqldumpslow -s t -t 10 -g "left join" /home/hadoop/data/mysql/3.log
4.還能夠結果管道(|)和more使用
mysqldumpslow -s r -t 10 /home/hadoop/data/mysql/4.log | more
這個確實很簡單,對於通常開發人員估計不會讓你幹這種事情,都是DBA和項目經理來幹,須要對mysqldumpslow工具使用熟練,並且有多年的經驗,真的該如何去分析。新手拿到日誌文件,並且知道每一個參數的意思,可能短期也拿不出比較切實有效的統計結果