三、索引優化分析(下)

4. 性能分析

4.1 MySQL 常見性能瓶頸

① CPU:CPU 在滿負荷運行一般發生在數據裝入到內存或從磁盤讀取數據時;

② IO:磁盤 IO 瓶頸發生在裝入數據遠大於內存容量時;

③ 服務器硬件瓶頸:通過 top、free、iostat、vmstat 命令查看系統的性能和狀態;

④ 數據庫服務器配置問題;

4.2 MySQL 性能分析

4.2.1 EXPLAIN 簡介

使用 EXPLAIN 關鍵字可以模擬 MySQL 優化器執行 SQL 查詢語句,從而查看 MySQL 是如何理解你的 SQL 語句,用於分析 SQL 查詢語句或者表結構的性能瓶頸。

類似於醫院檢查的化驗報告單。

作用

① 獲取表的讀取順序;

② 獲得數據讀取操作的操作類型;

③ 查看那些索引可以使用;

④ 查看哪些索引實際被使用;

⑤ 查看錶之間的引用關係;

⑥ 查看每張表有多少行被優化器查詢;

4.2.2 使用方法:

EXPLAIN + SQL 語句

示例:

mysql> explain select * from tbl_emp where id = 2;
+----+-------------+---------+-------+---------------+---------+---------+-------+------+-------+
| id | select_type | table   | type  | possible_keys | key     | key_len | ref   | rows | Extra |
+----+-------------+---------+-------+---------------+---------+---------+-------+------+-------+
|  1 | SIMPLE      | tbl_emp | const | PRIMARY       | PRIMARY | 4       | const |    1 |       |
+----+-------------+---------+-------+---------------+---------+---------+-------+------+-------+
1 row in set (0.00 sec)

EXPLAIN 執行後返回的信息列:

  • id:查詢的序列號,包含一組數字,表示查詢中執行 SELECT 子句或操作表的順序;
  • select_type:表示查詢的類型;
  • table:表示數據所在的表;
  • type:查詢的訪問類型;
  • possible_keys:顯示可能在這張表中的索引,一個或多個;
  • key:實際使用到的索引;
  • key_len:表示索引中使用的字節數,可以通過該列計算查詢中使用的索引長度
  • ref:顯示索引的那一列被使用;
  • rows:顯示 MySQL 認爲執行查詢時必須檢查的行數;
  • Extra:其他的額外重要的信息;

4.2.2 各分析字段解釋

使用以下 SQL 語句創建數據表

CREATE TABLE t1(id INT(10) AUTO_INCREMENT,content VARCHAR(100) NULL , PRIMARY KEY (id));
CREATE TABLE t2(id INT(10) AUTO_INCREMENT,content VARCHAR(100) NULL , PRIMARY KEY (id));
CREATE TABLE t3(id INT(10) AUTO_INCREMENT,content VARCHAR(100) NULL , PRIMARY KEY (id));
CREATE TABLE t4(id INT(10) AUTO_INCREMENT,content VARCHAR(100) NULL , PRIMARY KEY (id));
INSERT INTO t1(content) VALUES(CONCAT('t1_',FLOOR(1+RAND()*1000)));
INSERT INTO t2(content) VALUES(CONCAT('t2_',FLOOR(1+RAND()*1000)));
INSERT INTO t3(content) VALUES(CONCAT('t3_',FLOOR(1+RAND()*1000)));
INSERT INTO t4(content) VALUES(CONCAT('t4_',FLOOR(1+RAND()*1000)));

4.2.2.1 id

查詢的序列號,包含一組數字,表示查詢中執行 SELECT 子句或操作表的順序;

主要分爲三種情況:

① id 相同,執行順序由上至下;

mysql> explain select * from t1,t2,t3 where t1.id = t2.id and t2.id = t3.id;
+----+-------------+-------+--------+---------------+---------+---------+-----------------+------+-------+
| id | select_type | table | type   | possible_keys | key     | key_len | ref             | rows | Extra |
+----+-------------+-------+--------+---------------+---------+---------+-----------------+------+-------+
|  1 | SIMPLE      | t1    | ALL    | PRIMARY       | NULL    | NULL    | NULL            |    1 |       |
|  1 | SIMPLE      | t2    | eq_ref | PRIMARY       | PRIMARY | 4       | base_crud.t1.id |    1 |       |
|  1 | SIMPLE      | t3    | eq_ref | PRIMARY       | PRIMARY | 4       | base_crud.t1.id |    1 |       |
+----+-------------+-------+--------+---------------+---------+---------+-----------------+------+-------+
3 rows in set (0.00 sec)

② id 不同,如果是子查詢,id 的序號會遞增,id 值越大優先級越高,越先被執行;

mysql> explain select t1.id from t1 where t1.id in (select t2.id from t2 where t2.id in (select t3.id from t3 where t3.content = ''));
+----+--------------------+-------+-----------------+---------------+---------+---------+------+------+--------------------------+
| id | select_type        | table | type            | possible_keys | key     | key_len | ref  | rows | Extra                    |
+----+--------------------+-------+-----------------+---------------+---------+---------+------+------+--------------------------+
|  1 | PRIMARY            | t1    | index           | NULL          | PRIMARY | 4       | NULL |    1 | Using where; Using index |
|  2 | DEPENDENT SUBQUERY | t2    | unique_subquery | PRIMARY       | PRIMARY | 4       | func |    1 | Using index; Using where |
|  3 | DEPENDENT SUBQUERY | t3    | unique_subquery | PRIMARY       | PRIMARY | 4       | func |    1 | Using where              |
+----+--------------------+-------+-----------------+---------------+---------+---------+------+------+--------------------------+
3 rows in set (0.00 sec)

