Mysql索引優化

1、索引的數據結構 B-Tree(mysql主要使用 B-tree 平衡樹)

聚簇索引與非聚簇索引

聚簇索引:索引的葉節點指向數據
非聚簇索引:索引的葉節點指向數據的引用mysql

索引類型
聚簇索引 查詢數據少時,無須回行  不規則插入數據,頻繁的頁分裂

myisam使用非聚簇索引,innodb使用聚簇索引sql

對於innodb引擎:緩存

  1. 主鍵索引既存儲索引值,又在葉中存儲行數據
  2. 若是沒有主鍵,則會使用 unique key 作主鍵
  3. 若是沒有unique,則mysql會生成一個rowid作主鍵  

2、索引類型

1. 主鍵索引

primary key() 要求關鍵字不能重複,也不能爲null,同時增長主鍵約束
主鍵索引定義時,不能命名數據結構

2. 惟一索引

unique index() 要求關鍵字不能重複,同時增長惟一約束post

3. 普通索引

index() 對關鍵字沒有要求性能

4. 全文索引

fulltext key() 關鍵字的來源不是全部字段的數據,而是字段中提取的特別關鍵字測試

關鍵字:能夠是某個字段或多個字段,多個字段稱爲複合索引優化

建表:
creat table student(
    stu_id int unsigned not null auto_increment,
    name varchar(32) not null default '',
    phone char(11) not null default '',
    stu_code varchar(32) not null default '',
    stu_desc text,
    primary key ('stu_id'),     //主鍵索引
    unique index 'stu_code' ('stu_code'), //惟一索引
    index 'name_phone' ('name','phone'),  //普通索引,複合索引
    fulltext index 'stu_desc' ('stu_desc'), //全文索引
) engine=myisam charset=utf8;

更新:
alert table student
    add primary key ('stu_id'),     //主鍵索引
    add unique index 'stu_code' ('stu_code'), //惟一索引
    add index 'name_phone' ('name','phone'),  //普通索引,複合索引
    add fulltext index 'stu_desc' ('stu_desc'); //全文索引

刪除:
alert table sutdent
    drop primary key,
    drop index 'stu_code',
    drop index 'name_phone',
    drop index 'stu_desc';

3、索引使用原則

1. 列獨立

保證索引包含的字段獨立在查詢語句中,不能是在表達式中url

2. 左前綴

like:匹配模式左邊不能以通配符開始,才能使用索引
注意:前綴索引在排序 order by 和分組 group by 操做的時候沒法使用。code

3. 複合索引由左到右生效

創建聯合索引,要同時考慮列查詢的頻率和列的區分度。

  1. index(a,b,c)
語句 索引是否發揮做用
where a=3 是,只使用了a
where a=3 and b=5 是,使用了a,b
where a=3 and b=5 and c=4 是,使用了a,b,c
where b=3 or where c=4
where a=3 and c=4 是,僅使用了a
where a=3 and b>10 and c=7 是,使用了a,b
where a=3 and b like '%xx%' and c=7 使用了a,b

or的兩邊都有存在可用的索引,該語句才能用索引。

4. 不要濫用索引,多餘的索引會下降讀寫性能

即便知足了上述原則,mysql仍是可能會棄用索引,由於有些查詢即便使用索引,也會出現大量的隨機io,相對於從數據記錄中的順序io開銷更大。

4、mysql 中可以使用索引的典型應用

測試庫下載地址:https://downloads.mysql.com/d...

1. 匹配全值(match the full value)

對索引中全部列都指定具體值,便是對索引中的全部列都有等值匹配的條件。
例如,租賃表 rental 中經過指定出租日期 rental_date + 庫存編號 inventory_id + 客戶編號 customer_id 的組合條件進行查詢,熊執行計劃的 key he extra 兩字段的值看到優化器選擇了複合索引 idx_rental_date:

MySQL [sakila]> explain select * from rental where rental_date='2005-05-25 17:22:10' and inventory_id=373 and customer_id=343 \G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: rental
   partitions: NULL
         type: const
possible_keys: rental_date,idx_fk_inventory_id,idx_fk_customer_id
          key: rental_date
      key_len: 10
          ref: const,const,const
         rows: 1
     filtered: 100.00
        Extra: NULL
 1 row in set, 1 warning (0.00 sec)

explain 輸出結果中字段 type 的值爲 const,表示是常量;字段 key 的值爲 rental_date, 表示優化器選擇索引 rental_date 進行掃描。

2. 匹配值的範圍查詢(match a range of values)

對索引的值可以進行範圍查找。
例如,檢索租賃表 rental 中客戶編號 customer_id 在指定範圍內的記錄:

MySQL [sakila]> explain select * from rental where customer_id >= 373 and customer_id < 400 \G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: rental
   partitions: NULL
         type: range
possible_keys: idx_fk_customer_id
          key: idx_fk_customer_id
      key_len: 2
          ref: NULL
         rows: 718
     filtered: 100.00
        Extra: Using index condition
 1 row in set, 1 warning (0.05 sec)

類型 type 爲 range 說明優化器選擇範圍查詢,索引 key 爲 idx_fk_customer_id 說明優化器選擇索引 idx_fk_customer_id 來加速訪問,注意到這個列子中 extra 列爲 using index codition ,表示 mysql 使用了 ICP(using index condition) 來進一步優化查詢。

3. 匹配最左前綴(match a leftmost prefix)

僅僅使用索引中的最左邊列進行查詢,好比在 col1 + col2 + col3 字段上的聯合索引可以被包含 col一、(col1 + col2)、(col1 + col2 + col3)的等值查詢利用到,但是不可以被 col二、(col二、col3)的等值查詢利用到。
最左匹配原則能夠算是 MySQL 中 B-Tree 索引使用的首要原則。

4. 僅僅對索引進行查詢(index only query)

當查詢的列都在索引的字段中時,查詢的效率更高,因此應該儘可能避免使用 select *,須要哪些字段,就只查哪些字段。

5. 匹配列前綴(match a column prefix)

僅僅使用索引中的第一列,而且只包含索引第一列的開頭一部分進行查找。
例如,如今須要查詢出標題 title 是以 AFRICAN 開頭的電影信息,從執行計劃可以清楚看到,idx_title_desc_part 索引被利用上了:

MySQL [sakila]> create index idx_title_desc_part on film_text(title (10), description(20));
Query OK, 0 rows affected (0.07 sec)
Records: 0  Duplicates: 0  Warnings: 0

MySQL [sakila]> explain select title from film_text where title like 'AFRICAN%'\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: film_text
   partitions: NULL
         type: range
possible_keys: idx_title_desc_part,idx_title_description
          key: idx_title_desc_part
      key_len: 32
          ref: NULL
         rows: 1
     filtered: 100.00
        Extra: Using where
 1 row in set, 1 warning (0.00 sec)

extra 值爲 using where 表示優化器須要經過索引回表查詢數據。

6. 可以實現索引匹配部分精確而其餘部分進行範圍匹配(match one part exactly and match a range on another part)

例如,須要查詢出租日期 rental_date 爲指定日期且客戶編號 customer_id 爲指定範圍的庫存:

MySQL [sakila]> MySQL [sakila]> explain select inventory_id from rental where rental_date='2006-02-14 15:16:03' and customer_id >= 300 and customer_id <=400\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: rental
   partitions: NULL
         type: ref
possible_keys: rental_date,idx_fk_customer_id
          key: rental_date
      key_len: 5
          ref: const
         rows: 182
     filtered: 16.85
        Extra: Using where; Using index
 1 row in set, 1 warning (0.00 sec)
7. 若是列名是索引,那麼使用 column_name is null 就會使用索引。

例如,查詢支付表 payment 的租賃編號 rental_id 字段爲空的記錄就用到了索引:

MySQL [sakila]> explain select * from payment where rental_id is null \G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: payment
   partitions: NULL
         type: ref
possible_keys: fk_payment_rental
          key: fk_payment_rental
      key_len: 5
          ref: const
         rows: 5
     filtered: 100.00
        Extra: Using index condition
 1 row in set, 1 warning (0.00 sec)

5、存在索引但不能使用索引的典型場景

有些時候雖然有索引,可是並不被優化器選擇使用,下面舉例幾個不能使用索引的場景。

1.以%開頭的 like 查詢不能利用 B-Tree 索引,執行計劃中 key 的值爲 null 表示沒有使用索引
MySQL [sakila]> explain select * from actor where last_name like "%NI%"\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: actor
   partitions: NULL
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 200
     filtered: 11.11
        Extra: Using where
 1 row in set, 1 warning (0.00 sec)

由於 B-Tree 索引的結構,因此以%開頭的插敘很天然就無法利用索引了。通常推薦使用全文索引(Fulltext)來解決相似的全文檢索的問題。或者考慮利用 innodb 的表都是聚簇表的特色,採起一種輕量級別的解決方式:通常狀況下,索引都會比表小,掃描索引要比掃描表更快,而Innodb 表上二級索引 idx_last_name 實際上存儲字段 last_name 還有主鍵 actot_id,那麼理想的訪問應該是首先掃描二級索引 idx_last_name 得到知足條件的last_name like '%NI%' 的主鍵 actor_id 列表,以後根據主鍵回表去檢索記錄,這樣訪問避開了全表掃描演員表 actor 產生的大量 IO 請求。

ySQL [sakila]> explain select * from (select actor_id from actor where last_name like '%NI%') a , actor b where a.actor_id = b.actor_id \G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: actor
   partitions: NULL
         type: index
possible_keys: PRIMARY
          key: idx_actor_last_name
      key_len: 137
          ref: NULL
         rows: 200
     filtered: 11.11
        Extra: Using where; Using index
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: b
   partitions: NULL
         type: eq_ref
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 2
          ref: sakila.actor.actor_id
         rows: 1
     filtered: 100.00
        Extra: NULL

從執行計劃中可以看出,extra 字段 using wehre;using index。理論上比全表掃描更快一下。

2. 數據類型出現隱式轉換的時候也不會使用索引

當列的類型是字符串,那麼必定記得在 where 條件中把字符常量值用引號引發來,不然即使這個列上有索引,mysql 也不會用到,由於 MySQL 默認把輸入的常量值進行轉換之後才進行檢索。

例如,演員表 actor 中的姓氏字段 last_name 是字符型的,可是 sql 語句中的條件值 1 是一個數值型值,所以即使存在索引 idx_last_name, mysql 也不能正確的用上索引,而是繼續進行全表掃描:

MySQL [sakila]> explain select * from actor where last_name = 1 \G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: actor
   partitions: NULL
         type: ALL
possible_keys: idx_actor_last_name
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 200
     filtered: 10.00
        Extra: Using where
 1 row in set, 3 warnings (0.00 sec)

MySQL [sakila]> explain select * from actor where last_name = '1'\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: actor
   partitions: NULL
         type: ref
possible_keys: idx_actor_last_name
          key: idx_actor_last_name
      key_len: 137
          ref: const
         rows: 1
     filtered: 100.00
        Extra: NULL
 1 row in set, 1 warning (0.00 sec)
3. 複合索引的狀況下,假如查詢條件不包含索引列最左邊部分,即不知足最左原則 leftmost,是不會使用複合索引的。
4. 若是 MySQL 估計使用索引比全表掃描更慢,則不使用索引。
5. 用 or 分割開的條件,若是 or 前的條件中的列有索引,然後面的列中沒有索引,那麼涉及的索引都不會被用到。

6、查看索引使用狀況

