MySQL優化(1)--------經常使用的優化步驟

在開始博客以前,仍是一樣的給一個大概的目錄結構,實則即爲通常MySQL的優化步驟html

一、查看SQL的執行頻率---------------使用show status命令mysql

二、定位哪些須要優化的SQL------------經過慢查詢記錄+show processlist命令查看當前線程sql

三、分析爲何SQL執行效率低------------使用explain/desc命令分析緩存

  • 相關列簡單解釋:type、table、select_type...

四、對症下藥採起優化措施-----------舉例採起index進行優化服務器

  • 如何使用索引?
  • 使用索引應該注意的事項
  • 查看索引使用狀況

主要參考資料:《深刻淺出MySQL》,https://dev.mysql.com/doc/refman/8.0/en/statement-optimization.html網絡

 


 

1、查看SQL執行頻率

  使用show [session|gobal] status命令瞭解SQL執行頻率、線程緩存內的線程的數量、當前打開的鏈接的數量、得到的表的鎖的次數等。session

好比執行show status like 'Com_%'查看每一個語句執行的次數即頻率,其中Com_xxx中xxx表示就是語句,好比Com_select:執行select操做的次數。ide

 1 mysql> use test;
 2 Database changed
 3 mysql> show status like 'Com_%';
 4 +-----------------------------+-------+
 5 | Variable_name               | Value |
 6 +-----------------------------+-------+
 7 | Com_admin_commands          | 0     |
 8 | Com_assign_to_keycache      | 0     |
 9 | Com_alter_db                | 0     |
10 | Com_alter_db_upgrade        | 0     |
11 | Com_alter_event             | 0     |
12 | Com_alter_function          | 0     |
13 | Com_alter_instance          | 0     |
14 | Com_alter_procedure         | 0     |
15 | Com_alter_server            | 0     |
16 | Com_alter_table             | 0     |
17 | Com_alter_tablespace        | 0     |
18 | Com_alter_user              | 0     |
19 | Com_analyze                 | 0     |
20 | Com_begin                   | 0     |
21 | Com_binlog                  | 0     |
22 | Com_call_procedure          | 0     |
23 | Com_change_db               | 2     |
24 | Com_change_master           | 0     |
25 | Com_change_repl_filter      | 0     |
26 | Com_check                   | 0     |
27 | Com_checksum                | 0     |
28 | Com_commit                  | 0     |
29 | Com_create_db               | 0     |
30 | Com_create_event            | 0     |
31 | Com_create_function         | 0     |
32 | Com_create_index            | 0     |
  ..............................

好比執行show status like 'slow_queries'查看慢查詢次數(黑人問號??什麼是慢查詢呢?就是經過設置查詢時間閾值long_query_time(0-10s)並打開開關show_query_log(1=OFF/0=ON),當超過這個閾值的查詢都稱之爲慢查詢,一般用來劃分執行SQL效率)性能

mysql> show status like 'slow_queries';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| Slow_queries  | 0     |
+---------------+-------+
1 row in set

好比執行show status like 'uptime'查看服務工做時間(即運行時間)學習

mysql> show status like 'uptime';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| Uptime        | 21645 |
+---------------+-------+
1 row in set

好比執行show status like 'connections'查看MySQL鏈接數:

mysql> show status like 'connections';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| Connections   | 6     |
+---------------+-------+
1 row in set

  經過show [session|gobal] status命令很清楚地看到哪些SQL執行效率不如人意,可是具體是怎麼個不如意法,還得繼續往下看,使用EXPLAIN命令分析具體的SQL語句

 2、定位效率低的SQL

  上面也提到過慢查詢這個概念主要是用來劃分效率低的SQL,可是慢查詢是在整個查詢結束後才記錄的,因此光是靠慢查詢日誌是跟蹤不了效率低的SQL。通常有兩種方式定位效率低的SQL:

  一、經過慢查詢日誌查看效率低的SQL語句,慢查詢日誌是經過show_query_log_file指定存儲路徑的,裏面記錄全部超過long_query_time的SQL語句(關於日誌的查看,往後再一步研究學習),可是須要慢查詢日誌的產生是在查詢結束後纔有的。

  二、經過show processlist命令查看當前MySQL進行的線程,能夠看到線程的狀態信息