③ id 有相同的也有不同的,id 如果相同,可以認爲是一組的,從上往下執行;所在組中 id 值越大,優先級越高

mysql> explain select t2.* from t2, (select t3.* from t3) s3 where s3.id = t2.id;
+----+-------------+------------+--------+---------------+---------+---------+-------+------+-------+
| id | select_type | table      | type   | possible_keys | key     | key_len | ref   | rows | Extra |
+----+-------------+------------+--------+---------------+---------+---------+-------+------+-------+
|  1 | PRIMARY     | <derived2> | system | NULL          | NULL    | NULL    | NULL  |    1 |       |
|  1 | PRIMARY     | t2         | const  | PRIMARY       | PRIMARY | 4       | const |    1 |       |
|  2 | DERIVED     | t3         | ALL    | NULL          | NULL    | NULL    | NULL  |    1 |       |
+----+-------------+------------+--------+---------------+---------+---------+-------+------+-------+
3 rows in set (0.00 sec)

總結:每個 id 號碼。表示一個獨立的查詢,SQL 的查詢行數越少越好

4.2.2.2 select_type

表示查詢的類型,主要用於區分普通查詢、聯合查詢、子查詢等複雜查詢。

select_type屬性 含義
SIMPLE 簡單的 SELECT 查詢,查詢中不包含子查詢或者 UNION
PRIMARY 查詢中若包含複雜查詢的子部分,最外層查詢則標記爲 PAIMARY
DERIVED FROM 列表中包含的子查詢被標記爲 DERIVED (衍生)MySQL 會遞歸執行這些子查詢,把結果放到臨時表中。
SUBQUERY SELECTWHERE 列表中包含了子查詢。
DEPEDENT SUBQUERY SELECTWHERE 列表中包含了子查詢,子查詢基於外層。
UNCACHEABLE SUBQUERY 無法使用緩存的子查詢。
UNION 若第二個 SELECT 出現在 UNION 之後,則被標記爲 UNION;若 UNION 包含在 FROM 子句的子查詢中,外層 SELECT 將被標記爲 DERIVED
UNION RESULT UNION 表獲取結果的 SELECT

SIMPLE:表示單表查詢

mysql> explain select * from t1;
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
|  1 | SIMPLE      | t1    | ALL  | NULL          | NULL | NULL    | NULL |    1 |       |
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
1 row in set (0.00 sec)

PRIMARY:查詢中 若包含任何複雜的子部分,最外層查詢則會被標記爲 PRIMARY

mysql> explain select * from (select * from t2) a;
+----+-------------+------------+--------+---------------+------+---------+------+------+-------+
| id | select_type | table      | type   | possible_keys | key  | key_len | ref  | rows | Extra |
+----+-------------+------------+--------+---------------+------+---------+------+------+-------+
|  1 | PRIMARY     | <derived2> | system | NULL          | NULL | NULL    | NULL |    1 |       |
|  2 | DERIVED     | t2         | ALL    | NULL          | NULL | NULL    | NULL |    1 |       |
+----+-------------+------------+--------+---------------+------+---------+------+------+-------+
2 rows in set (0.00 sec)

DERIVED:在 FROM 列表中包含的子查詢被標記爲 DERIVED(衍生)MySQL 會遞歸執行這些子查詢,把結果放大臨時表中。


mysql> explain select * from (select * from t2) a;
+----+-------------+------------+--------+---------------+------+---------+------+------+-------+
| id | select_type | table      | type   | possible_keys | key  | key_len | ref  | rows | Extra |
+----+-------------+------------+--------+---------------+------+---------+------+------+-------+
|  1 | PRIMARY     | <derived2> | system | NULL          | NULL | NULL    | NULL |    1 |       |
|  2 | DERIVED     | t2         | ALL    | NULL          | NULL | NULL    | NULL |    1 |       |
+----+-------------+------------+--------+---------------+------+---------+------+------+-------+
2 rows in set (0.00 sec)

SUBQUERYSELECTWHERE 列表中包含子查詢

mysql> EXPLAIN SELECT t2.id FROM t2 WHERE t2.id = (SELECT t3.id FROM t3 WHERE t3.id = 1);
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
| id | select_type | table | type  | possible_keys | key     | key_len | ref   | rows | Extra       |
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
|  1 | PRIMARY     | t2    | const | PRIMARY       | PRIMARY | 4       | const |    1 | Using index |
|  2 | SUBQUERY    | t3    | const | PRIMARY       | PRIMARY | 4       |       |    1 | Using index |
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
2 rows in set (0.00 sec)

DEPEDENT SUBQUERY:在 SELECTWHERE 列表中包含子查詢,子查詢基於外層。

mysql> explain select t2.id from t2 where t2.id in (select t3.id from t3 where t3.content = 't3_993');
+----+--------------------+-------+-----------------+---------------+---------+---------+------+------+--------------------------+
| id | select_type        | table | type            | possible_keys | key     | key_len | ref  | rows | Extra                    |
+----+--------------------+-------+-----------------+---------------+---------+---------+------+------+--------------------------+
|  1 | PRIMARY            | t2    | index           | NULL          | PRIMARY | 4       | NULL |    1 | Using where; Using index |
|  2 | DEPENDENT SUBQUERY | t3    | unique_subquery | PRIMARY       | PRIMARY | 4       | func |    1 | Using where              |
+----+--------------------+-------+-----------------+---------------+---------+---------+------+------+--------------------------+
2 rows in set (0.00 sec)

都在 WHERE 後加入子查詢,SUPERQUERY 是單個值,DEPENDENT SUBQUERY 是一組值。

UNCACHEABLE SUBQUERY:無法使用緩存的子查詢。

mysql> explain select * from t3 where id = (select id from t2 where t2.id = @@sort_buffer_size);
+----+----------------------+-------+------+---------------+------+---------+------+------+-----------------------------------------------------+
| id | select_type          | table | type | possible_keys | key  | key_len | ref  | rows | Extra                                               |
+----+----------------------+-------+------+---------------+------+---------+------+------+-----------------------------------------------------+
|  1 | PRIMARY              | NULL  | NULL | NULL          | NULL | NULL    | NULL | NULL | Impossible WHERE noticed after reading const tables |
|  2 | UNCACHEABLE SUBQUERY | NULL  | NULL | NULL          | NULL | NULL    | NULL | NULL | no matching row in const table                      |
+----+----------------------+-------+------+---------------+------+---------+------+------+-----------------------------------------------------+
2 rows in set (0.01 sec)

當使用 @@ 來引用系統變量時,不會使用緩存。

UNION:若第二個 SELECT 出現在 UNION 後,則被標記爲 UNION;若 UNION 包含在 FROM 子句的子查詢中,外層 SELECT 將被標記爲 DERIVED

mysql> explain select * from t1 union all select * from t2;
+----+--------------+------------+------+---------------+------+---------+------+------+-------+
| id | select_type  | table      | type | possible_keys | key  | key_len | ref  | rows | Extra |
+----+--------------+------------+------+---------------+------+---------+------+------+-------+
|  1 | PRIMARY      | t1         | ALL  | NULL          | NULL | NULL    | NULL |    1 |       |
|  2 | UNION        | t2         | ALL  | NULL          | NULL | NULL    | NULL |    1 |       |
| NULL | UNION RESULT | <union1,2> | ALL  | NULL          | NULL | NULL    | NULL | NULL |       |
+----+--------------+------------+------+---------------+------+---------+------+------+-------+
3 rows in set (0.00 sec)

UNION RESULT:從 UNION 表獲取結果的 SELECT

4.2.2.3 table

表示數據所在的表

4.2.2.4 type

查詢的訪問類型;

屬性 含義
system 表只有一行記錄(等於系統表)是 const 類型的特例,一般不會出現。
const 表示通過索引一次就找到,const 用於比較 primary key 或者 unique 索引。
eq_ref 唯一性索引掃描,對於每個索引鍵,表中只有一條記錄與之匹配。常見於主鍵或者唯一索引掃描。
ref 非唯一性索引掃描,返回匹配某個單獨值的所有行。本質上也是一種索引訪問,它返回所有匹配某個單獨值的行,然而,也可能會找到多個符合條件的行,所以它屬於查找和掃描的混合體。
range 只檢索指定範圍的行,使用一個索引來選擇行。key 列顯示使用了那個索引一般就在 WHERE 語句中出現了 between、<、>、in 等的查詢這種範圍掃描比全表掃描要好,因爲它只需要開始於索引的某一點,結束於另一點,不需要掃描全部索引。
index 出現 index 是 SQL 語句使用了索引但是沒使用索引進行過濾,一般是使用了覆蓋索引或者利用索引進行了排序分組
ALL 全表掃描以找到匹配的行
index_merge 在查詢中需要用到多個索引組合使用,通常出現在有 OR 關鍵字的 SQL 中。
ref_or_null 對某個字段即需要關聯條件,也需要 null 值的情況下,查詢優化器會選擇用 ref_or_null 連接查詢。
index_subquery 利用索引關聯子查詢,不再全表掃描。
unique_subquery 類似於 index_subquery 子查詢中的唯一索引。

查詢的訪問類型,是比較重要的一個指標,結果值從最好好到最壞依次是:

system > const > eq_ref > ref > fulltext > ref_or_null > index_merege > unique_subquery > index_subquery > range > index > ALL

一般而言要保證查詢至少達到 range 級別,最好能達到 ref

indexALL 的區別:

雖然 indexALL 都是讀全表,但是 index 是從索引中讀取的,只遍歷索引樹,而 ALL 是從硬盤中讀取的,相比較而言索引文件通常比數據文件小,因此 indexALL 更快。

4.2.2.5 possible_keys

顯示可能應用在這張表中的索引,一個或多個。查詢涉及到的字段上若存在索引,則該索引被列出,但不一定被查詢實際使用

4.2.2.6 key

實際使用到的索引,如果爲 NULL 則表示沒有使用索引;

查詢中若使用了覆蓋索引,則該索引僅出現在 key 列表中;

4.2.2.7 key_len

表示索引中使用的字節數,可以通過該列計算查詢中使用的索引長度。key_len 字段能幫助檢查是否充分利用索引。

key_len 越長說明索引使用越充分,即使用的索引數越多,匹配越精確;

在不損失精度的情況下,長度越短越好;

