一、如何選擇合適的列建立索引
- 在where從句,group by從句,order by從句,on從句中出現的列
- 索引字段越小越好
- 離散度大的列放到聯合索引的前面
explain select * from payment where staff_id=2 and customer_id=584; -- 思考: index(staff_id,customer_id)好?還是index(customer_id,staff_id)好? select count(distinct customer_id),count(distinct staff_id) from payment; +-----------------------------+--------------------------+ | count(distinct customer_id) | count(distinct staff_id) | +-----------------------------+--------------------------+ | 599 | 2 | +-----------------------------+--------------------------+ -- 由於customer_id的離散度更大(重復率小,可選擇性更大),所以應該使用 index(customer_id,staff_id)
二、索引的維護
1、重復及冗余索引
-- 冗余索引是指多個索引的前綴列相同,或是在聯合索引中包含了主鍵的索引。如下:key(name,id)就是一 個冗余索引 -- 可以刪除冗余索引,達到優化效果。 create table test( id int not null primary key, name varchar(10) not null, key(name,id) )engine=innodb;
2、檢查重復及冗余索引
使用pt-duplicate-key-checker工具檢查重復及冗余索引,安裝如下:
wget http://www.percona.com/downloads/percona-toolkit/2.2.4/percona-toolkit-2.2.4.tar.gz tar -xzvf percona-toolkit-2.2.4.tar.gz cd percona-toolkit-2.2.4 perl Makefile.PL make && make install #如果報錯(Can't locate Time/HiRes.pm in @INC (@INC contains....) yum -y install perl-Time-HiRes #如果報錯: Cannot connect to MySQL because the Perl DBD::mysql module is not installed or not found. yum -y install perl-DBD-mysql
使用:
pt-duplicate-key-checker -h127.0.0.1 -uroot -proot
#指定數據庫 pt-duplicate-key-checker -h127.0.0.1 -uroot -proot -dsakila
3、刪除不用索引
在mysql中可以通過慢查日志配合pt-index-usage工具來進行索引使用情況分析。
pt-index-usage -h127.0.0.1 -uroot -proot /data/mysql/hive-slow.log
三、SQL優化(慢查詢)
1、慢查詢
如何發現有問題的SQL?我們可以 使用Mysql慢查詢日志對有效率問題的SQL進行監控。
-- 查看包含log的參數 show variables like '%log%'; -- 查看慢查詢日志是否開啟 show variables like 'slow_query_log'; -- 查看慢查詢日志存儲位置 show variables like 'slow_query_log_file'; -- 開啟慢查詢日志 set global slow_query_log=on; -- 指定慢查詢日志存儲位置 set global show_query_log_file='/data/mysql/hive-slow.log'; -- 記錄沒有使用索引的sql 開啟慢查詢日志 set global log_queries_not_using_indexes=on; -- 查看慢查詢設置的時間 超過此時間記錄到慢查詢日志中 show variables like 'long_query_time'; #記錄查詢超過1s的sql set global long_query_time=1;
如:
測試:
-- 執行sql select sleep(3);
#查看日志 [root@hive ~]# tail -f /data/mysql/hive-slow.log /root/mysql/bin/mysqld, Version: 5.6.38 (MySQL Community Server (GPL)). started with: Tcp port: 3306 Unix socket: /tmp/mysql.sock Time Id Command Argument # Time: 190708 9:38:47 # User@Host: root[root] @ [192.168.3.36] Id: 40 //執行sql的主機信息 # Query_time: 3.000991 Lock_time: 0.000000 Rows_sent: 1 Rows_examined: 0 //sql的執行信息 SET timestamp=1562593127; //sql執行時間 select sleep(3); //sql的內容
2、慢查詢日志分析工具 mysqldumpslow
安裝完MySQL后,默認就帶了mysqldumpslow,mysql官方提供的一個常用工具。
-- 查看參數列表
mysqldumpslow -h -- 分析慢查詢日志中前三條比較慢的sql mysqldumpslow -t 3 /data/mysql/hive-slow.log | more
輸出效果:
[root@hive ~]# mysqldumpslow -t 3 /data/mysql/hive-slow.log | more Reading mysql slow query log from /data/mysql/hive-slow.log Died at /root/mysql/bin/mysqldumpslow line 161, <> chunk 1. Count: 1 Time=3.00s (3s) Lock=0.00s (0s) Rows=1.0 (1), root[root]@[192.168.3.36] select sleep(N)
3、慢查詢日志分析工具pt-query-digest
分析結果比mysqldumpslow更詳細全面,它可以分析binlog、General log、slowlog, 安裝 :
wget percona.com/get/pt-query-digest chmod u+x pt-query-digest mv /root/pt-query-digest /usr/bin/ #如果出現Can't locate Time/HiRes.pm in @INC 錯誤 yum -y install perl-Time-HiRes
#查看參數列表
pt-query-digest --help [root@hive bin]# pt-query-digest /data/mysql/hive-slow.log | more # 50ms user time, 50ms system time, 20.09M rss, 165.33M vsz # Current date: Mon Jul 8 10:35:26 2019 # Hostname: hive # Files: /data/mysql/hive-slow.log # Overall: 1 total, 1 unique, 0 QPS, 0x concurrency ______________________ # Time range: all events occurred at 2019-07-08 09:38:47 # Attribute # ============ # Exec time #Locktime 0000000 #Rowssent 1111101 #Rowsexamine 0000000 # Query size 15 15 15 15 15 0 15 total min max avg 95% stddev median ======= ======= ======= ======= ======= ======= ======= 3s 3s 3s 3s 3s 0 3s # Profile # Rank Query ID Response time Calls R/Call V/M # ==== ================================== ============= ===== ====== ===== # 1 0x59A74D08D407B5EDF9A57DD5A41825CA 3.0010 100.0% 1 3.0010 0.00 SELECT # Query 1: 0 QPS, 0x concurrency, ID 0x59A74D08D407B5EDF9A57DD5A41825CA at byte 0 # This item is included in the report because it matches --limit. # Scores: V/M = 0.00 # Time range: all events occurred at 2019-07-08 09:38:47 # Attribute pct total min max avg 95% stddev median # ============ === ======= ======= ======= ======= ======= ======= ======= # Count 100 1 # Exec time 100 3s 3s #Locktime 0 0 0 #Rowssent 100 1 1 #Rowsexamine0 0 0 # Query size # String: # Hosts 192.168.3.36 # Users root # Query_time distribution # 1us # 10us # 100us # 1ms # 10ms # 100ms # 1s ################################################################ # 10s+ # EXPLAIN /*!50100 PARTITIONS*/ select sleep(3)\G
輸出分為三部分:
- 顯示除了日志的時間范圍,以及總的sql數量和不同的sql數量
- Response Time:響應時間占比 Calls:sql執行次數
- sql的具體日志
如何通過慢查詢日志發現有問題的SQL?
- 查詢次數多且每次查詢占用時間長的SQL:通常為pt-query-digest分析的前幾個查詢
- IO大的SQL(數據庫主要瓶頸出現在IO層次):注意pt-query-digest分析中的Rows examine項
- 未命中索引的SQL:注意pt-query-digest分析中的Rows examine和Rows Sent的對比
四、常見優化實例
1、Count()和Max()的優化
-- 查詢最后支付時間--優化max()函數 explain select max(payment_date) from payment;
-- 給payment_date建立索引(覆蓋索引) create index idx_paydate on payment(payment_date);
-- 刪除索引 drop index idx_release_year on payment; -- 顯示索引 show index from payment; -- 在一條SQL中同時查出2006年和2007年電影的數量--優化Count()函數 -- count('任意內容')都會統計出所有記錄數,因為count只有在遇見null時不計數,即 count(null)==0 explain select count(release_year='2006' or null) as '2006年電影數 量',count(release_year='2007' or null) as '2007年電影數量' from film; -- 優化,為release_year列設置索引 create index idx_release_year on film(release_year);
2、子查詢優化
通常情況下,需要把子查詢優化為join查詢,但在優化時要注意關聯鍵是否有一對多的關系,要注意重復數據。
-- 查詢sandra出演的所有影片 explain select title,release_year,length from film where film_id in ( select film_id from film_actor where actor_id in ( select actor_id from actor where first_name='sandra')); -- 優化之后 explain select title,release_year,length from film f join film_actor fa on fa.film_id=f.film_id join actor a on fa.actor_id = a.actor_id where a.first_name='sandra';
繼續優化,將first_name設為索引:
create index idx_first_name on actor(first_name);
3、group by的優化
優化策略: 先給分組字段建索引;再對該表分組、分組后再和其他表關聯查詢
-- 每個演員參與影片的數量 explain select a.first_name,a.last_name,count(*) from film_actor fa inner join actor a using(actor_id) group by fa.actor_id; -- 優化后 子查詢 索引 explain select a.first_name,a.last_name,c.cnt from actor a inner join ( select actor_id,count(*) as cnt from film_actor group by actor_id) as c USING(actor_id)
4、limit優化
limit常用於分頁處理,時常會伴隨order by 從句使用,因此大多時候會使用Filesorts這樣會造成大量的IO問題。
避免數據量大時掃描過多的記錄:
-- 分頁查詢影片描述信息 explain select film_id,description from film order by title limit 50,5; -- 優化1:使用有索引的列或主鍵進行order by操作(order by film_id) -- 頁數越大,rows越大 explain select film_id,description from film order by film_id limit 50,5; -- 優化2:記錄上次返回的主鍵,在下次查詢的時候用主鍵過濾,避免了數據量大時掃描過多的記錄 -- 注意要求有序主鍵 或者建立有序輔助索引列 explain select film_id,description from film where film_id>55 and film_id<=60 order by film_id limit 1,5;
5、in和exsits優化
原則:小表驅動大表,即小的數據集驅動大的數據集
- in:當B表的數據集必須小於A表的數據集時,in優於exists:select * from A where id in (select id from B);
explain select * from film where id in(select film_id from film_actor);
- exists:當A表的數據集小於B表的數據集時,exists優於in。將主查詢A的數據,放到子查詢B中做條件驗證,根據驗證結果(true或false)來決定主查詢的數據是否保留:
select * from A where exists (select 1 from B where B.id = A.id) -- A表與B表的ID字段應建立索引
explain select * from film where exists (select 1 from film_actor where film_actor.film_id = film.id)
注意:
- EXISTS (subquery)只返回TRUE或FALSE,因此子查詢中的SELECT * 也可以是SELECT 1或select X,官方說法是實際執行時會忽略SELECT清單,因此沒有區別;
- EXISTS子查詢的實際執行過程可能經過了優化而不是我們理解上的逐條對比;
- EXISTS子查詢往往也可以用JOIN來代替,何種最優需要具體問題具體分析。
6、join
對連接屬性進行排序時,應當選擇驅動表的屬性作為排序表中的條件
explain select * from film join film_actor on film_actor.film_id=film.id order by film.id;
explain select * from film join film_actor on film_actor.film_id=film.id order by film_actor.film_id;
explain select name from film join film_actor on film_actor.film_id=film.id order by film_actor.film_id;