讀高效MYSQL,效能優化建議
總結自<高效mysql效能優化>:
第一章:5分鐘DBA
一.鑑定效能問題:
1) 查詢慢SQL語句:
show full processlist
可以得到當前mysql所有連線中正在執行的操作,此指令將會輸出當前執行的所有sql列表以及其耗時資訊:
id:42
User:***
Host:****
Command:QUERY
Time:3
Info:select * from table
2) 查詢計劃(QEP):
可以通過Explain語句檢視SELECT語句執行的情況.
提示:Alter操作,將會阻塞當前table中所有的其他操作,直到alter操作結束.所以避免在生產環境中對大表進行多次alter,或者避免在業務
繁忙期,使用alter.
3) >show create table tableName;
此指令能夠直觀的檢視當前表結構,以及使用索引的情況.
show table status like 'tableName';
此指令能夠告訴我們當前表的整體資訊,其中包括表的引擎型別,表中行數,資料總尺寸大小,索引總尺寸大小.能夠幫助我們分析效能問題是否因為
資料尺寸過大帶來的.
結果格式:
Name:tableName
Engine:Innodb
Rows:134500
Data_length:123131123
Index_length:...
CheckSum:null.
第二章:必備的分析指令
1) EXPLAN:
SELECT查詢計劃工具,能夠較為詳細的告知當前sql執行所涉及到的索引/過濾條件等.
2) EXPLAN EXTENDED:
和EXPLAIN類似,不過額外提供了filtered屬性資訊,filtered表示當前語句所能過濾掉的資料的百分比,實際參與連線的行數為rows * filtered /100,注意fitered
的資訊為不含%的整數部分.
此外,EXTENDED還能對一些select計劃生成warning資訊,如果當前sql是無索引或者時低效時,將會提示一個warning,我們可以通過>show warning資訊檢視具體原因.
waring中將會展示出,當前sql最終被"轉換/優化"之後的SQL,我們可以從中看到"索引失效"等相關資訊.這些是我們使用EXPLAIN所不能得到的.
比如,將兩個表進行join,但是on條件中的欄位屬性在兩個表中不一致,將會導致索引失效.例如對name進行關聯,但是兩個表的name編碼不同等.
3) SHOW INDEXES FROM tableName:
查詢指定表的索引列表資訊,此指令將會輸出當前表所關聯的所有索引資訊,包括"主鍵".
例如:
Table:tableName
Key_Name:PRIMARY
Seq_in_index:1 //索引順序?
Column_name: id //索引針對"列"
Index_type:BTREE
Cardinality:2880 //基準,表示當前表中不同索引值的個數,"區分度".
......
4) SHOW TABLE STATUS like 'tableName'
5) SHOW [GLOBAL | SESSION] STATUS like '%read%':
此指令用於檢視當前server的有關狀態,DBA使用.
6) SHOW [GLOBAL | SESSION] VARIABLES like '%buffer_size%':
檢視當前系統的變數值,比如檢視mysam表的快取大小,innodb的查詢快取等.
第三章:理解索引
1) 索引的作用:維護資料約束,優化資料查詢,提升表關聯,結果排序,聚集資料
2) 主鍵特徵:
->每張表只能有一個主鍵
->主鍵不能包含NULL
->主鍵提供了直接手段獲取指定的行
->如果當前欄位為AUTO_INCREAMENT,那麼它必須是主鍵或者主鍵的一部分(聯合主鍵)
3) 唯一索引:
->一個表中可以有多個唯一索引
->索引值可以為NULL,不過每個NULL值之於它自己相等.(NULL != NULL)
使用索引,可以避免全表掃描,類似於隨即IO操作和順序IO操作一樣.
如果sql中存在表關聯,那麼儘可能為關聯欄位建立索引.MYSQL中,對索引值以排序的方式儲存.這就有利於對資料輸出結果排序.
4) BREE:
二叉樹,所有資料或者索引節點將會以樹狀關係分佈,對於任何一個節點,其左葉節點總比右葉節點小.
Node {
key;
Node left;
Node right;
}
在資料節點較多時,如果查詢一個數據,有可能導致較深的遞迴比較.這種結構,存在"樹深"問題,不能作為"資料庫索引結構".
B-TREE:
是BTREE的"改進版",用於解決"樹深"問題,並提供可靠的資料查詢策略.
Node {
keySelf;
key1;
key2;
...
keyN;
Node1;
Node2;
...
NodeN;
}
B-Tree的樹深有限,一般為2層,除root節點外,分為Node page,和Leaf page兩種結構,每個非葉子節點的節點為子節點,每個子節點維護者多個葉子節點,
具體每個Node維護多少個葉子節點,在不同的設計中有所不同.Node page中維護者子節點序列,正序排列,子節點所代表的值為最大葉子節點值.
B-tree中,需要保證,每個子節點的值都比其後續子節點的最小葉子節點值小,而且比前繼子節點的最大葉子節點值大...針對任何值查詢,可以使用二分法
找到其所屬於的子節點,如果此值剛好為子節點值,則直接返回;否則從其leaf page中最左葉節點逐個遍歷,直到獲得值或者為空返回.
B-tree結構,避免了樹深問題,但是如果設計不良,導致leaf page中葉子個數過多,同樣產生類似樹深的問題(順序遍歷).每個葉子節點到root節點的距離都一樣.
ROOT
|
NODE1 NODE2 NODE3
| | |
LEAF1->LEAF2 LEAF->LEAF LEAF->LEAF
B+Tree,在B-tree的基礎上做了一些微小的改造,在每個節點的最右葉子節點增加了指向下個子節點的指標.由此這種改動,對於"range scan"有很大的幫助.
在MYSQL中,對這兩種資料結構使用,還有些特殊的區別.
5) 索引結構:
在mysql中,B+tree具有B-tree所有的特性,他們有個很大的區別就是,B+tree所引用的底層資料是按照被索引列排序的(聚簇索引,Primary key),而B-tree不是.
hash表資料結構,key為"索引",value為實際資料的指標位置.對hash表的訪問,時間開支為線性的.
fullText索引目前只能被myisam表支援,mysql5.6之後innodb表也將支援fullText.
一個很現實的事情是,B-tree結構在myisam和innodb中的實現也有些區別.在myisam引擎中,將使用B-tree結構儲存主鍵/唯一索引和二級索引.對於二級索引,myisam中儲存索引值和主鍵資料的指標.這和Innodb有些區別.
在innodb引擎中,使用B+tree來儲存主鍵,由於在每個leaf page間,有後繼指標關係,允許我們能夠更加簡單的使用range scan操作.
在innodb引擎中,二級索引使用了B-tree結構,不過和myisam有些區別,在innodb中,二級索引儲存的是主鍵的實際值,在myisam中則儲存為實際資料的指標.因此在innodb中,如果主鍵的值長很大,例如40個位元組,將可能導致二級索引檔案尺寸也非常大;此外因為二級索引持有了主鍵的實際值,所以在表關聯操作或者"covering indexes"時,可以提升效能.
對於memory table,使用hash索引時可以理解的,但是如果memory表尺寸很大的時候,同時sql查詢中,有基於key的"模糊"查詢,那麼此時效能就會大幅度下降,因為這樣的操作會導致
hash表的全掃描.:select * from memory_table where key like '%ok%'.. 針對這種情況,其實我們還可以對memory表,建立B-tree索引,對於此sql效能將得到大幅度提升.
alter table mem_table add index USING BTREE(key).此時針對key將會在記憶體中構建邏輯索引結構.在資料量較小的情況下,hash索引和btree索引效能差距不大,但是在資料量較大的情況下,
針對key='**'操作,hash效能幾乎穩定,但是btree卻有所下降.
第四章:建立索引
1) 當一個查詢,有多個索引可被選擇時(possible_keys),mysql將會嘗試使用一個最高效的索引.如何評估一個索引對當前查詢更加高效,那麼索引資訊中cardinality(計數)起到重要作用.
通過show indexes from table指令來檢視.一個索引中,如果cardinality值越高,則表示其持有的"唯一值"越多,這也就以為可以在更少的資料遍歷中找到需要的rows;不過這些值只是基於"統計"
估測出來的.最理想的情況,就是表中的行數和cardinality值很接近,即表達此表的某欄位中幾乎沒有重複的索引值.
2) 當在一個索引列上使用表示式模糊查詢時,會導致索引失效.不過有一種特殊情況不會,使用左字首匹配,"key%"這種方式,仍然會使用索引的優勢..但是對於"%key""%key%"都將會使索引失效.
3) 如果能夠確定,一列中的資料都不會重複,那麼可以考慮建立唯一索引,唯一索引將給我們打來"資料唯一性約束""資料快速查詢(如果找到,則發揮,類似於limit 1)"等優點.
4) 如果sql語句中含有order by,那麼建議為此後的列和where字句所含欄位建立索引.
5) index_merge_intersection = on變數開啟,意味著可以使用"索引聯合",即一個sql查詢,將有可能使用多個索引的查詢結果然後"merge[/即union]"或者"intersect(交集)".
例如:select * from table where name = 'zhangsan' or age =25,如果在name和age上分別建立了索引,並且開啟了上述變數,那麼最終此查詢將會在查詢計劃大概為:
possible_keys:name,age
key:name,age
Extra:Using union(name,age);Using where
如果語句為:select * from table where name = 'zhangsan' and age =25,那麼其查詢計劃為:
possible_keys:name,age
key:name,age
Extra:Using intersect(name,age);Using where
"索引聯合"效能不一定比單索引更加高效,當然單索引也不一定比"索引聯合"更高效,如果實際環境中存在如上述例子的情況,還是需要多多測試.
預設"索引聯合"變數是關閉的,即任何sql查詢,最終只會使用一個索引.
6) "建立索引"的其他影響:
索引,會影響表中資料寫入的效能,索引個數越多,那麼導致insert/update等DML語句效能下降.原理很簡單,任何導致資料修改的操作,都可能觸發一個或者多個索引檔案的調整,甚至重建,這
會同時發生在記憶體和索引檔案中.
索引優化中,我們還需要移除"重複索引",mysql允許你對一個列建立重複索引且不提示異常.但是這對我們的查詢是沒有太大幫助的,例如:
create table..
key name
,
key user_type
,
key m1
(name
,user_type
),
key m2
(user_type
,name
);
其中m1和m2兩個索引就是重複的.
索引多磁碟空間的影響,建立多個索引或者對欄位建立不合適的索引,將會增減索引檔案的尺寸,而且有可能最終索引檔案比實際data檔案還要大.需要申明的是,在innobd中,主鍵的值要儘量的小.
因為innodb中B-tree的索引將會把主鍵值直接新增到索引記錄中,假如每個主鍵值有40個位元組,那麼當資料量很大時,索引檔案也將膨脹而且既有可能遠大於記憶體,很多時候,我們期望索引資訊能夠盡多
的被載入記憶體,盡少次數的索引page在記憶體中交換.
第五章: 建立高效索引
建立合適的索引,可以更好的改善效能.
1) Covering index(覆蓋索引):
例如: add index (user_name
,user_type
),那麼針對select id,user_name,user_type from user where user_name='xxx'語句將會使用"覆蓋索引值".此查詢將會使用索引,
因為建立索引時"user_name""user_type"的值已經在建立索引時作為索引值而持有,因此在select輸出時不會導致對實際資料的磁碟檢索,而是通過索引值直接可以獲取,因此對於"uer_type"
的值是可以直接獲得的,這就是"覆蓋索引".同時在innodb中,任何select中包括主鍵時,也被covering.
注意"覆蓋索引"仍然需要考慮"索引值最左原則",即只有組合索引的最左索引列參與查詢才會使用到"覆蓋索引";對於組合索引,參與查詢的最左索引列(按照順序順序)越多,其索引效率越高.
比如組合索引("name","age","date"),如果查詢語句中未包含"name"篩選條件,將會導致不會使用索引(全文檢索),如果篩選條件為"name" + "date"將會導致只能使用"name"索引列值(通過explain可以看出index_len為name的值長,而date不參與索引值比較);如果篩選條件為"name" + "age"那麼將會使用覆蓋索引,且索引值為name + age.
2) partial index(區域性索引):
例如:add index (name(20)),此指令將會對name欄位的最左20個字元建立二級索引.因此對name的查詢,有可能會使用此索引,而且在一定程度上,減少了索引檔案的尺寸.
第六章:MYSQL配置選項
對於mysql server端將採取執行緒池的方式處理client的連線請求,可以確保server在多client情況下能夠保持穩定可控.
1) innodb_buffer_pool_size:此變數用於定義innodb的資料和索引的緩衝池大小.此引數為全域性記憶體buffer大小,使用與所有的innodb引擎表.
2) query_cache_size:定義select語句執行結果的快取大小.此引數為全域性記憶體buffer,用來快取頻繁查詢的資料結果.不過在資料insert/update等write操作中,將會觸發cache的結果和當前表
有關的所有資料失效,所以較大的query_cache_size在write操作頻繁的環境中反而會影響效能.可以在讀寫比較大的環境中,設定合適的cache,可以提高效能.否則,建議關閉此引數.
3) key_buffer_size:針對myisam引擎,一個全域性的記憶體buffer,它只快取了myisam表索引資訊,索引資料可以從磁碟中相應的檔案中讀取然後載入記憶體.如果記憶體不足以快取不斷更新的索引資訊,將會
採用LRU策略,將不活躍的索引block交換出記憶體.key_cache_age_threshold/key_cache_block_size/key_cache_division_limit四個引數互相調整來決定記憶體中索引資料交換的方式.
快取以block為單位維護(載入和移除),key_cache_block_size來決定其大小.如果查詢是所涉及的索引不在記憶體,則觸發到索引檔案中讀取響應的資料(一個block讀入),並加入到cache的佇列頂部;
如果insert/update/delete所涉及到的索引調整,則將記憶體中相應的block設定為dirty,並將新索引更新到檔案;dirty狀態的block在下一次被命中是重新載入或者被LRU移除.
key_cache_age_threshold是設計定block的"活躍性"基數的,由其單位時間命中的次數來決定(簡單的這麼認為吧),如果其"活躍性"達到閥值,會導致此block被已到"hot區",如果hot區中的block低於閥值,
會被移動到"warm區".閥值的計算有個公式,請參考其他資料.
4) join_buffer_size:執行緒引數,當sql中存在表關聯且沒有使用索引的情況下使用.這個引數被建議為保留預設值,否則此引數的設定有可能導致查詢失敗(過小),增大此引數值,對記憶體有很大影響,而且不能
有效的提升效能.
5) sort_buffer_size:執行緒引數,如果當前操作中有資料排序時使用.建議此引數為預設值.
6) max_allowed_packet:用來定義sql查詢所允許返回的最大結果數,如果限定了此值,那麼在遇到大結果集處理是,有可能是無法進行或者是低效.
第七章:SQL生命週期
1) 捕獲慢查詢sql是優化的第一步,可以通過如下幾個手段獲得:
-->普通查詢日誌(General query log):mysql的啟動配置檔案中可以通過
general_log = 1(//開啟);
general_log_file = path/g_log.log
此日誌記錄下所有的被server指定的語句.通過select * from mysql.general_log可以簡單的看出日誌的記錄.
此日誌檔案,不能提供每條sql所執行的時間,只是提供了SQL語句/執行的執行緒ID,我們可以用這些分析某些sql執行的次數和頻率.
此日誌功能,不建議在生產環境中開啟.
2) 慢查詢日誌(slow query log):
通過在啟動配置檔案中配置:
slow_query_log = 1
slow_query_log_file = //
log_query_time = 2 (單位:微秒,千分之一毫秒)
log_output = FILE
對於所有執行時間超過log_query_time的查詢,都會記錄在慢查詢日誌中.此日誌中展示出執行的sql/實際耗時/client資訊等.
3) 二進位制日誌(binary log):
二進位制日誌,是mysql server必備的日誌檔案,對於所有的DDL以及非select型別的DML語句都將被記錄.
//
log-bin=path/bin.log
此日誌檔案不能幫助我們分析select查詢耗時問題,但是它提供了針對ALTER操作已經update/delete等語句造成的慢速問題.
4) processlist:
通過->show full processlist;質量可以幫助我們看到當前正在被執行的sql語句以及其處理狀態,重要的是,可以告訴我們耗時情況.不過此指令也可以提供我們檢視當前sql是否
處於"lock"狀態(死鎖問題).
順便提示一下,mysql對於死鎖有自己的檢測和預設解決方案,在大部分情況下無需應用干預lock.針對mysql死鎖的原因做如下簡單解釋:
mysql中鎖的型別分為表鎖(無死鎖情況),行鎖(最小粒度鎖,極少機率死鎖),葉鎖(一定機率死鎖).mysql中獲取lock不是在實際data上,而是在索引上,
針對表(例如innodb)索引可以同時存在主鍵索引和二級索引.如果一條語句操作了非主鍵索引,MySQL會先鎖定該非主鍵索引,再鎖定相關的主鍵索引。
在UPDATE、DELETE操作時,MySQL不僅鎖定WHERE條件掃描過的所有索引記錄,而且會鎖定相鄰的鍵值,即所謂的next-key locking。
例如如下2個語句:
update table set name = ... where age > 20;
update table set name = ... where id = 145;
加入id = 145的資料,其age > 25,意味著這2個sql都會覆蓋一個id = 145的資料,如果他們同時執行,而且第一個語句首先被接受的情況下,將會發生:
語句1首先對age > 20記錄的二級索引上鎖,然後獲得記錄的主鍵,再對主鍵索引加鎖..
在語句1尚未對主鍵加鎖時,語句2成功的獲取主鍵索引值,此時它也希望獲得name索引上的鎖...此時死鎖產生了.
//備註,此sql的前提是,name欄位為二級索引,id為主鍵索引.
儘管mysql提供了死鎖自動檢測和解決方案,我們還是期望對索引值進行批量更新的sql,最好先轉換成主鍵查詢條件,例如:
select id from table where age > 20;
update table set name = ** where id in(....),通過程式達成對鎖獲取順序的共識.
5) 工具採集:
這個可能不是生產環境中所允許的,不過我們可以使用一些開源的mysql server監控工具,來獲得實時的慢查詢語句資訊.
第八章: 隱藏的效能
1) 組合你的DDL:
我們都知道DDL語句,將會對錶加鎖,且阻塞任何其他的資料查詢.特別在生產環境中,如果資料量很大,那麼DDL語句的執行將是很耗時的.
例如,我們向表新增多個索引:
ADD index ..
ADD index ..
如果我們將上述2個DDL,組合成一個:ADD INDEX..,ADD INDEX..那麼實際的阻塞行為將只有一次,而且很大程度上減少了阻塞時間.Alter操作底層實際上是對整個表進行了一次"full copy";
儘管這個策略在不同的mysql版本中進行各種優化,但是alter仍然是耗時的以及無法避免的對錶結構和資料的copy過程..組合DDL語句,就以為一次copy即可.
2) 移除重複索引
重複索引帶來的2個直接影響就是:DML操作將會增加多餘的工作量來維護索引和資料,再者增加沒有必要的磁碟儲存開支.
例如,對主鍵再次建立索引,組合索引的最左欄位再次建立索引等,都屬於重複索引.
index m1 (name),
index m2 (name,type)
事實上name欄位已經存在m2索引的最左側,沒有必要對name列再次單獨建立索引.
3) 移除無用索引
隨著業務的發展,可能系統的sql語句已經調整,將導致某些索引已經不再會被使用到,因此可以移除這些無用的索引.
這個操作,可能需要整理出目前在用的所有sql語句,經過綜合分析之後,才能決定某些索引是否可用.
4) 選擇合適的索引列屬性
-->bigInt vs int
bigint為8個位元組,int為4個位元組,對於"無/有符號"的情況下所支援的值域不同.
比如,對於自增的id,大家都習慣設定為bigint,其實想一想,一張表絕對不可能達到bigint極限,甚至int的極限都達不到,為何不宣告為無符號int型別呢?可以大大減少儲存空間.
-->Datetime vs timestamp
Datetime儲存為8個位元組,timestamp為4個位元組.如果業務允許(timestamp存在時間界限,每個N年會被重新計算)可以考慮使用timestamp.
Enum:列舉型別是mysql中表達有限值域的最佳屬性.比如:性別(男/女),比如,物品型別等等.列舉的特點是:提供了資料的值域約束功能,同時只需要一個位元組即可表達最多255中值.
NULL VS NOT-NULL:除非你能確定此欄位可以包含不確定值(NULL),那麼你最好對此此欄位設定為not null.這主要是從索引的角度考慮,如果一個欄位為索引列,且大量存在NULL,
是不利於索引查詢資料.
此外,在存在或者潛在存在表關聯的欄位,最好此欄位的屬性(型別,字符集,長度,NULL約束)都要保持一致,否則可能會導致在關聯查詢時索引失效.
5) 其他的優化:
->show profile for queury x;x表示上下文中指定的查詢語句.show profile能夠幫助我們檢視一個sql被執行(以及被優化)的所有步驟和每個步驟的耗時情況.
比如:starting --> check permissions --> open table --> system lock -> create tmp table -->executing -->copy to tmp table-->sending data -->....
因為一條sql最終輸出結果需要經過很多步驟,每條sql可能不盡相同.我們可以從每個步驟中獲得其執行的時間,來了解究竟耗時的原因.
6) 表關聯:
表關聯操作,如果沒有依賴索引,將可能造成麻煩.即使再有索引的情況下,表關聯也是一個耗時/耗記憶體的操作.
左連線:對where字句的篩選結果中,參與關聯的表,輸出結果以左邊為基準.對於右表缺失的列將以NULL補白.如果沒有where字句,將全表資料輸出.
右連線:原理同上
內連線:不以任何表為基準,在符合where字句的篩選結果中,只輸出"on"關聯條件的資料,不存在NULL補白問題.
7) 按需輸出列:
根據實際需要輸出列,輸出多餘的列,將會導致不必要的IO操作,以及對臨時表記憶體的消耗,同時如果輸出的列不被索引覆蓋,也將會導致額外的磁碟IO檢索.
第九章:EXPLAIN語法詳解
1) EXPAIN tableName,此指令幾乎等同於DESC tableName.
如果對update/delete語句使用EXPLAIN分析,最終將會轉換成select方式.比如 EXPLAIN update table set.. where name = ..//等同於EXPLAIN select name from table where name = ..
如下介紹EXPLAIN對select語句的輸出資訊.
-->key: 表示當前select使用的索引
-->rows: sql為了得到指定結果,可能(預估)檢索的行數,此值與表總行數的比值越低則表示索引的效果越好.
-->possible_keys:sql的執行,可被選擇的索引列表,最終將會從此列表中評估出執行效率最高的一個索引,作為最終的結果執行.
-->key_len:索引值的長度,位元組數,此值越小,表示索引所佔磁碟/記憶體空間越小.也意味著索引值的比較更加高效.
-->select_type:當前sql的查詢型別,比如:
simple(簡單查詢,無子查詢,無符合查詢語法等),
PRIMARY(在符合查詢是,此表為主表),
DERIVED(派生,即此表不代表實際物理表,它可能是字句生成的臨時表名),
UNION(關聯型別)
-->Extra:表示sql的一些額外提示資訊.資訊中有可能出現如下幾個字樣:
Using where:使用where語句過濾了資料,且有效了使用索引.
Using temprary:表示使用了記憶體中臨時表.比如在來自多個表的不同欄位使用DISTINCT/ORDER BY/GROUP BY等,導致資料在記憶體中以臨時表的方式多次計算.
Using filesort:在沒有使用索引的情況下,使用了ORDER BY對結果進行排序.
Using index:在滿足輸出結果列的情況下,只使用了索引值,而沒有進行實際磁碟檢索.
-->type:表示sql在篩選資料時所採取的訪問型別
const:此表最多隻有一行匹配,唯一索引或者主鍵索引時發生.
System:const的一種特例,表示表有且只有一行匹配.主鍵索引時發生.
eq_ref:在出現表關聯時,使用=符號作為2個關聯表字段的比較,且使用了當前表的唯一索引或者主鍵;導致關聯表從當前表中只讀取到一條記錄,類似於非關聯操作中的const.
例如:explian select t1.name ,t2.name from t1,t2 where t1.id = t2.uid;如果此時t1.id為主鍵或者唯一索引,那麼在執行計劃中,很有可能t1條目的type為eq_ref.
ref:區別與eq_ref,即關聯表時,使用=或者<=>符合比較,當前表沒有使用到主鍵或者唯一索引,但是有效的使用了二級索引篩選出了有限結果集,對於另一個關聯表而言,資料將依賴於此結果集.
此型別在表關聯中,應該有較好效能.
ref_or_null:查詢行中對索引列使用IS NULL語法,例如:select * from table where index_cloumn is null.
range:範圍檢索時,對索引列使用 >,< or,between等比較符使,導致索引進行範圍檢索.
index:使用到了索引,但是對索引樹進行了全量掃描,效能僅高於all.
all:沒有使用索引,對錶實際資料進行了全表掃描.
效能由上往下逐漸變低,非關聯查詢,index級別是最低要求.對於關聯查詢,儘量保持為ref或者eq_ref.