key_len 的計算方式:

① 首先查看索引上字段的類型和長度,比如:int = 4; varchar(20) = 20; char(20) = 20

varchar、char 這種字符串字段,不同的字符集要乘以不同的值,如:utf8 要乘以 3,GBK 要乘以 2;

varchar 這樣的動態字符要加 2 個字節;

④ 允許爲空的字段要加 1 個字節;

列類型 key_len 備註
int 4 + 1 允許 NUll,加 1 個字節
int not null 4 不允許爲 NULL
char(30) utf8 30 * 3 + 1 允許爲 NULL
varchar(30) not null utf8 30 * 3 + 2 動態列類型,加 2 個字節
varchar(30) utf8 30 * 3 + 2 + 1 動態列類型,加兩個字節;允許爲 NULL 再加一個字節
text(10) utf8 30 * 3 + 3 + 1 text 截取部分,被視作動態列類型,且允許爲 NULL

4.2.2.8 ref

顯示索引的那一列被使用,如果可能的話是個 const(常量)。那些列或常數被用於查找索引列上的值。

4.2.2.9 rows

根據表統計信息和索引選用情況,大致估算出找到所需記錄所需讀取的行數,行數越少越好。

4.2.2.10 Extra

包含不適合在其他列中顯示但是十分重要的額外信息。

Using filesort

MySQL 會對數據使用一個外部的索引排序,而不是按照表內的索引順序進行讀取。MySQL 無法利用索引完成的排序操作稱爲**“文件排序”**

查詢中排序的字段,排序字段若是通過索引去訪問將大大提高排序速度。

Using temporary

使用了臨時表保存中間結果,MySQL 在對查詢結果排序時使用臨時表。常見於排序 ORDER BY 和分組查詢 GROUP BY

排序 ORDER BY 和分組查詢 GROUP BY 是拖慢 SQL 執行的元兇,臨時表會增加 SQL 負擔。

Using index

表示相應的 SELECT 操作中使用了覆蓋索引(Covering Index)避免訪問了表的數據行,效率不錯。如果同時出現 Using where,表明索引被用來執行索引鍵值的查找;如果沒有同時出現 Using where,表明索引只是用來讀取數據而非利用索引執行查找。利用索引進行了排序或分組。

覆蓋索引(Covering Index):

  • SELECT 的數據列只用從索引中就能獲取,不需要讀取數據行,MySQL 可以利用索引返回 SELECT 列表中的字段,而不需要根據索引再次讀取數據文件,也就是說查詢列要被所建立的索引覆蓋。
  • 索引是高效找到行的一個方法,但是一般數據庫也能夠使用索引找到一個列的數據,因此它不必讀取整個行。畢竟索引葉子節點存儲了他們索引的數據;當能通過索引就可以得到需要的數據,就不需要讀取行了。索引包含了(或者覆蓋了)滿足查詢結果的數據就叫覆蓋索引;
  • 簡單的講覆蓋索引就是 SELECT 中查詢的字段和順序與建立的複合索引順序和字段剛好是一樣的;

注意:

如果要使用覆蓋索引,一定要注意 SELECT 列表中取出需要的列,不能使用 SELECT *,因爲如果將所有字段一起做索引會導致索引文件過大,查詢性能下降。

Using filesort:採用文件排序,影響 SQL 執行效率;

Using temporary:使用了臨時表,嚴重影響 SQL 執行效率;

Using index:使用了覆蓋索引,效率不錯;

以上三個參數是判斷 SQL 執行效率的基本方法。

Using where

表明使用了 WHERE 過濾。

Using join buffer

表明使用了連接緩存。

impossible where

WHERE 子句的值總是 false,不能用來獲取任何元組。

select tables optimized away

在沒有 GROUP BY 子句的情況下,基於索引優化 MIN/MAX 操作或者對於 MyISAM 存儲引擎優化 COUNT(*) 操作,不必等到執行階段再進行計算,查詢執行計劃生成階段即完成優化。

distinct

優化 distinct,再找到第一匹配元組後即停止查找同樣值的工作。

5. 索引優化

5.1 索引分析

5.1.1 單表優化分析

① 創建數據表

CREATE TABLE IF NOT EXISTS `article`(
`id` INT(10) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
`author_id` INT (10) UNSIGNED NOT NULL,
`category_id` INT(10) UNSIGNED NOT NULL , 
`views` INT(10) UNSIGNED NOT NULL , 
`comments` INT(10) UNSIGNED NOT NULL,
`title` VARBINARY(255) NOT NULL,
`content` TEXT NOT NULL
);
INSERT INTO `article`(`author_id`,`category_id` ,`views` ,`comments` ,`title` ,`content` )VALUES
(1,1,1,1,'1','1'),
(2,2,2,2,'2','2'),
(3,3,3,3,'3','3');

創建後的數據庫

mysql> select * from article;
+----+-----------+-------------+-------+----------+-------+---------+
| id | author_id | category_id | views | comments | title | content |
+----+-----------+-------------+-------+----------+-------+---------+
|  1 |         1 |           1 |     1 |        1 | 1     | 1       |
|  2 |         2 |           2 |     2 |        2 | 2     | 2       |
|  3 |         3 |           3 |     3 |        3 | 3     | 3       |
+----+-----------+-------------+-------+----------+-------+---------+
3 rows in set (0.00 sec)

② 查詢需求

