- 先來了解一下兩大類索引
- 聚簇索引(也稱聚集索引,主鍵索引等)
- 普通索引(也成非聚簇索引,二級索引等)
- 聚簇索引
- 如果表設置了主鍵,則主鍵就是聚簇索引
- 如果表沒有主鍵,則會默認第一個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)