MySQL索引那些事兒

1.B+樹

  • 一個節點有多個元素
  • 所有元素都在葉子節點冗餘
  • 葉子節點間有指針且有序

推薦一個B+樹演示網站 https://www.cs.usfca.edu/~galles/visualization/BPlusTree.html

2.InnoDB數據引擎中的頁

默認頁大小爲16kb

mysql> show global status like 'Innodb_page_size';
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| Innodb_page_size | 16384 |
+------------------+-------+
1 row in set (0.01 sec)

mysql> select 16384/1024;
+------------+
| 16384/1024 |
+------------+
|    16.0000 |
+------------+
1 row in set (0.01 sec)

3.主鍵索引

創建表,並插入數據

CREATE TABLE `t1` (
  `a` int(11) NOT NULL,
  `b` int(11) DEFAULT NULL,
  `c` int(11) DEFAULT NULL,
  `d` int(11) DEFAULT NULL,
  `e` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`a`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `t1` 
VALUES 
('6', '6', '4', '4', 'f'), 
('1', '1', '1', '1', 'a'), 
('2', '2', '2', '2', 'b'), 
('7', '4', '5', '5', 'g'), 
('4', '3', '1', '1', 'd'), 
('5', '2', '3', '6', 'e'), 
('3', '3', '2', '2', 'c'), 
('8', '8', '8', '8', 'h');
COMMIT;

以主鍵數據創建的B+樹,即主鍵索引

查詢索引

mysql> show index from t1;

+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| t1    |          0 | PRIMARY  |            1 | a           | A         |           7 |     NULL | NULL   |      | BTREE      |         |               |
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
1 row in set (0.00 sec)
  • 如果建表時,
    • 有主鍵,建立主鍵索引
    • 無主鍵
      • 先判斷是否有唯一索引
        • 如果有唯一索引,創建唯一索引
        • 如果沒有唯一索引,創建一個隱藏字段作爲唯一索引,即rowid

瞭解了主鍵索引,就可以順理成章的分析出,使用uuid作爲主鍵的劣勢了:

  • uuid佔用空間大,在存儲相同數據量的情況下,需要使用更多的頁來存儲,影響了B+樹的高度,影響查詢效率。
  • uuid無需,相較於自增主鍵,每次插入都要重新排序。

4.聯合索引

mysql> create index idx_t1_bcd on t1(b,c,d);
Query OK, 0 rows affected (0.04 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> show index from t1;
+-------+------------+------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name   | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-------+------------+------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| t1    |          0 | PRIMARY    |            1 | a           | A         |           7 |     NULL | NULL   |      | BTREE      |         |               |
| t1    |          1 | idx_t1_bcd |            1 | b           | A         |           6 |     NULL | NULL   | YES  | BTREE      |         |               |
| t1    |          1 | idx_t1_bcd |            2 | c           | A         |           7 |     NULL | NULL   | YES  | BTREE      |         |               |
| t1    |          1 | idx_t1_bcd |            3 | d           | A         |           7 |     NULL | NULL   | YES  | BTREE      |         |               |
+-------+------------+------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
4 rows in set (0.00 sec)

按照bcd的值創建B+樹,在葉子節點上,把這條數據對應的主鍵a的值也關聯上,對應起來。

當select後字段不在abcd字段裏時,需要根據查到的主鍵值二次查詢,就是平時我們說的回表。

在這裏就可以看到,爲什麼要有“最左前綴原則”了,因爲如果查詢是最左側的不能確定,沒辦法從在B+樹上確定位置,就沒有辦法走索引了。

 

5.幾個具體實例

5.1 key_len值

mysql> explain select * from t1 where a = 3;
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref   | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | t1    | NULL       | const | PRIMARY       | PRIMARY | 4       | const |    1 |   100.00 | NULL  |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

mysql> explain select * from t1 where b = 1 and c = 1 and d = 1;
+----+-------------+-------+------------+------+---------------+------------+---------+-------------------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key        | key_len | ref               | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------------+---------+-------------------+------+----------+-------+
|  1 | SIMPLE      | t1    | NULL       | ref  | idx_t1_bcd    | idx_t1_bcd | 15      | const,const,const |    2 |   100.00 | NULL  |
+----+-------------+-------+------------+------+---------------+------------+---------+-------------------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

mysql> explain select * from t1 where b = 1 and d = 1;
+----+-------------+-------+------------+------+---------------+------------+---------+-------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key        | key_len | ref   | rows | filtered | Extra                 |
+----+-------------+-------+------------+------+---------------+------------+---------+-------+------+----------+-----------------------+
|  1 | SIMPLE      | t1    | NULL       | ref  | idx_t1_bcd    | idx_t1_bcd | 5       | const |    2 |    14.29 | Using index condition |
+----+-------------+-------+------------+------+---------------+------------+---------+-------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)

3條SQL語句

  • 第1條走主鍵索引
    • 其中key_len長度爲4,int類型在MySQL中佔4字節
  • 第2條走bcd聯合索引
    • 其中key_len長度爲15,int類型在MySQL中佔4字節,再加上可能爲null的情況(MySQL需要1個字節來標識NULL),而且此次查詢使用到了bcd3個字段,所以(4+1)*3=15
  • 第3條走bcd聯合索引
    • 其中Extra,Using index condition,即沒有全部使用到bcd3個字段,只用到了b字段
    • key_len長度爲15,int類型在MySQL中佔4字節,再加上可能爲null的情況(MySQL需要1個字節來標識NULL),4+1=5

5.2 回表

mysql> explain select * from t1 where b > 1; 
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | t1    | NULL       | ALL  | idx_t1_bcd    | NULL | NULL    | NULL |    7 |   100.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

mysql> explain select * from t1 where b > 7;
+----+-------------+-------+------------+-------+---------------+------------+---------+------+------+----------+-----------------------+
| id | select_type | table | partitions | type  | possible_keys | key        | key_len | ref  | rows | filtered | Extra                 |
+----+-------------+-------+------------+-------+---------------+------------+---------+------+------+----------+-----------------------+
|  1 | SIMPLE      | t1    | NULL       | range | idx_t1_bcd    | idx_t1_bcd | 5       | NULL |    1 |   100.00 | Using index condition |
+----+-------------+-------+------------+-------+---------------+------------+---------+------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)