查詢 category_id 爲 1 且 comments 大於等於 1 的情況下,views 最多的 article_id

mysql> select id,author_id from article where category_id = 1 AND comments >= 1 order by views desc limit 1;
+----+-----------+
| id | author_id |
+----+-----------+
|  1 |         1 |
+----+-----------+
1 row in set (0.00 sec)

③ 分析優化 SQL

image-20210413161149440

SQL 查詢中存在的問題:

  • SQL 進行了全表掃描;
  • 沒有也沒有使用索引;
  • 使用了文件排序效率低下;

④ 解決方案

創建索引

create index idx_article_ccv on article(category_id, comments, views);
alter table `article` add index idx_article_ccv(`category_id`,`comments`,`views`)

查看索引

mysql> show index from article;
+---------+------------+-----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table   | Non_unique | Key_name        | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+---------+------------+-----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| article |          0 | PRIMARY         |            1 | id          | A         |           3 |     NULL | NULL   |      | BTREE      |         |               |
| article |          1 | idx_article_ccv |            1 | category_id | A         |           3 |     NULL | NULL   |      | BTREE      |         |               |
| article |          1 | idx_article_ccv |            2 | comments    | A         |           3 |     NULL | NULL   |      | BTREE      |         |               |
| article |          1 | idx_article_ccv |            3 | views       | A         |           3 |     NULL | NULL   |      | BTREE      |         |               |
+---------+------------+-----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
4 rows in set (0.00 sec)

查看性能

image-20210413165354340

優化後可以看到查詢已經使用了索引,但是依舊存在文件排序問題。

建立了索引但是沒有完全使用的原因:

根據B-Tree 索引的工作原理

① 先排序 category_id

② 如果遇到相同的 category_id 則再排序 comments

③ 如果遇到相同的 comments 則再排序 views

④ 當 comments 處於聯合索引中間位置時,因 comments >= 1 條件是範圍值(即爲 range

⑤ MySQL 無法再利用索引後面的 views 部分進行檢索;

⑥ 最終 MySQL 使用的索引類型是 range,後面的索引失效;

優化索引

# 刪除不合適的索引
mysql> drop index idx_article_ccv on article;
Query OK, 0 rows affected (0.02 sec)
Records: 0  Duplicates: 0  Warnings: 0
# 創建新的索引
mysql> create index idx_article_cv on article(category_id, views);
Query OK, 0 rows affected (0.02 sec)
Records: 0  Duplicates: 0  Warnings: 0
# 查看新索引
mysql> show index from article;
+---------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table   | Non_unique | Key_name       | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+---------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| article |          0 | PRIMARY        |            1 | id          | A         |           3 |     NULL | NULL   |      | BTREE      |         |               |
| article |          1 | idx_article_cv |            1 | category_id | A         |           3 |     NULL | NULL   |      | BTREE      |         |               |
| article |          1 | idx_article_cv |            2 | views       | A         |           3 |     NULL | NULL   |      | BTREE      |         |               |
+---------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
3 rows in set (0.00 sec)

查看性能

image-20210413171622860

使用新索引後 type 類型變成了 ref,使用了索引 idx_article_cv 且兩個索列都被使用,而且不會出現文件排序問題,SQL 性能很好。

5.1.2 兩表優化分析

兩表關聯索引加在哪裏?

① 創建數據表

CREATE TABLE IF NOT EXISTS `class`(
`id` INT(10) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
`card` INT (10) UNSIGNED NOT NULL
);
CREATE TABLE IF NOT EXISTS `book`(
`bookid` INT(10) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
`card` INT (10) UNSIGNED NOT NULL
);
INSERT INTO class(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card)VALUES(FLOOR(1+(RAND()*20)));
 
INSERT INTO book(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card)VALUES(FLOOR(1+(RAND()*20)));

創建後的數據表

mysql> select * from book;
+--------+------+
| bookid | card |
+--------+------+
|      1 |    1 |
|      2 |   16 |
|      3 |   18 |
|      4 |    2 |
|      5 |   13 |
|      6 |   19 |
|      7 |   18 |
|      8 |   10 |
|      9 |   17 |
|     10 |   12 |
|     11 |   10 |
|     12 |   13 |
|     13 |   15 |
|     14 |   14 |
|     15 |    6 |
|     16 |    6 |
|     17 |   11 |
|     18 |   17 |
|     19 |   10 |
|     20 |   20 |
+--------+------+
20 rows in set (0.00 sec)

mysql> select * from class;
+----+------+
| id | card |
+----+------+
|  1 |   10 |
|  2 |    3 |
|  3 |    4 |
|  4 |    9 |
|  5 |   14 |
|  6 |    4 |
|  7 |   16 |
|  8 |   10 |
|  9 |   19 |
| 10 |    6 |
| 11 |   11 |
| 12 |   17 |
| 13 |   12 |
| 14 |    6 |
| 15 |   16 |
| 16 |    1 |
| 17 |   15 |
| 18 |   10 |
| 19 |    8 |
| 20 |    9 |
+----+------+
20 rows in set (0.00 sec)

② 實際問題

兩表關聯時索引的建立位置:

  • 左連接時在左表還是右表建立索引?
  • 右連接時在左表還是右表建立索引?

② 分析優化

左連接時 class 作爲左表,book 作爲右表

image-20210414083530776

分析可知查詢進行全表掃描,且沒有使用索引。

③ 解決方案

給 class 表建立索引

# 在class表建立索引
mysql> create index idx_class_card on class(card);
Query OK, 0 rows affected (0.02 sec)
Records: 0  Duplicates: 0  Warnings: 0
# 查詢索引
mysql> show index from class;
+-------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name       | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| class |          0 | PRIMARY        |            1 | id          | A         |          20 |     NULL | NULL   |      | BTREE      |         |               |
| class |          1 | idx_class_card |            1 | card        | A         |          20 |     NULL | NULL   |      | BTREE      |         |               |
+-------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
2 rows in set (0.00 sec)

image-20210414084633237

給左連接的左表加索引後,type 達到 index 級別,但是掃描的行數沒有減少。

爲 book 表建立索引


mysql> drop index idx_class_card on class;
Query OK, 0 rows affected (0.03 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> create index idx_book_card on book(card);
Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> show index from book;
+-------+------------+---------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name      | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-------+------------+---------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| book  |          0 | PRIMARY       |            1 | bookid      | A         |          20 |     NULL | NULL   |      | BTREE      |         |               |
| book  |          1 | idx_book_card |            1 | card        | A         |          20 |     NULL | NULL   |      | BTREE      |         |               |
+-------+------------+---------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
2 rows in set (0.00 sec)

image-20210414085037889

給左連接的右表添加索引,type 達到 ref 級別,且使用了索引,右表掃描的行數減少。

出現以上情況的原因是由左連接的特性導致的:

  • LEFT JOIN 條件用於確定如何讓右表的搜索行,左表是一定全都包含的。
  • 因此右表是我們的搜索關鍵表,需要建立索引。

同理可推出右連接存在同樣的問題,因此得出結論:左連接時在右表建立索引,右連接在左表建立索引,即**“連接索引相反建立”**

5.1.3 三表優化分析

① 創建數據表

CREATE TABLE IF NOT EXISTS `phone`(
`phoneid` INT(10) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
`card` INT (10) UNSIGNED NOT NULL
)ENGINE = INNODB;

INSERT INTO phone(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card)VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card)VALUES(FLOOR(1+(RAND()*20)));
mysql> select * from phone;
+---------+------+
| phoneid | card |
+---------+------+
|       1 |   18 |
|       2 |   13 |
|       3 |   10 |
|       4 |   12 |
|       5 |    8 |
|       6 |    5 |
|       7 |    2 |
|       8 |   11 |
|       9 |   12 |
|      10 |    4 |
|      11 |    3 |
|      12 |    5 |
|      13 |   13 |
|      14 |   11 |
|      15 |   13 |
|      16 |   15 |
|      17 |   15 |
|      18 |    7 |
|      19 |   10 |
|      20 |    7 |
+---------+------+
20 rows in set (0.00 sec)

② 查詢需求

三表關聯索引建立的位置?

③ 分析優化

image-20210414093209940

三表關聯沒有使用索引且進行了全表掃描。

④ 解決方案

通過兩表關聯可知需要和連接建立相反方向的索引,因此需要在 book 表和 phone 表的 card 字段上建立索引。

# 在 book 表建立索引
mysql> create index idx_book_card on book(card);
Query OK, 0 rows affected (0.02 sec)
Records: 0  Duplicates: 0  Warnings: 0
# 在 phone 表建立索引
mysql> alter table phone add index idx_phone_card(card);
Query OK, 0 rows affected (0.02 sec)
Records: 0  Duplicates: 0  Warnings: 0
# 查詢索引
mysql> show index from book;
+-------+------------+---------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name      | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-------+------------+---------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| book  |          0 | PRIMARY       |            1 | bookid      | A         |          20 |     NULL | NULL   |      | BTREE      |         |               |
| book  |          1 | idx_book_card |            1 | card        | A         |          20 |     NULL | NULL   |      | BTREE      |         |               |
+-------+------------+---------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
2 rows in set (0.00 sec)

mysql> show index from pnone;
ERROR 1146 (42S02): Table 'base_crud.pnone' doesn't exist
mysql> show index from phone;
+-------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name       | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| phone |          0 | PRIMARY        |            1 | phoneid     | A         |          20 |     NULL | NULL   |      | BTREE      |         |               |
| phone |          1 | idx_phone_card |            1 | card        | A         |          20 |     NULL | NULL   |      | BTREE      |         |               |
+-------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
2 rows in set (0.00 sec)

查詢分析

image-20210414094310710

加入索引後可以看到查詢使用了索引,且表掃描的行數下降。

5.1.4 關聯查詢優化總結

① 儘可能減少 Join 語句中的 NestedLoop 的循環次數;“用小結果集驅動大結果集”

② 優先優化 NestedLoop 的內層字段;

③ 當無法保證驅動表的 JOIN 字段被索引且內存充足的條件下,可以適當調整 JoinBuffer 設置;

5.2 索引失效

MySQL 中導致索引失效的情況:

① 在索引上做操作(計算、函數、手動/自動類型轉化)會導致索引失效轉而進行全表掃描;

② SQL 中使用 (!= 或 <>) 會導致索引失效轉而進行全表掃描;

③ SQL 中使用 IS NOT NULL 會導致索引失效轉而進行全表掃描;

④ SQL 中 LIKE 以通配符開頭 LIKE(&字符串) 會導致索引失效轉而進行全表掃描;

⑤ SQL 中字符串不加單引號會導致索引失效轉而進行全表掃描;

⑥ SQL 中使用 OR 進行連接會導致索引失效轉而進行全表掃描;

5.2.1 儘量使用全值匹配

SQL 語句中 WHERE 查詢的字段按照順序在索引中可以全部匹配。

SQL 中查詢字段的順序,跟使用索引中字段的順序沒有關係。優化器會在不影響 SQL 執行結果的前提下,進行自動優化。

5.2.2 最佳左前綴法則

SQL 中的查詢字段和索引建立順序不同會導致索引無法使用,甚至索引失效。因此 SQL 中進行條件過濾時需要按照建立索引時的順序,依次匹配跳過某個字段索引後面的字段都無法使用。如果使用複合索引,要遵守最左前綴法則。

最左前綴法則:查詢從索引最左列開始並且不跳過索引中的列。

① 查詢時完全按照索引建立順序,可以看到索引被使用且查詢均爲常量

image-20210414110129501

② 不按照索引順序進行查詢且不完全使用索引列,可以看到索引沒有完全使用

image-20210414110751812

③ 不使用索引列首位進行查詢,可以看到完全沒有使用索引進行了全表掃描

image-20210414111213976

5.2.3 索引列上不做任何操作

計算、函數、(自動/手動)類型轉換,會導致索引失效轉向全表掃描。

image-20210414111753397

SQL 中 WHERE 查詢字符串不加單引號 SQL 會進行字符串轉化,導致索引失效。

image-20210414112336601

5.2.4 索引列上不進行範圍查詢

存儲引擎不能使用索引範圍條件右邊的列,所以將可能做範圍查詢的字段索引順序放到最後。

image-20210414112959681

5.2.5 儘量使用覆蓋索引

只訪問索引的查詢(索引列和查詢列保持一致)減少 SELECT * 操作

image-20210414113524833

5.2.6 索引列上不做判空操作

MySQL 在使用 (!= 或 <>) 時會導致索引失效,導致進行全表掃描

image-20210414134630064

雖然判空操作會導致索引失效,但是具體情況需要具體分析,SQL 查詢不能僅考慮索引是否失效。

5.2.7 索引列上不做非空查詢

SQL 中在表字段允許爲 NULL 的情況下,WHERE 條件中使用 IS NULL 不會導致索引失效,但是 IS NOT NULL 會導致索引失效。

image-20210414134946352

不要進行字段非空判斷,最好給字段設置默認值。

5.2.8 正確使用模糊查詢

SQL 中 LIKE 以通配符開頭 ('%abc...') MySQL 會導致索引失效,轉而進行全表掃描。即使用 LIKE 的進行模糊匹配時,左模糊和全模糊會導致索引失效,右模糊才能使用索引。

image-20210414135735562

面試題目:解決 LIKE '%字符%' 索引不被使用的方式?建立覆蓋索引解決全模糊導致索引失效問題。

如下圖在 name、age 字段上建立索引,再進行全模糊查詢時可以使用到覆蓋索引。

image-20210414141928737

5.2.9 注意連接的使用

使用 OR 連接會導致索引失效,儘量使用 UNION 或 UNION ALL 替代。

image-20210414151523558

5.2.10 索引優化總結

全值匹配我最愛,最左原則要遵守。

帶頭大哥不能死,中間兄弟不能斷。

索引列上少計算,範圍之後全失效。

LIKE 百分寫最右,覆蓋索引不寫 *

不等空值還有 OR,索引影響要注意。

VAR 引號不可丟,SQL 優化有訣竅。

5.2.11 索引優化面試題

① 創建數據庫

create table test(
id int primary key not null auto_increment,
c1 char(10),
c2 char(10),
c3 char(10),
c4 char(10),
c5 char(10));

insert into test(c1,c2,c3,c4,c5) values ('a1','a2','a3','a4','a5');
insert into test(c1,c2,c3,c4,c5) values ('b1','b2','b3','b4','b5');
insert into test(c1,c2,c3,c4,c5) values ('c1','c2','c3','c4','c5');
insert into test(c1,c2,c3,c4,c5) values ('d1','d2','d3','d4','d5');
insert into test(c1,c2,c3,c4,c5) values ('e1','e2','e3','e4','e5');

create index idx_test03_c1234 on test03(c1,c2,c3,c4);
mysql> select * from test;
+----+------+------+------+------+------+
| id | c1   | c2   | c3   | c4   | c5   |
+----+------+------+------+------+------+
|  1 | a1   | a2   | a3   | a4   | a5   |
|  2 | b1   | b2   | b3   | b4   | b5   |
|  3 | c1   | c2   | c3   | c4   | c5   |
|  4 | d1   | d2   | d3   | d4   | d5   |
|  5 | e1   | e2   | e3   | e4   | e5   |
+----+------+------+------+------+------+
5 rows in set (0.00 sec)

② 創建索引

# 創建索引
mysql> create index idx_test_c1234 on test(c1,c2,c3,c4);
Query OK, 0 rows affected (0.03 sec)
Records: 0  Duplicates: 0  Warnings: 0
# 查詢索引
mysql> show index from test;
+-------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name       | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| test  |          0 | PRIMARY        |            1 | id          | A         |           5 |     NULL | NULL   |      | BTREE      |         |               |
| test  |          1 | idx_test_c1234 |            1 | c1          | A         |           5 |     NULL | NULL   | YES  | BTREE      |         |               |
| test  |          1 | idx_test_c1234 |            2 | c2          | A         |           5 |     NULL | NULL   | YES  | BTREE      |         |               |
| test  |          1 | idx_test_c1234 |            3 | c3          | A         |           5 |     NULL | NULL   | YES  | BTREE      |         |               |
| test  |          1 | idx_test_c1234 |            4 | c4          | A         |           5 |     NULL | NULL   | YES  | BTREE      |         |               |
+-------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
5 rows in set (0.00 se

③ 分析以下 SQL 執行情況

1)基本查詢

explain select * from test where c1 = 'a1';
explain select * from test where c1 = 'a1' and c2 = 'a2';
explain select * from test where c1 = 'a1' and c2 = 'a2' and c3 = 'a3';
explain select * from test where c1 = 'a1' and c2 = 'a2' and c3 = 'a3' and c4 = 'a4';

以上 SQL 均可以使用索引,且使用的索引列逐漸增加。

2)基本查詢

explain select * from test where c1 = 'a1' and c2 = 'a2' and c3 = 'a3' and c4 = 'a4';
explain select * from test where c1 = 'a1' and c3 = 'a3' and c2 = 'a2' and c4 = 'a4';
explain select * from test where c4 = 'a4' and c3 = 'a3' and c2 = 'a2' and c1 = 'a1';

以上的 SQL 查詢均使用四個索引列,原因在於 MySQL 內部優化器進行 SQL 優化,但是建議索引順序和查詢順序保持一致。

3)範圍查詢

explain select * from test where c1 = 'a1' and c2 = 'a2' and c3 > 'a3' and c4 = 'a4';

以上的 SQL 只能應用索引列 c1、c2、c3因爲 c3 處進行範圍查詢導致索引失效。

explain select * from test where c1 = 'a1' and c2 = 'a2' and c4 > 'a4' and c3 = 'a3';

以上的 SQL 應用索引列 c1、c2、c3,c4 因爲 c4 處進行範圍查詢導致索引失效,但是 c4 是最後一個索引列,雖然是範圍查找,但是仍使用了全部的索引列。

4)單值排序查詢

explain select * from test where c1 = 'a1' and c2 = 'a2' and c4 = 'a4' order by c3;

以上 SQL 實際使用了三個索引列 c1、c2、c3,但是僅顯示使用了 c1、c2 實際情況是 c1、c2 參與查詢操作, a3 參與排序操作。由此引出索引的兩大作用,查詢和排序。