若是索引正在工做, Handler_read_key 的值將很高,這個值表明了一個行被索引值讀的次數,很低的值表名增長索引獲得的性能改善不高,由於索引並不常用。
Handler_read_rnd_next 的值高則意味着查詢運行低效,而且應該創建索引補救。這個值的含義是在數據文件中讀下一行的請求數。若是正在進行大量的表掃描,Handler_read_rnd_next 的值較高,則一般說明表索引不正確或寫入的查詢沒有利用索引,具體以下。

MySQL [sakila]> show status like 'Handler_read%';
+-----------------------+-------+
| Variable_name         | Value |
+-----------------------+-------+
| Handler_read_first    | 1     |
| Handler_read_key      | 5     |
| Handler_read_last     | 0     |
| Handler_read_next     | 200   |
| Handler_read_prev     | 0     |
| Handler_read_rnd      | 0     |
| Handler_read_rnd_next | 0     |
+-----------------------+-------+

7、使用索引的小技巧

1. 字符串字段權衡區分度與長度的技巧

截取不一樣長度,測試區分度

# 這裏假設截取6個字符長度計算區別度,直到區別度達到0.1,就能夠把這個字段的這個長度做爲索引了
mysql> select count(distinct left([varchar]],6))/count(*) from table;

#注意:設置前綴索引時指定的長度表示字節數,而對於非二進制類型(CHAR, VARCHAR, TEXT)字段而言的字段長度表示字符數,所
#      以,在設置前綴索引前須要把計算好的字符數轉化爲字節數,經常使用字符集與字節的關係以下:
# latin      單字節:1B
# GBK        雙字節:2B
# UTF8       三字節:3B
# UTF8mb4    四字節:4B     
# myisam 表的索引大小默認爲 1000字節,innodb 表的索引大小默認爲 767 字節,能夠在配置文件中修改 innodb_large_prefix 
# 項的值增大 innodb 索引的大小,最大 3072 字節。

區別度能達到0.1,就能夠。

2. 左前綴不易區分的字段索引創建方法

這樣的字段,左邊有大量重複字符,好比url字段彙總的http://

  1. 倒過來存儲並創建索引
  2. 新增僞hash字段 把字符串轉化爲整型
3. 索引覆蓋

概念:若是查詢的列剛好是索引的一部分,那麼查詢只須要在索引文件上進行,不須要回行到磁盤,這種查詢,速度極快,江湖人稱——索引覆蓋

4. 延遲關聯

在根據條件查詢數據時,若是查詢條件不能用的索引,能夠先查出數據行的id,再根據id去取數據行。
eg.

//普通查詢 沒有用到索引
select * from post where content like "%新聞%";
//延遲關聯優化後 內層查詢走content索引,取出id,在用join查全部行
select a.* from post as a inner join (select id from post where content like "%新聞%") as b on a.id=b.id;
5. 索引排序 

排序的字段上加入索引,能夠提升速度。

6. 重複索引和冗餘索引

重複索引:在同一列或者相同順序的幾個列創建了多個索引,成爲重複索引,沒有任何意義,刪掉
冗餘索引:兩個或多個索引所覆蓋的列有重疊,好比對於列m,n ,加索引index m(m),indexmn(m,n),稱爲冗餘索引。

7. 索引碎片與維護

在數據表長期的更改過程當中,索引文件和數據文件都會產生空洞,造成碎片。修復表的過程十分耗費資源,能夠用比較長的週期修復表。

//清理方法
alert table xxx engine innodb; 
//或
optimize table xxx;
8. innodb引擎的索引注意事項

Innodb 表要儘可能本身指定主鍵,若是有幾個列都是惟一的,要選擇最常做爲訪問條件的列做爲主鍵,另外,Innodb 表的普通索引都會保存主鍵的鍵值,因此主鍵要儘量選擇較短的數據類型,能夠有效的減小索引的磁盤佔用,提升索引的緩存效果。

相關文章
相關標籤/搜索