2、MySQL 索引失效的場景
阿新 • • 發佈:2019-01-04
索引失效的場景:
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%’; ——————> selectcount(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 indexfrom 語法其實訪問的是 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