explain select * from test where c1 = 'a1' and c2 = 'a2' order by c3;

與上面的 SQL 執行情況一致。

explain select * from test where c1 = 'a1' and c2 = 'a2' order by c4;

上面的 SQL 會出現 filesort,原因在於複合索引在 a3 處斷裂,導致後續的 a4 只能進行文件排序

5)多值排序查詢

explain select * from test where c1 = 'a1' and c5 = 'a5' order by c2,c3;

以上 SQL 僅使用到 c1 索引列,實際上 c2、c3 被用作於排序。

explain select * from test where c1 = 'a1' and c5 = 'a5' order by c3,c2;

以上的 SQL 會出現 filesort,原因在於索引在 c2 處斷裂導致索引失效轉而進行文件排序。

explain select * from test where c1 = 'a1' and c2 = 'a2' order by c2,c3;

應用兩個索引列 c1、c2,同時 c2、c3 參與排序

explain select * from test where c1 = 'a1' and c2 = 'a2' and c5 = 'a5' order by c2,c3;

與上一條 SQL 執行情況一致。

explain select * from test where c1 = 'a1' and c2 = 'a2' and c5 = 'a5' order by c3,c2;

結合 MySQL 的特性可知,c2 沒有參與排序只有 c3 參與了排序,實際使用索引列 c1、c2 進行查找,c3 進行排序索引沒有失效。

原因:MySQL 中當排序的值是個常量時,這個常量不會參與排序。

explain select * from test where c1 = 'a1' and c5 = 'a5' order by c3,c2;

以上 SQL 會出現 filesort,僅使用索引列 c1 進行查詢,在排序使用 c3 時因爲索引在 c2 處斷裂,導致索引失效轉而進行文件排序。

6)分組查詢

explain select * from test where c1 = 'a1' and c4 = 'a4' group by c2,c3;

查詢時使用索引列 c1 分組時使用索引列 c2、c3 索引沒有失效。

explain select * from test where c1 = 'a1' and c4 = 'a4' group by c3,c2;

以上 SQL 會出現 filesorttemporary,僅使用索引列 c1 進行查詢,在分組時使用 c3 時因爲索引在 c2 處斷裂,導致索引失效轉而進行文件排序。同時由於對c3 進行分組操作,MySQL 在分組前會先進行排序導致出現臨時表。

④ 面試題總結

  • 定值、範圍實際上還是排序,通常 order by 給定的是個範圍;
  • group by 一般都需要進行排序,會產生臨時表;

5.3 優化建議

① 對於單鍵索引,儘量選擇針對當前 query 過濾性更好的字段;

② 在選擇組合索引時,當前 query 中過濾性最好的字段在索引字段順序中越靠前越好;

③ 在選擇組合索引時,儘量選擇能包含當前 queryWHERE 子句中更多字段的索引;

④ 儘可能通過分析統計信息和調整 query 寫法來達到選擇合適索引的目的;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章