MySQL索引及執行計划


索引

在mysql中稱之為鍵, 一種數據結果, 幫助減少SQL語句經歷的IO次數

一. Mysql 查找數據的兩種方式

  • 全表遍歷掃描
  • 通過索引查找算法進行遍歷掃描

二. 索引作用

提供了類似書中目錄的作用, 目的是為了優化查詢

三. 索引種類

根據不同的算法進行划分

  • B樹索引
  • Hash索引
  • R樹
  • Full text 全文索引
  • GIS

hash索引和BTree索引

hash類型的索引:查詢單條快,范圍查詢慢
btree類型的索引:b+樹,層數越多,數據量指數級增長(我們就用它,因為innodb默認支持它)

B樹索引分類

  • B-TREE
  • B+TREE, 默認工作模式(算法)
  • B*TREE

B+樹的工作原理


b+樹的查找過程

如圖所示,如果要查找數據項29,那么首先會把磁盤塊1(根節點)由磁盤加載到內存,此時發生一次IO,確定29是大於5和大於28這兩個數據像,最后肯定是選擇與29這個數更接近的28這個磁盤像並鎖定磁盤塊1的P2指針,內存時間因為非常短(相比磁盤的IO)可以忽略不計,通過磁盤塊1的P2指針的磁盤地址把磁盤塊3由磁盤加載到內存,發生第二次IO,29大於28,所以只能選擇28這個磁盤像並鎖定磁盤塊3的P1指針,通過指針加載磁盤塊8到內存,發生第三次IO,同時內存中做二分查找找到29,結束查詢,總計三次IO。真實的情況是,3層的b+樹可以表示上百萬的數據,如果上百萬的數據查找只需要三次IO,性能提高將是巨大的,如果沒有索引,每個數據項都要發生一次IO,那么總共需要百萬次的IO,顯然成本非常非常高


b+樹性質

1.索引字段要盡量的少:如果只需要檢索一個id, 正常的只會進行3次IO, 而如果檢索多個,則肯定是需要超過3次IO的
2.索引的最左匹配特性:當b+樹的數據項是復合的數據結構,比如(name,age,sex)的時候,b+數是按照從左到右的順序來建立搜索樹的,比如當(張三,20,F)這樣的數據來檢索的時候,b+樹會優先比較name來確定下一步的所搜方向,如果name相同再依次比較age和sex,最后得到檢索的數據;但當(20,F)這樣的沒有name的數據來的時候,b+樹就不知道下一步該查哪個節點,因為建立搜索樹的時候name就是第一個比較因子,必須要先根據name來搜索才能知道下一步去哪里查詢。比如當(張三,F)這樣的數據來檢索時,b+樹可以用name來指定搜索方向,但下一個字段age的缺失,所以只能把名字等於張三的數據都找到,然后再匹配性別是F的數據了, 這個是非常重要的性質,即索引的最左匹配特性。


MySQL Btree 種類細分

輔助索引(二級索引)

人為操作最多的, 創建在表中的列上

**創建過程: **

  • 創建索引時, 選擇表中的某個列作為索引鍵(key)

  • 會將整個列的值提取出來, 做排序

  • 將排序后的值,軍訓的分布到BTree索引的"葉子節點"中, 進而生成"枝節點", 最終生成"根節點"

  • "葉子節點"同時會存儲原表的數據行的指針, 進而找到行中的其他列的數據, 這個動作叫回表查詢(隨機Io)

    隨機Io: 對磁盤上進行遍歷


覆蓋索引(聯合索引)

分析業務, 將大部分的數據查詢的列, 聯合起來建立B樹索引, 可以大量減少回表查詢, 從而減少隨機IO


唯一索引

列的值必須是不重復的


聚集索引(主鍵索引, 建表時創建)

生成條件:

  1. 會選擇主鍵列作為聚集索引列(一般主鍵是在建表時加入)
  2. 沒有主鍵, 會選擇唯一鍵作為聚集索引列

結構:

  1. 按照聚集索引列的值的順序存儲數據項形成葉子節點
  2. 枝節點和根節點只存儲下層的最小值和指針

索引的高度

