1. 程式人生 > >2、MySQL 索引失效的場景

2、MySQL 索引失效的場景

索引失效的場景: 1、沒有 where 條件 直接看 SQL 語句   2、where 條件中所在的列沒有建立索引 show index from t;   3、從表中取得資料超過某個閾值。通常認為是 20~30%,即使 where 條件和索引都滿足,也不會走索引
看錶的行數、看下索引列的 cardinality 值,card 值只能直觀反映 = 操作符返回的行數。
對於>=、<=、like、between and 的情況,card 值不能直觀判斷返回值的資料量。
有時候可以嘗試著執行一下,但要注意,不是執行真正的 SQL,而是主要是為了得到 where 訪問條件返回的行數,所以可以使用下面的技巧實現需求的轉換:
select  x.c_id,s.s_name from xuanke x join student s on x.stu_id=s.stu_id  where s.s_name like ‘abc%’; ——————>
select
count(1) from student where s_name like ‘abc%’; 上面的 sql 是不是原始 SQL,而是你想得到某個結果而自己寫的 SQL。

 

4、多列索引沒有使用前導列      show index from t1;   5、索引本身失效
如何查詢失效索引?
如果索引失效,重建索引!
MySQL 中目前沒法檢視索引的狀態資訊。
Use information_schema,show tables,有 innodb_sys_indexs 系統所有的索引資訊,
但索引狀態資訊實際沒法看,show index 
from 語法其實訪問的是 information_schema.STATISTICS 資料字典。

 

 

6、where 條件列上不乾淨
比如在列上有函式:MySQL 中不支援,Oracle 中支援在列上建立函式索引
select ... from t1 where upper(name) like 'ABC%';
比如在 where 條件中存在運算:
select ... from t1 where id-100<30;

 

7、小表也儘量走索引,由於 gap 鎖的存在
select
儘量走索引,對於 dml 一定要走索引(否則就是全表鎖,導致業務序列化)

 

8、使用了 ignore index hints
使用了 hints,強制忽略了某個索引,導致沒走索引

 

  9、統計資訊不真實(嚴重不真實)
統計資訊可能會出現嚴重不真實導致不走索引:表中有 1000 萬,索引唯一值有 20 萬,但是舊統計資訊中唯一值的數量才 2,導致不走索引,走全表掃描。
如何判斷統計資訊是否真實:
show table status like 't1';
show index from t1;

手工收集統計資訊:
analyze table t1;

例如
對一個表做了 truncate 以後,系統在隨後的時間裡面,啟動了一個自動收集統計資訊的作業,這個表的行數更新變成 0 行。隨後,對這個表進行資料的
匯入,匯入 1000 萬行,這時候 MySQL 不會去看錶中真實的行數,還是會看統計資訊的 0 行,這時候會出現問題,需要手工收集統計資訊。
但是對於遞增式的、每日規律變化的情況,統計資訊沒有必要每日收集。