兩條SQL語句,差異只在b大於的常量值,但是第1條沒走索引,第2條走索引了。

這裏是索引優化器,在走索引或全表掃描二選一的結果。

如果走索引,回表次數過多時,性能可能還不如直接全表掃描。

5.3 覆蓋索引

mysql> explain select * from t1 where b > 1;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | t1    | NULL       | ALL  | idx_t1_bcd    | NULL | NULL    | NULL |    7 |   100.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

mysql> explain select b,c,d from t1 where b > 1;
+----+-------------+-------+------------+-------+---------------+------------+---------+------+------+----------+--------------------------+
| id | select_type | table | partitions | type  | possible_keys | key        | key_len | ref  | rows | filtered | Extra                    |
+----+-------------+-------+------------+-------+---------------+------------+---------+------+------+----------+--------------------------+
|  1 | SIMPLE      | t1    | NULL       | index | idx_t1_bcd    | idx_t1_bcd | 15      | NULL |    7 |   100.00 | Using where; Using index |
+----+-------------+-------+------------+-------+---------------+------------+---------+------+------+----------+--------------------------+
1 row in set, 1 warning (0.00 sec)

mysql> explain select b,c,d,a from t1 where b > 1;
+----+-------------+-------+------------+-------+---------------+------------+---------+------+------+----------+--------------------------+
| id | select_type | table | partitions | type  | possible_keys | key        | key_len | ref  | rows | filtered | Extra                    |
+----+-------------+-------+------------+-------+---------------+------------+---------+------+------+----------+--------------------------+
|  1 | SIMPLE      | t1    | NULL       | index | idx_t1_bcd    | idx_t1_bcd | 15      | NULL |    7 |   100.00 | Using where; Using index |
+----+-------------+-------+------------+-------+---------------+------------+---------+------+------+----------+--------------------------+
1 row in set, 1 warning (0.00 sec)