mysql> show processlist;
+----+------+-----------------+------+---------+------+----------+------------------+
| Id | User | Host            | db   | Command | Time | State    | Info             |
+----+------+-----------------+------+---------+------+----------+------------------+
|  2 | root | localhost:58377 | NULL | Sleep   | 2091 |          | NULL             |
|  3 | root | localhost:58382 | test | Sleep   | 2083 |          | NULL             |
|  4 | root | localhost:58386 | test | Sleep   | 2082 |          | NULL             |
|  5 | root | localhost:59092 | test | Query   |    0 | starting | show processlist |
+----+------+-----------------+------+---------+------+----------+------------------+
4 rows in set

  其中主要的是state字段,表示當前SQL語句線程的狀態,如Sleeping 表示正在等待客戶端發送新請求,Sending data把查詢到的data結果發送給客戶端等等,具體請看https://dev.mysql.com/doc/refman/8.0/en/general-thread-states.html

3、 查看分析效率低的SQL

  MYSQL 5.6.3之前只能EXPLAIN SELECT; MYSQL5.6.3之後就能夠EXPLAIN SELECT,UPDATE,DELETE,如今咱們先建立一個user_table的表,以後分析select* from user where name=''語句

mysql> create table user(id int, name varchar(10),password varchar(32),primary key(id))engine=InnoDB;
Query OK, 0 rows affected

以後插入三條數據:

mysql> insert into user values(1,'Zhangsan',replace(UUID(),'-','')),(2,'Lisi',replace(UUID(),'-','')),(3,'Wangwu',replace(UUID(),'-',''));
Query OK, 3 rows affected
Records: 3  Duplicates: 0  Warnings: 0
mysql> select* from user;
+----+----------+----------------------------------+
| id | name     | password                         |
+----+----------+----------------------------------+
|  1 | Zhangsan | 2d7284808e5111e8af74201a060059ce |
|  2 | Lisi     | 2d73641c8e5111e8af74201a060059ce |
|  3 | Wangwu   | 2d73670c8e5111e8af74201a060059ce |
+----+----------+----------------------------------+
3 rows in set

下面以分析select*from user where name='Lisi'語句爲例:

mysql> explain select*from user where name='Lisi';
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | user  | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    3 |    33.33 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set

 

下面講解select_type等常見列的含義的:

(1)select_type:表示SELECT的類型,主要有:

  • SIMPLE:簡單表,沒有錶鏈接或者子查詢
  • PRIMARY:主查詢,即最外城的查詢
  • UNION:UNION中的第二個或者後面的語句
  • SUBQUERY:子查詢中的第一個SELECT

(2)table:結果輸出的表

(3)type:表示表的鏈接類型,性能由好到差爲:

  • system:常量表
  • const:單表中最多有一行匹配,好比primary key,unique index
  • eq_ref:多表鏈接中使用primary key,unique index
  • ref:使用普通索引
  • ref_or_null:與ref相似,可是包含了NULL查詢
  • index_merge:索引合併優化
  • unique_subquery:in後面是一個查詢主鍵字段的子查詢
  • index_subquery:in後面是非惟一索引字段的子查詢
  • range:單表中範圍查看,使用like模糊查詢
  • index:對於後面每一行都經過查詢索引獲得數據
  • all:表示全表查詢

(3)possible_key:查詢時可能使用的索引

(4)key:表示實際使用的索引

(5)key_len:索引字段的長度

(6)rows:查詢時實際掃描的行數

(7)Extra:執行狀況的說明和描述

(8)partitions:分區數目

(9)filtered:查詢過濾的表佔的百分比,好比這裏查詢的記錄是name=Lisi的記錄,佔三條記錄的33.3%

4、 關於索引的優化

一、使用索引優化的舉例

  上個例子咱們看到到執行explain select*from user where name='Lisi',掃描了3行(所有行數)使用了全表搜索all。若是實際業務中name是常常用到查詢的字段(是指常常跟在where後的字段,不是select後的字段)而且數據量很大的狀況呢?這時候就須要索引了(索引常常用到where後面的字段比select後面的字段效果更好,或者說就是要使用在where後面的字段上)

增長name前綴索引(這裏只是舉例,並無選擇最合適的前綴):

mysql> create index index_name on user(name(2));
Query OK, 0 rows affected
Records: 0  Duplicates: 0  Warnings: 0

執行explain分析

mysql> explain select*from user where name = 'Lisi';
+----+-------------+-------+------------+------+---------------+------------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key        | key_len | ref   | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------------+---------+-------+------+----------+-------------+
|  1 | SIMPLE      | user  | NULL       | ref  | index_name    | index_name | 9       | const |    1 |      100 | Using where |
+----+-------------+-------+------------+------+---------------+------------+---------+-------+------+----------+-------------+
1 row in set

  能夠看到type變爲ref、rows降爲1(實際上只要使用了索引都是1),filtered過濾百分比爲100%,實際用到的索引爲index_name。若是數據量很大的話使用索引就是很好的優化措施,對於如何選擇索引,何時用索引,我作出了以下總結:

二、如何高效使用索引?

  (1) 建立多列索引時,只要查詢條件中用到最左邊的列,索引通常都會被用到

  咱們建立一張沒有索引的表user_1:

mysql> show create table 
user_1;
+--------+--------------------------------------------------------------------------------------------------------------------------+
| Table  | Create Table                                                                                                             |
+--------+--------------------------------------------------------------------------------------------------------------------------+
| user_1 | CREATE TABLE `user_1` (
  `id` int(11) DEFAULT NULL,
  `name` varchar(10) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 |
+--------+--------------------------------------------------------------------------------------------------------------------------+
 1 row in set

 以後一樣插入數據:

mysql> select *from user_1;
+----+----------+
| id | name     |
+----+----------+
|  1 | Zhangsan |
|  2 | Lisi     |
+----+----------+
2 rows in set

 建立多列索引index_id_name

mysql> create index index_id_name on user_1(id,name);
Query OK, 0 rows affected
Records: 0  Duplicates: 0  Warnings: 0

 實驗查詢explain分析name與id

mysql> explain select * from user_1 where id=1;
+----+-------------+--------+------------+------+---------------+---------------+---------+-------+------+----------+-------------+
| id | select_type | table  | partitions | type | possible_keys | key           | key_len | ref   | rows | filtered | Extra       |
+----+-------------+--------+------------+------+---------------+---------------+---------+-------+------+----------+-------------+
|  1 | SIMPLE      | user_1 | NULL       | ref  | index_id_name | index_id_name | 5       | const |    1 |      100 | Using index |
+----+-------------+--------+------------+------+---------------+---------------+---------+-------+------+----------+-------------+
1 row in set

mysql> explain select * from user_1 where name='Lisi';
+----+-------------+--------+------------+-------+---------------+---------------+---------+------+------+----------+--------------------------+
| id | select_type | table  | partitions | type  | possible_keys | key           | key_len | ref  | rows | filtered | Extra                    |
+----+-------------+--------+------------+-------+---------------+---------------+---------+------+------+----------+--------------------------+
|  1 | SIMPLE      | user_1 | NULL       | index | NULL          | index_id_name | 38      | NULL |    2 |       50 | Using where; Using index |
+----+-------------+--------+------------+-------+---------------+---------------+---------+------+------+----------+--------------------------+
1 row in set

  能夠看到使用最左列id的時候,rows爲1,而且Extra明確使用了index,key的值爲id_name_index,type的值爲ref,而where不用到id,而是name的話,rows的值爲2。filtered爲50%,雖然key是index_id_name,可是代表是索引(我的理解,應該不太準確)

  (2) 使用like的查詢,只有%不是第一個字符而且%後面是常量的狀況下,索引纔可能會被使用。

   執行explain select *from user where name like ‘%Li’後type爲ALLkey的值爲NULL,執行explain select *from user where name like ‘Li%’後key值不爲空爲index_name。

mysql> explain select*from user where name like '%Li';
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | user  | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    3 |    33.33 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set
mysql> explain select*from user where name like 'Li%';
+----+-------------+-------+------------+-------+---------------+------------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type  | possible_keys | key        | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+-------+---------------+------------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | user  | NULL       | range | index_name    | index_name | 9       | NULL |    1 |      100 | Using where |
+----+-------------+-------+------------+-------+---------------+------------+---------+------+------+----------+-------------+
1 row in set

  (3) 若是對打的文本進行搜索,使用全文索引而不是用like ‘%...%’(只有MyISAM支持全文索引)

  (4) 若是列名是索引,使用column_name is null將使用索引

mysql> explain select*from user where name is null;
+----+-------------+-------+------------+------+---------------+------------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key        | key_len | ref   | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------------+---------+-------+------+----------+-------------+
|  1 | SIMPLE      | user  | NULL       | ref  | index_name    | index_name | 9       | const |    1 |      100 | Using where |
+----+-------------+-------+------------+------+---------------+------------+---------+-------+------+----------+-------------+
1 row in set

mysql> explain select*from user where password
 is null;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | user  | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    3 |    33.33 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set

三、哪些狀況下即便有索引也用不到?

  (1) MySQL使用MEMORY/HEAP引擎(使用的HASH索引),而且WHERE條件中不會使用」=」,in等進行索引列,那麼不會用到索引(這是關於引擎部分特色,以後會介紹)。

  (2) 用OR分隔開的條件,若是OR前面的條件中的列有索引,然後面的列沒有索引,那麼涉及到的列索引不會被使用。

  執行命令show index from user能夠看出password字段並無使用任何索引,而id使用了兩個索引,可是where id=1 or password='2d7284808e5111e8af74201a060059ce' 致使沒有使用id列的primary索引與id_name_index索引

mysql> show index from user;
+-------+------------+---------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name      | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-------+------------+---------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| user  |          0 | PRIMARY       |            1 | id          | A         |           3 | NULL     | NULL   |      | BTREE      |         |               |
| user  |          1 | index_name    |            1 | name        | A         |           3 |        2 | NULL   | YES  | BTREE      |         |               |
| user  |          1 | id_name_index |            1 | id          | A         |           3 | NULL     | NULL   |      | BTREE      |         |               |
| user  |          1 | id_name_index |            2 | name        | A         |           3 | NULL     | NULL   | YES  | BTREE      |         |               |
+-------+------------+---------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
4 rows in set

mysql> explain select*from user where id=1 or password='2d7284808e5111e8af74201a060059ce';
+----+-------------+-------+------------+------+-----------------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys         | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+------+-----------------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | user  | NULL       | ALL  | PRIMARY,id_name_index | NULL | NULL    | NULL |    3 |    55.56 | Using where |
+----+-------------+-------+------------+------+-----------------------+------+---------+------+------+----------+-------------+
1 row in set

  (3) 不是用到複合索引中的第一列即最左邊的列的話,索引就不起做用(上面已經介紹)。

  (4) 若是like是以%開頭的(上面已經介紹)

  (5) 若是列類型是字符串,那麼where條件中字符常量值不用’’引號引發來的話,那就不會失去索引效果,這是由於MySQL會把輸入的常量值進行轉換再使用索引。

  select * from user_1 where name =250,其中name的索引爲name_index,而且是varchar字符串類型,可是並無將250用引號變成’250’,那麼explain以後的ref仍然爲NULL,rows爲3

mysql> show index from user_1;
+--------+------------+---------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table  | Non_unique | Key_name      | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+--------+------------+---------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| user_1 |          1 | index_id_name |            1 | id          | A         |           2 | NULL     | NULL   | YES  | BTREE      |         |               |
| user_1 |          1 | index_id_name |            2 | name        | A         |           2 | NULL     | NULL   | YES  | BTREE      |         |               |
| user_1 |          1 | name_index    |            1 | name        | A         |           3 |        5 | NULL   | YES  | BTREE      |         |               |
+--------+------------+---------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
3 rows in set

mysql> explain select*from user_1 where name=250;
+----+-------------+--------+------------+-------+---------------+---------------+---------+------+------+----------+--------------------------+
| id | select_type | table  | partitions | type  | possible_keys | key           | key_len | ref  | rows | filtered | Extra                    |
+----+-------------+--------+------------+-------+---------------+---------------+---------+------+------+----------+--------------------------+
|  1 | SIMPLE      | user_1 | NULL       | index | name_index    | index_id_name | 38      | NULL |    3 |    33.33 | Using where; Using index |
+----+-------------+--------+------------+-------+---------------+---------------+---------+------+------+----------+--------------------------+
1 row in set

mysql> explain select*from user_1 where name='250';
+----+-------------+--------+------------+------+---------------+------------+---------+-------+------+----------+-------------+
| id | select_type | table  | partitions | type | possible_keys | key        | key_len | ref   | rows | filtered | Extra       |
+----+-------------+--------+------------+------+---------------+------------+---------+-------+------+----------+-------------+
|  1 | SIMPLE      | user_1 | NULL       | ref  | name_index    | name_index | 18      | const |    1 |      100 | Using where |
+----+-------------+--------+------------+------+---------------+------------+---------+-------+------+----------+-------------+
1 row in set

 

四、查看索引的使用狀況

執行show status like Handler_read%’能夠看到一個值Handler_read_key,它表明一行被索引值讀的次數,若是值很低說明增長索引獲得的性能改善不高,由於索引並不常用。

mysql> show status like 'Handler_read%' ;
+-----------------------+-------+
| Variable_name         | Value |
+-----------------------+-------+
| Handler_read_first    | 3     |
| Handler_read_key      | 5     |
| Handler_read_last     | 0     |
| Handler_read_next     | 0     |
| Handler_read_prev     | 0     |
| Handler_read_rnd      | 0     |
| Handler_read_rnd_next | 20    |
+-----------------------+-------+
7 rows in set

(1)Handler_read_first:索引中第一條被讀的次數。若是較高,它表示服務器正執行大量全索引掃描;

(2)Handler_read_key:若是索引正在工做,這個值表明一個行被索引值讀的次數,若是值越低,表示索引獲得的性能改善不高,由於索引不常用。

(3)Handler_read_next :按照鍵順序讀下一行的請求數。若是你用範圍約束或若是執行索引掃描來查詢索引列,該值增長。

(4)Handler_read_prev:按照鍵順序讀前一行的請求數。該讀方法主要用於優化ORDER BY ... DESC。

(5)Handler_read_rnd :根據固定位置讀一行的請求數。若是你正執行大量查詢並須要對結果進行排序該值較高。你可能使用了大量須要MySQL掃描整個表的查詢或你的鏈接沒有正確使用鍵。這個值較高,意味着運行效率低,應該創建索引來補救。

(6)Handler_read_rnd_next:在數據文件中讀下一行的請求數。若是你正進行大量的表掃描,該值較高。一般說明你的表索引不正確或寫入的查詢沒有利用索引。

   注:以上6點來自於網絡總結,其中比較重要的兩個參數是Handler_read_key與Handler_read_rnd_next。

相關文章
相關標籤/搜索