對於統計資訊的收集:
學會使用 UE、notepad++等工具批量手工收集統計資訊:
Analyze table table_name;
利用 select concat(‘analyze table ',TABLE_NAME,';') from tables where table_schema=’tpcc1000’;
複製所有的表,利用 UE 編輯器的列編輯模式,直接去掉不必要的列,加上 analyze table 和分號變成語句,直接在 MySQL 裡執行就好了。
 
舉例:
MySQL> select concat('analyze table ',TABLE_NAME,';') from information_schema.tables where table_schema='TENNIS';
+-----------------------------------------+
| concat('analyze table ',TABLE_NAME,';') |
+-----------------------------------------+
| analyze table COMMITTEE_MEMBERS; |
| analyze table MATCHES; |
| analyze table PENALTIES; |
| analyze table PLAYERS; |
| analyze table TEAMS; |

在 ultraedit 裡使用列模式,直接編輯,編輯完直接複製貼上到 MySQL 裡執行就行。
analynize table COMMITTEE_MEMBERS ;
analynize table MATCHES ;
analynize table PENALTIES ;
analynize table PLAYERS ;
analynize table TEAMS ;
 
MySQL 自動收集統計資訊的引數:
比如在 show table status like ‘customer’;的時候會自動收集,最好是手工收集。
1、自動儲存引數:innodb_stats_persistent
2、變化量大的情況下,自動收集引數:innodb_stats_auto_recalc:
MySQL> show variables like '%stat%';
| innodb_stats_sample_pages | 8 —

—不管你訪問多少,每次都是隨機掃 8 個頁,看看裡面有多少行。
| innodb_stats_persistent_sample_pages | 20 ——
analyze 是取樣 20 個頁,一般可以設定成 64 個頁,所以 analyze 準確一些,建議定期手工收集一下。

| innodb_stats_persistent | ON ——
收集完統計資訊以後,把收集的資訊永久儲存到資料字典裡面去,資料庫重新啟動的時候這個統計資訊還在,還可以繼續使用,如果是 off,存到記憶體裡面去,下次啟動就沒了,所以這個引數一定要是 on。
| innodb_stats_auto_recalc | ON —

—當 update 或 delete 等操作產生大的影響時,如果這個引數是 on,會觸發統計資訊的自動收集。例如:變化超過 20%就觸發自動收集,有時候會關閉。

統計資訊:
1、表的行數
2、索引列的唯一值的數量

關於統計資訊需要知道:
1、只有在統計的時候,才會更新對應的資料
2、統計資訊使用來生成執行計劃的
3、統計資訊沒有必要和表、索引保持實時更新
比如:一個錶行數是 1000 萬,索引列唯一值的數量是 20 萬,走索引效果很好;如果這個表的行數變成了 2000 萬,索引列唯一值的數量變成了 40萬,不影響走索引的效果,所以一般在對錶做 dml 時不會主動更新統計資訊,因為這樣會加重系統的負擔。

4、統計資訊總是近似的反應表和索引的資訊
如何手工修改表的行數以及 cardinality 值:為了欺騙 MySQL 是否走索引
需要注意:在執行 show table status like 語句時會自動收集統計資訊。

1、根據 mysql.innodb_table_stats 資料字典修改 n_rows 值:
[[email protected]][mysql]> select * from mysql.innodb_table_stats limit 1;
+---------------+-------------------+---------------------+--------+----------------------+--------------------------+
| database_name | table_name        | last_update         | n_rows |clustered_index_size  | sum_of_other_index_sizes |
+---------------+-------------------+---------------------+--------+----------------------+--------------------------+
| TENNIS        | COMMITTEE_MEMBERS | 2016-05-05 01:47:33 |16      | 1                    | 0                        |
+---------------+-------------------+---------------------+--------+----------------------+--------------------------+
1 row in set (0.00 sec)
[[email protected]][mysql]>

2、根據 mysql.innodb_index_stats 資料字典修改 cardinality 值:
[[email protected]][tpcc1000]> select * from mysql.innodb_index_stats where database_name='tpcc1000' and table_name='customer' and stat_description='c_first';
+---------------+------------+------------+---------------------+--------------+------------+-------------+------------------+
| database_name | table_name | index_name | last_update         | stat_name    | stat_value | sample_size | stat_description |
+---------------+------------+------------+---------------------+--------------+------------+-------------+------------------+
| tpcc1000      | customer   | id_first   | 2016-11-03 15:18:10 | n_diff_pfx01 | 299855     | 20          | c_first |
+---------------+------------+------------+---------------------+--------------+------------+-------------+------------------+
1 row in set (0.00 sec)
[[email protected]][tpcc1000]> update mysql.innodb_index_stats set stat_value=10 where database_name='tpcc1000' and table_name='customer' and stat_description='c_first';
Query OK, 1 row affected (0.01 sec)
Rows matched: 1 Changed: 1 Warnings: 0

[[email protected]][tpcc1000]> select * from mysql.innodb_index_stats where database_name='tpcc1000' and table_name='customer' and stat_description='c_first';
+---------------+------------+------------+---------------------+--------------+------------+-------------+------------------+
| database_name | table_name | index_name | last_update         | stat_name    | stat_value | sample_size | stat_description |
+---------------+------------+------------+---------------------+--------------+------------+-------------+------------------+
| tpcc1000      | customer   | id_first   | 2016-12-28 05:51:27 | n_diff_pfx01 | 10         | 20          | c_first          |
+---------------+------------+------------+---------------------+--------------+------------+-------------+------------------+
1 row in set (0.01 sec)

[[email protected]][tpcc1000]>
mysql> select * from xuanke where c_id >1000;
Empty set (0.31 sec)
mysql> explain select * from xuanke where c_id >1000;
+----+-------------+--------+-------+-------------------+-------------------+---------+------+------+-----------------------+
| id | select_type | table  | type  | possible_keys     | key               |key_len  | ref  | rows | Extra |
+----+-------------+--------+-------+-------------------+-------------------+---------+------+------+-----------------------+
| 1  | SIMPLE      | xuanke | range | FK_Relationship_3 | FK_Relationship_3 | 5       | NULL |    1 | Using index condition |
+----+-------------+--------+-------+-------------------+-------------------+---------+------+------+-----------------------+
1 row in set (0.00 sec)

mysql> explain select * from xuanke where c_id >100;
+----+-------------+--------+------+-------------------+------+---------+------+--------+-------------+
| id | select_type | table | type  | possible_keys     | key  | key_len | ref  | rows   |Extra |
+----+-------------+--------+------+-------------------+------+---------+------+--------+-------------+
| 1  | SIMPLE      | xuanke | ALL  | FK_Relationship_3 | NULL | NULL    |NULL  | 148988 | Using where |
+----+-------------+--------+------+-------------------+------+---------+------+--------+-------------+
1 row in set (0.38 sec)
mysql>

 

 

10、資料傾斜的情況下
例如狀態值的列,有可能出現沒有走索引的情況:
select * from dingdan where dingdanzhuangtai='未處理';
如何解決行資料傾斜:
  1、使用手工修改統計資訊,card 值提升一下
  2、使用 like 的時候,會臨時性使用取樣的方式,從表中取 8 個數據塊,統計“未處理”值的數量,這時候反而準確了。
  3、使用 force index 和 ignore index 來做特殊處理,在 Oracle 中不會出現資料傾斜導致不走索引的情況,因為有資料值所佔百分比,能夠正確引導是否走索引。 
  11、CBO 計算走索引花費太大
根本原因還是從表中訪問的行數過多
針對 like、<=、>=、between and 等不確定的一些條件,會進行動態取樣,可能出現有時候走索引,有時候不走索引的情況。
資料庫最核心的元件是優化器,對 SQL 進行解析,生成執行計劃。

優化器工作模式:
 1、RBO(rule based optimization),基於規則的優化器,條件太苛刻,現在基本不用
主要幹什麼:
  1、是否走索引(定義規則為:是否有 where 條件、where 條件是否有索引等,如果滿足規則就走索引,不滿足就不走索引)
  2、表的連線順序等(定義規則為:按照寫的 SQL 中的表連線順序)針對這種優化器,我們在寫 SQL 的時候,就需要了解這種優化器的工作習性(規則庫),按照他的脾氣來,強烈依賴 SQL 的寫法。

2、CBO(cost based optimization),基於成本的優化器(現在主要是 CBO,MySQL只有 CBO)
  1、在解析以前,會做一件事情,對 SQL 進行改寫,改寫成更合理的一些 SQL語句
  2、將執行路徑列出來,計算每一個執行路徑的成本(cpu 和 io 的成本,主要是 io 成本),基於統計資訊進行計算,估算一個成本
  3、選擇執行成本最低的 SQL 作為執行計劃

對於 CBO:
1、不過度依賴 SQL 寫法
2、嚴重依賴統計資訊

 

  12、隱式型別轉換導致索引失效
1、數字列不害怕型別轉換
2、字串列非常害怕隱式型別轉換,因此對於字串的列,一定要加上'':
字串列“坑”
我們習慣於將很多列定位為字串,例如手機號列,在 where 的時候,也習慣與=123,不加'',因為我們認為這是數字,但是定義的是字元列,發生隱式轉換,導致索引失效。
3、日期列不害怕隱式型別轉換
設計原則:
如果儲存的是數字,就定義成數字列
如果儲存的是日期,就定義成日期列
如果儲存的是字串,where 條件的時候,右面一定要加上'' 

 

  13、<>會導致索引失效
因為資料庫認為等於的時候會取少量資料,認為不等於會取大量的資料

 

14、在 where 條件的 like 中%在前
where like '%abc',有索引也會失效,後面的中這種寫法索引可能不會失效
select * from ... where name like 'abc%' 

 

15、not in(值的列表)經常索引失效,in(值的列表)一般走索引
因為認為 not in 時結果集會比較大,而 in 的時候結果集會比較小。

 

16、對於 not in 和 not exists 子查詢的情況,索引不一定失效
對 not in、not exists 和 left join 之間的相互轉換,索引都生效了:
mysql> explain select * from student s where s.stu_id not in (select stu_id from xuanke);
+----+-------------+--------+-------+-------------------+-------------------+---------+------ +--------+-------------+
| id | select_type | table  | type  | possible_keys     | key |key_len      | ref     | rows  | Extra  |
+----+-------------+--------+-------+-------------------+-------------------+---------+------ +--------+-------------+
| 1  | PRIMARY     | s      | ALL   | NULL              | NULL              | NULL    | NULL  | 194737 | Using where |
| 2  | SUBQUERY    | xuanke | index | FK_Relationship_1 |FK_Relationship_1  | 5       | NULL  | 149877 | Using index |
+----+-------------+--------+-------+-------------------+-------------------+---------+------ +--------+-------------+
2 rows in set (0.00 sec)
mysql> explain select * from student s where not exists (select 1 from xuanke x where x.stu_id=s.stu_id);
+----+--------------------+-------+------+-------------------+-------------------+---------+-----------------+--------+-------------+
| id | select_type        | table | type | possible_keys     | key| key_len      | ref     | rows            | Extra |
+----+--------------------+-------+------+-------------------+-------------------+---------+-----------------+--------+-------------+
| 1 | PRIMARY             | s     | ALL  | NULL              | NULL              | NULL    | NULL            | 194737 | Using where |
| 2 | DEPENDENT SUBQUERY  | x     | ref  | FK_Relationship_1 |FK_Relationship_1  | 5       | xuanke.s.stu_id | 1      | Using index |
+----+--------------------+-------+------+-------------------+-------------------+---------+-----------------+--------+-------------+
mysql> explain select * from student s left join xuanke x on s.stu_id=x.stu_id and x.c_id is null;
+----+-------------+-------+------+-------------------------------------+-------------------+---------+-----------------+--------+-------------+
| id | select_type | table | type | possible_keys                       | key               | key_len | ref             | rows   | Extra |
+----+-------------+-------+------+-------------------------------------+-------------------+---------+-----------------+--------+-------------+
| 1  | SIMPLE      | s     | ALL  | NULL                                | NULL              | NULL    | NULL            | 194737 | NULL|
| 1  | SIMPLE      | x     | ref  | FK_Relationship_1,FK_Relationship_3 |FK_Relationship_1  | 5       | xuanke.s.stu_id | 1      | Using where |
+----+-------------+-------+------+-------------------------------------+-------------------+---------+-----------------+--------+-------------+
2 rows in set (0.00 sec)

 

17、對於 in、exists 子查詢的情況,索引一般也不會失效
mysql> explain select * from student s where exists (select 1 from xuanke x
where x.stu_id=s.stu_id);
+----+--------------------+-------+------+-------------------+-------------------+---------+-----------------+--------+-------------+
| id | select_type        | table | type | possible_keys     | key               | key_len | ref             | rows   | Extra |
+----+--------------------+-------+------+-------------------+-------------------+---------+-----------------+--------+-------------+
| 1  | PRIMARY            | s     | ALL  | NULL              | NULL              | NULL    | NULL            | 194737 | Using where |
| 2  | DEPENDENT SUBQUERY | x     | ref  | FK_Relationship_1 |FK_Relationship_1  | 5       | xuanke.s.stu_id | 1      | Using index |
+----+--------------------+-------+------+-------------------+-------------------+---------+-----------------+--------+-------------+
2 rows in set (0.00 sec)
mysql> explain select * from student s where s.stu_id in (select stu_id from xuanke);
+----+--------------+-------------+--------+-------------------+-------------------+--------- +-----------------+--------+-------------+
| id | select_type  | table       | type   | possible_keys     | key               | key_len  | ref             | rows   | Extra       |
+----+--------------+-------------+--------+-------------------+-------------------+--------- +-----------------+--------+-------------+
| 1  | SIMPLE       | s           | ALL    | PRIMARY           |NULL               | NULL     | NULL            | 194737 | Using where |
| 1  | SIMPLE       | <subquery2> | eq_ref | <auto_key>        |<auto_key>         | 5        | xuanke.s.stu_id | 1      | NULL        |
| 2  | MATERIALIZED | xuanke      | index  | FK_Relationship_1 |FK_Relationship_1  | 5        | NULL            | 149877 | Using index |
+----+--------------+-------------+--------+-------------------+-------------------+--------- +-----------------+--------+-------------+
3 rows in set (0.00 sec)

  

18、對於日期時間列來說,下面的索引會失效
發生了隱式型別轉換,導致索引會失效:
explain select * from login_record1 where t_time=cast('2011-1-1' as date);
explain select * from login_record1 where d_date=cast('13:00:00' as time);
explain select * from login_record1 where t_time='2011-1-1'; //索引不會失效

  

19、is null 一般會走索引,即使所有的資料都為空,這是一個 bug
is not null 有時候走索引,有時候不走索引,還是比較準確,主要看空值和非空值的數量。
is null 可能會成為一個坑。
MySQL 沒有儲存資料分佈,因此在進行 where 條件的時候,可能會出現資料傾斜的盲點,反而採用 like 等模糊匹配的時候,因為會刺激 mysql 進行動態取樣,反而會比較準確。有時候會出現下面的一些寫法:
  1、like 'abc';來代替='abc'
  2、between 1 and 1 來代替=1