第1條SQL查詢*所有字段,查詢出來後需要回表,索引優化器對比後,發現全表掃描比先索引再回表性能更優,直接全表掃描。

第2條走bcd聯合索引,比較好理解。

第3條,也走bcd聯合索引,因爲在bcd聯合索引的B+樹上,關聯了主鍵索引,而且查詢的是bcda字段,所以不需要回表,直接走索引就可以查詢出來,也就是平時所說的“覆蓋索引”。

覆蓋索引(Covering Index):包含所有滿足查詢需要數據的索引。

5.4 類型轉換

在MySQL中,會默認把字符轉換爲數字,如果字符不是數字,則轉換爲0。

mysql> select 0='0';
+-------+
| 0='0' |
+-------+
|     1 |
+-------+
1 row in set (0.00 sec)

mysql> select 0='1';
+-------+
| 0='1' |
+-------+
|     0 |
+-------+
1 row in set (0.01 sec)

mysql> select 0='a';
+-------+
| 0='a' |
+-------+
|     1 |
+-------+
1 row in set, 1 warning (0.00 sec)

mysql> select 0='ab';
+--------+
| 0='ab' |
+--------+
|      1 |
+--------+
1 row in set, 1 warning (0.00 sec)

插入1條數據

INSERT INTO `t1` VALUES ('0', '9', '9', '9', 'i');
mysql> select * from t1 where a = 'abcd';
+---+------+------+------+------+
| a | b    | c    | d    | e    |
+---+------+------+------+------+
| 0 |    9 |    9 |    9 | i    |
+---+------+------+------+------+
1 row in set, 1 warning (0.00 sec)

mysql> select * from t1 where a = 0;
+---+------+------+------+------+
| a | b    | c    | d    | e    |
+---+------+------+------+------+
| 0 |    9 |    9 |    9 | i    |
+---+------+------+------+------+
1 row in set (0.00 sec)

MySQL會自動把'abcd'轉換爲0,所以以上2條SQL等價,都把a=0的數據查詢出來了。

5.5 索引及類型轉換

給e字段增加索引

create index idx_t1_e on t1(e);
explain select * from t1 where a = 1; 	-- 走索引
explain select * from t1 where a = '1';	-- 走索引
explain select * from t1 where e = 1;	-- 不走索引
explain select * from t1 where e = '1';	-- 走索引
  • 對於1、2SQL來講,a字段爲int類型
    • 等號右側爲數字時,不需要類型轉換,直接走索引
    • 等號右側爲字符時,會自動轉換爲數字,類型一致,走索引
  • 對於3、4SQL來講,e字段爲varchar類型
    • 等號右側爲數字時,沒辦法從數字轉換爲字符,等號左側爲字符,右側爲數字,只能將e字段轉換爲數字,因爲B+樹中存儲的是字符,類型不一致導致無法比對,只能全表掃描
    • 等號右側爲字符時,不需要類型轉換,直接走索引
mysql> explain select * from t1 where a = 1;
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref   | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | t1    | NULL       | const | PRIMARY       | PRIMARY | 4       | const |    1 |   100.00 | NULL  |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

mysql> explain select * from t1 where a = '1';
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref   | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | t1    | NULL       | const | PRIMARY       | PRIMARY | 4       | const |    1 |   100.00 | NULL  |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

mysql> explain select * from t1 where e = 1;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | t1    | NULL       | ALL  | idx_t1_e      | NULL | NULL    | NULL |    9 |    11.11 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 3 warnings (0.00 sec)

mysql> explain select * from t1 where e = '1';
+----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key      | key_len | ref   | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | t1    | NULL       | ref  | idx_t1_e      | idx_t1_e | 63      | const |    1 |   100.00 | NULL  |
+----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

在where子句中,等號左側,不要對字段進行運算或操作,否則無法走索引,導致全表掃描,性能下降。

 

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