索引其實也是表, 也占磁盤空間

  • 數據行數越多, 高度越高

    優化方案

    • 表分區(一般以800w行作為標准, 比較早期的解決方案)
    • 分布式架構(MyCat, TDDL, DBLE,DRDS)

  • 索引列的值如果很長的時候, 高度越高

    優化方案

    • 前綴索引

    • 使用varchar()數據類型

      變長長度列, 使用char()類型, 如果索引數據的長度小於char()數據類型設置的長度, 則剩下的就會使用空格填充, 這樣在計算索引長度的時候會把索引數據本身長度和填充的空格也會算進去, 所以使用varchar()


無索引和有索引的區別

無索引: 從前往后一條一條查詢
有索引:創建索引的本質,就是創建額外的文件(某種格式存儲,查詢的時候,先去格外的文件找,定好位置,然后再去原始表中直接查詢。但是創建索引越多,會對硬盤也是有損耗

建立索引的目的

  • 保存特殊的數據結構
  • 查詢快,但是插入更新刪除依然慢
  • 創建索引之后,必須命中索引才能有效

四. 索引案例

據庫表中添加索引后確實會讓查詢速度起飛,但前提必須是正確的使用索引來查詢,如果以錯誤的方式使用,則即使建立索引也會不奏效。

使用索引,我們必須知道:

  • 創建索引
  • 命中索引
  • 正確使用索引

案例

 准備300W條數據:

1. 准備數據庫並切換
MariaDB [(none)]> create database s18 charset='utf8';
MariaDB [(none)]> use s18;
MariaDB [s18]>

2. 准備表
MariaDB [s18]> use s18;create table userinfo(
Database changed
    -> id int,
    -> name varchar(20),
    -> gender char(6),
    -> email varchar(50)
    -> )
    -> ;

MariaDB [s18]> desc userinfo;
+--------+-------------+------+-----+---------+-------+
| Field  | Type        | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| id     | int(11)     | YES  |     | NULL    |       |
| name   | varchar(20) | YES  |     | NULL    |       |
| gender | char(6)     | YES  |     | NULL    |       |
| email  | varchar(50) | YES  |     | NULL    |       |
+--------+-------------+------+-----+---------+-------+

2. 創建存儲過程,實現批量插入記錄
MariaDB [s18]> delimiter $$
MariaDB [s18]> create procedure auto_insert1()
    -> BEGIN
    -> declare i int default 1;
    -> while(i<3000000)do
    -> insert into userinfo values(i,concat('alex',i),'male',concat('egon',i,'@oldboy'));
    -> set i=i+1;
    -> end while;
    -> END$$
MariaDB [s18]> delimiter ;

3. 查看存儲過程
MariaDB [s18]> show create procedure auto_insert1\G
*************************** 1. row ***************************
           Procedure: auto_insert1
            sql_mode: NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION
    Create Procedure: CREATE DEFINER=`root`@`localhost` PROCEDURE `auto_insert1`()
BEGIN
declare i int default 1;
while(i<3000000)do
insert into userinfo values(i,concat('alex',i),'male',concat('egon',i,'@oldboy'));
set i=i+1;
end while;
END
character_set_client: utf8
collation_connection: utf8_general_ci
  Database Collation: latin1_swedish_ci

4. 調用存儲過程
MariaDB [s18]> call auto_insert1();
Query OK, 1 row affected (8 min 36.25 sec)

5. 查看數據的數量
MariaDB [s18]> select count(2) from userinfo;
+----------+
| count(2) |
+----------+
|  2999999 |
+----------+
1 row in set (1.20 sec)

未加索引的查詢效率

MariaDB [s18]> select * from userinfo where name='alex2000000';
+---------+-------------+--------+--------------------+
| id      | name        | gender | email              |
+---------+-------------+--------+--------------------+
| 2000000 | alex2000000 | male   | egon2000000@oldboy |
+---------+-------------+--------+--------------------+
1 row in set (1.41 sec)

MariaDB [s18]> select * from userinfo where id=2000000;
+---------+-------------+--------+--------------------+
| id      | name        | gender | email              |
+---------+-------------+--------+--------------------+
| 2000000 | alex2000000 | male   | egon2000000@oldboy |
+---------+-------------+--------+--------------------+
1 row in set (1.38 sec)

添加索引字段

添加索引需要耗費時間, 索引也是一種數據結構, 所以創建需要時間

MariaDB [s18]> create index ix_id on userinfo(id);
Query OK, 0 rows affected (6.10 sec)                
Records: 0  Duplicates: 0  Warnings: 0

MariaDB [s18]> show index from userinfo\G;
*************************** 1. row ***************************
        Table: userinfo
   Non_unique: 1
     Key_name: ix_id
 Seq_in_index: 1
  Column_name: id
    Collation: A
  Cardinality: 2984616
     Sub_part: NULL
       Packed: NULL
         Null: YES
   Index_type: BTREE
      Comment: 
Index_comment: 
1 row in set (0.00 sec)

添加索引后的查詢效率

MariaDB [s18]> select * from userinfo where name='alex2000000';
+---------+-------------+--------+--------------------+
| id      | name        | gender | email              |
+---------+-------------+--------+--------------------+
| 2000000 | alex2000000 | male   | egon2000000@oldboy |
+---------+-------------+--------+--------------------+
1 row in set (1.74 sec)

MariaDB [s18]> select * from userinfo where id=2000000;
+---------+-------------+--------+--------------------+
| id      | name        | gender | email              |
+---------+-------------+--------+--------------------+
| 2000000 | alex2000000 | male   | egon2000000@oldboy |
+---------+-------------+--------+--------------------+
1 row in set (0.02 sec)

五. 索引的基本管理

  • 創建表
MariaDB [s18]> create table city (id int primary key auto_increment, name varchar(128), countrycode varchar(4) not null, District varchar(20) not null, population int not null);

輔助索引管理

  • 將name字段設置成輔助索引
MariaDB [s18]> alter table city add index idx_name(name);

  • 刪除索引
MariaDB [s18]> alter table city drop index idx_name;

  • 查詢索引
MariaDB [s18]> show index from city\G;
*************************** 1. row ***************************
        Table: city
   Non_unique: 0
     Key_name: PRIMARY
 Seq_in_index: 1
  Column_name: id
    Collation: A
  Cardinality: 0
     Sub_part: NULL
       Packed: NULL
         Null: 
   Index_type: BTREE
      Comment: 
Index_comment: 

  • 查看創建索引的幫助信息
MariaDB [s18]> help create index

覆蓋索引管理(聯合索引)管理

  • 將countrycode和population 做組合索引
MariaDB [s18]> alter table city add index idx_co_po(countrycode,population);

前綴索引

MariaDB [s18]> alter table city add index idx_name(name(10));

唯一索引

  • 創建
MariaDB [s18]> alter table city add unique index idx_name(name);
Query OK, 0 rows affected (0.00 sec)
Records: 0  Duplicates: 0  Warnings: 0

MariaDB [s18]> desc city;
+-------------+--------------+------+-----+---------+----------------+
| Field       | Type         | Null | Key | Default | Extra          |
+-------------+--------------+------+-----+---------+----------------+
| id          | int(11)      | NO   | PRI | NULL    | auto_increment |
| name        | varchar(128) | YES  | UNI | NULL    |                |
| countrycode | varchar(4)   | NO   |     | NULL    |                |
| District    | varchar(20)  | NO   |     | NULL    |                |
| population  | int(11)      | NO   |     | NULL    |                |
+-------------+--------------+------+-----+---------+----------------+


執行計划

select獲取數據的方法:

  1. 全表掃苗
  2. 索引掃描

一. 執行計划的作用

SQL最終獲取的方法


二. 執行計划獲取

獲取優化器選擇后的執行計划

MariaDB [s18]> explain select * from userinfo where id=1\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: userinfo
         type: ref
possible_keys: ix_id
          key: ix_id
      key_len: 5
          ref: const
         rows: 1
        Extra: 
1 row in set (0.00 sec)

需要關注的信息

 table: userinfo				------>查詢操作的表
  type: ref							------>索引類型
possible_keys: ix_id		------>可能會走的索引
          key: ix_id		------>真正走的索引
           Extra: 			------>額外信息

type詳解

  • ALL 全表掃描

    注意:  生產中幾乎沒有這種需求的,盡量避免

explain select * from userinfo;


  • INDEX 全索引掃描

    注意: 生產中幾乎沒有這種需求的,盡量避免

desc select id from userinfo;


  • RANGE 索引范圍掃描
    1. select 范圍查詢: > < >= <=
    2. in, or

desc select * from userinfo where id<10;


  • REF 輔助索引的等值查詢

desc select * from userinfo where id=1union allselect * from userinfo where id=2;


  • eq_ref 多表連接查詢中, 連接條件是主鍵或者是唯一鍵的時候

  • system,const 主鍵或者唯一鍵的等值查詢
為name字段創建唯一鍵
MariaDB [s18]> alter table userinfo add unique index idx_name(name);
Query OK, 0 rows affected (10.76 sec)

MariaDB [s18]> desc select * from userinfo where name='alex1';


  • NULL 索引中不包含查詢的值

MariaDB [s18]> desc select * from userinfo where name='alexdddd1';


結論: 在索引掃描類型方面, 至少保證在range以上級別


其他字段解釋

extra: Using filesort

MariaDB [s18]> show index from userinfo;
MariaDB [s18]> alter table userinfo drop index ix_id;
MariaDB [s18]> select * from userinfo where id between 10 and 20 order by name;
+------+--------+--------+---------------+
| id   | name   | gender | email         |
+------+--------+--------+---------------+
|   10 | alex10 | male   | egon10@oldboy |
|   11 | alex11 | male   | egon11@oldboy |
|   12 | alex12 | male   | egon12@oldboy |
|   13 | alex13 | male   | egon13@oldboy |
|   14 | alex14 | male   | egon14@oldboy |
|   15 | alex15 | male   | egon15@oldboy |
|   16 | alex16 | male   | egon16@oldboy |
|   17 | alex17 | male   | egon17@oldboy |
|   18 | alex18 | male   | egon18@oldboy |
|   19 | alex19 | male   | egon19@oldboy |
|   20 | alex20 | male   | egon20@oldboy |
+------+--------+--------+---------------+

desc select * from userinfo where id between 10 and 20 order by name;

解決思路: 

索引可以減少排序, 可以很大程度減少CPU時間

輔助索引 應用順序(優化器選擇的)

如果查詢條件符合覆蓋索引的順序時, 優先選擇覆蓋索引

不符合順序, 優先會走where條件的索引

優化方法: , 將where列和order列建立聯合索引

alter table userinfo add index idx_id_name(id,name);


explain使用場景(面試題)

公司的業務慢, 從數據庫的角度分析原因

  1. mysql出現性能問題(排除硬件,架構原因,參數,鎖), 有2種情況

    • 應急性的慢

      數據庫hang住了

    處理過程

    1. show processlist; 獲取到導致數據庫hang住的語句
    2. explain 分析SQL的執行計划, 有沒有走索引, 如果走了, 查看索引的類型情況
    3. 建索引, 改語句

  2. 一段時間慢

    處理過程

    1. 記錄慢日志showlog, 分析showlog
    2. explain 分析SQL的執行計划, 有沒有走索引, 如果走了, 查看索引的類型情況
    3. 建索引, 改語句

索引應用規范


建立索引的原則(運維規范)

為了使索引的使用效率更高, 在創建索引時, 必須考慮在哪些字段上創建索引和創建什么類型的索引


  • 建表時一定要有主鍵, 如果相關列不可以作為主鍵, 做一個無關列, 一般都是id列


  • 選擇唯一的索引

    唯一性索引的值是唯一的, 可以更快速的通過該索引來確定某條記錄

    例如:

    ​ 學生表中學號是具有唯一性的字段. 為該字段建立唯一性索引可以很快的確定某個學生的信息

    ​ 如果選擇姓名的話, 可能存在同名現象, 從而降低查詢速度


    主鍵索引和唯一鍵索引, 在查詢中使用是效率最高的


  • 為經常需要排序, 分組和聯合操作的字段建立索引

    經常需要ORDER BY, GROUP BY, join on等操作的字段, 排序操作會浪費很多時間

    如果為其建立索引, 可以有效地避免排序操作


  • 為經常作為where查詢條件的字段建立索引

    如果某個字段經常用來做查詢條件, 那么該字段的查詢速度會影響整個表的查詢速度,

    因此為這樣的字段建立索引, 可以提高整個表的查詢速度

    where后面查詢的字段符合的要走: 

    • 經常查詢
    • 列值的重復值少(業務層面調整)

  • 盡量使用前綴來索引

    如果索引字段的值很長, 最好使用值的前綴來索引


  • 限制索引的數目

    索引的數目不是越多越好. 每個索引都需要占用磁盤空間, 索引越多, 需要的磁盤空間就越大

    修改表時, 對索引的重構和更新很麻煩. 越多的索引, 會使更新表變得浪費時間


  • 刪除不再使用或者很少使用的索引

    表中的數據被大量更新, 或者數據的使用方式被改變后, 原有的一些索引可能不再需要

    數據庫管理員應當定期找出這些索引, 將他們刪除, 從而減少對更新操作的影響


  • 大表加索引, 要在業務不繁忙期間操作

  • 少在經常更新值的列上建索引

不走索引的情況(開發規范)

  • 沒有查詢條件, 或者查詢條件沒有建立索引

    在業務數據庫中, 特別是數據量比較大的表, 是沒有全表掃描這種需求的


    1. 對用戶查看是非常痛苦的
    2. 對服務器來講毀滅性的

    • select * from tab;

    SQL 改寫

    select * from tab order by price limit 10; 需要在price列上建立索引


    • select * from tab where name='zhangsan'; name列沒有索引

    修改

    1. 將有索引條件的列作為查詢條件
    2. 對name列建立索引

  • 查詢結果集是原表中的大部分數據, 大約在25%以上

    查詢結果集, 就是select查詢輸出的結果, 如果這個結果超過了總行數的25%, 優化器覺得沒有必要走索引


  • 索引本身失效,統計數據不真實

    索引有自我維護的能力

    對於表內容變化比較頻繁的情況下, 有可能會出現索引失效


  • 查詢條件使用函數在索引列上, 或者對索引列進行運算(+,-,*,/,子查詢)

    錯誤的例子: select * from test where id-1=9;

    正確d額例子: select * from test where id=10;


  • 隱式轉換導致索引失效, 在開發中經常會犯的錯誤

    比如說索引字段是字符串, 通過某個函數把字符串轉換為數字, 再利用where條件使用索引查詢, 這種情況也是不走索引的


  • <>, not in 不走索引

  • like "%_" 百分號在前面不走

%linux%類的搜索需求, 可以用elasticsearch+mongodb專門做搜索服務的數據


  • 單獨引用聯合索引里非第一位置的索引列, 作為條件查詢時不走索引
MariaDB [s18]> create table t1(
    -> id int,
    -> name varchar(20),
    -> age int,
    -> sex enum('male','female'),
    -> money int
    -> );
Query OK, 0 rows affected (0.02 sec)

MariaDB [s18]> alter table t1 add index t1_idx(money,age,sex);
Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0

MariaDB [s18]> desc t1;
+-------+-----------------------+------+-----+---------+-------+
| Field | Type                  | Null | Key | Default | Extra |
+-------+-----------------------+------+-----+---------+-------+
| id    | int(11)               | YES  |     | NULL    |       |
| name  | varchar(20)           | YES  |     | NULL    |       |
| age   | int(11)               | YES  |     | NULL    |       |
| sex   | enum('male','female') | YES  |     | NULL    |       |
| money | int(11)               | YES  | MUL | NULL    |       |
+-------+-----------------------+------+-----+---------+-------+
5 rows in set (0.00 sec)

MariaDB [s18]> 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    |          1 | t1_idx   |            1 | money       | A         |           0 |     NULL | NULL   | YES  | BTREE      |         |               |
| t1    |          1 | t1_idx   |            2 | age         | A         |           0 |     NULL | NULL   | YES  | BTREE      |         |               |
| t1    |          1 | t1_idx   |            3 | sex         | A         |           0 |     NULL | NULL   | YES  | BTREE      |         |               |
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
3 rows in set (0.00 sec)


走索引的where條件語句

where money age sex

where money sex age

where money sex

where money age


不走索引的情況where條件語句

where age sex money

where age money sex

where age sex

where age money

where sex age money

where sex money age

where sex money

where sex age


免責聲明!

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



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