【MySQL】覆蓋索引和回表


  • 先來了解一下兩大類索引
    • 聚簇索引(也稱聚集索引,主鍵索引等)
    • 普通索引(也成非聚簇索引,二級索引等)

 

  • 聚簇索引
    • 如果表設置了主鍵,則主鍵就是聚簇索引
    • 如果表沒有主鍵,則會默認第一個NOT NULL,且唯一(UNIQUE)的列作為聚簇索引
    • 以上都沒有,則會默認創建一個隱藏的row_id作為聚簇索引
InnoDB的聚簇索引的葉子節點存儲的是行記錄(其實是頁結構,一個頁包含多行數據),InnoDB必須要有至少一個聚簇索引。
由此可見,使用聚簇索引查詢會很快,因為可以直接定位到行記錄。

 

  • 普通索引
普通索引也叫二級索引,除聚簇索引外的索引,即非聚簇索引。
InnoDB的普通索引葉子節點存儲的是主鍵(聚簇索引)的值,而MyISAM的普通索引存儲的是記錄指針。

 

 

請看如下示例:

  • 建表
 CREATE TABLE IF NOT EXISTS `user`(
    -> `id` INT UNSIGNED AUTO_INCREMENT,
    -> `name` VARCHAR(60),
    -> `age` TINYINT(4),
    -> PRIMARY KEY (id),
    -> INDEX idx_age (age)
    -> )ENGINE=innodb charset=utf8mb4;

# id 字段是聚簇索引,age 字段是普通索引(二級索引)

 

  • 隨便加幾個數據
insert into user(name,age) values('張三',30);
insert into user(name,age) values('李四',20);
insert into user(name,age) values('王五',40);
insert into user(name,age) values('劉八',10);
mysql> select * from user;
+----+------+-----+
| id | name | age |
+----+------+-----+
|  1 | 張三 |  30 |
|  2 | 李四 |  20 |
|  3 | 王五 |  40 |
|  4 | 劉八 |  10 |
+----+------+-----+
4 rows in set (0.06 sec)

 

  • 索引存儲結構
id 是主鍵,所以是聚簇索引,其葉子節點存儲的是對應行記錄的數據

 

age 是普通索引(二級索引),非聚簇索引,其葉子節點存儲的是聚簇索引的的值

 

 

如果查詢條件為主鍵(聚簇索引),則只需掃描一次B+樹即可通過聚簇索引定位到要查找的行記錄數據。
如:select * from user where id = 1;

 

 

如果查詢條件為普通索引(非聚簇索引),需要掃描兩次B+樹,第一次掃描通過普通索引定位到聚簇索引的值,然后第二次掃描通過聚簇索引的值定位到要查找的行記錄數據。
如:select * from user where age = 30;

1》先通過普通索引【age=30】定位到主鍵值 【id=1】

 

 

 2》在通過聚集索引【id=1】定位到行記錄數據

 

 

  • 回表查詢
先通過普通索引的值定位到聚簇索引值,在通過聚簇索引的值定位到行記錄數據,要通過掃描兩次索引B+樹,它的性能較掃描一次較低

 

  •  索引覆蓋
只需在一顆索引樹上就能獲取SQL所需的所有列數據,無需回表,速度更快。
例如:select id,age from user where age = 10;

 

  • 如何實現覆蓋索引
常見的方法是:將被查詢的字段,建立到聯合索引里去(若查詢有where條件,同時where條件字段也必須為索引字段)。
1》如實現:select id,age from user where age = 10;
explain分析:因為age是普通索引,使用到了age索引,通過一次掃描B+樹即可查詢到相應的結果,這樣就實現了覆蓋索引
此時的Extra列的【Using Index】表示進行了聚簇索引
mysql> explain select id,age from user where age = 10;
+----+-------------+-------+------------+------+---------------+---------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key     | key_len | ref   | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+---------+---------+-------+------+----------+-------------+
|  1 | SIMPLE      | user  | NULL       | ref  | idx_age       | idx_age | 2       | const |    1 |   100.00 | Using index |
+----+-------------+-------+------------+------+---------------+---------+---------+-------+------+----------+-------------+
1 row in set (0.07 sec)

 

2》如實現:select id,age,name from user where age = 10;
explain分析:age是普通索引,但name列不在索引樹上,所以通過age索引在查詢到id和age的值后,需要進行回表再查詢name的值。
此時的Extra列的NULL表示進行了回表查詢
mysql> explain select id,age,name from user where age = 10;
+----+-------------+-------+------------+------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key     | key_len | ref   | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+---------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | user  | NULL       | ref  | idx_age       | idx_age | 2       | const |    1 |   100.00 | NULL  |
+----+-------------+-------+------------+------+---------------+---------+---------+-------+------+----------+-------+
1 row in set (0.05 sec)


explain 使用方式如下:
EXPLAIN +SQL語句
如:EXPLAIN SELECT * FROM t1

 

  • 為了實現索引覆蓋,需要建組合索引idx_age_name(age,name)
mysql> drop index idx_age on user;
mysql> create index idx_age_name on user(`age`,`name`);

我們再次EXPLAIN分析一次:

mysql> explain select id,age,name from user where age = 10;
+----+-------------+-------+------------+------+---------------+--------------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key          | key_len | ref   | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+--------------+---------+-------+------+----------+-------------+
|  1 | SIMPLE      | user  | NULL       | ref  | idx_age_name  | idx_age_name | 2       | const |    1 |   100.00 | Using index |
+----+-------------+-------+------------+------+---------------+--------------+---------+-------+------+----------+-------------+
1 row in set (0.05 sec)

#可見Extra的值為【Using Index】,表示使用的覆蓋索引

 

哪些場景適合使用索引覆蓋來優化SQL:

  • 全表count查詢優化
mysql> explain select count(age) from user;
+----+-------------+-------+------------+-------+---------------+--------------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type  | possible_keys | key          | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+-------+---------------+--------------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | user  | NULL       | index | NULL          | idx_age_name | 245     | NULL |    4 |   100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+--------------+---------+------+------+----------+-------------+
1 row in set (0.06 sec)

 

  • 分頁查詢
mysql> explain select id,age,name from user order by age limit 100,2;
+----+-------------+-------+------------+-------+---------------+--------------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type  | possible_keys | key          | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+-------+---------------+--------------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | user  | NULL       | index | NULL          | idx_age_name | 245     | NULL |    4 |   100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+--------------+---------+------+------+----------+-------------+
1 row in set (0.06 sec)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM