ie9無法獲取未定義或 null 引用的屬性“indexof”_explain的屬性詳解與提速百倍的優化示例...
技術標籤:ie9無法獲取未定義或 null 引用的屬性“indexof”
在MySQL中,可以通過EXPLAIN命令獲取MySQL如何執行SELECT語句的資訊,包括在SELECT語句執行過程中表如何連線和連線的順序。
EXPLAIN命令雖然沒有提供任何優化建議,但它能夠提供重要的資訊有助於調優決策。
EXPLAIN只能解釋SELECT操作,其他操作要重寫為SELECT後檢視執行計劃。
使用方法
在要查詢的SQL語句前加上explain,然後執行就可以了。如:
EXPLAIN SELECT goods_name, seckill_priceFROM seckill_goods, goodsWHERE seckill_goods.id = goods.id
explain屬性的含義
執行上面SQL語句之後。
explain
各屬性含義:(筆者常關注的引數:type、key、rows)
id
查詢的序列號。
id是一組數字,表示查詢中執行select子句或操作表的順序;如果id相同,則執行順序從上至下,如果是子查詢,id的序號會遞增,id越大則優先順序越高,越先會被執行。
id列為null則表示這一行是一個結果集,不需要使用它來進行查詢。
select_type
顯示每個select子句的查詢型別。
- simple:表示不需要union操作或者不包含子查詢的簡單select查詢。有連線查詢時,外層的查詢為simple,且只有一個。
- primary:一個需要union操作或者含有子查詢的select,位於最外層的單位查詢的select_type即為primary。且只有一個。
- union:union連線的兩個select查詢,第一個查詢是dervied派生表,除了第一個表外,第二個以後的表select_type都是union。
- dependent union:與union一樣,出現在union 或union all語句中,但是這個查詢要受到外部查詢的影響。
- union result:包含union的結果集,在union和union all語句中,因為它不需要參與查詢,所以id欄位為null。
- subquery:除了from字句中包含的子查詢外,其他地方出現的子查詢都可能是subquery。
- dependent subquery:與dependent union類似,表示這個subquery的查詢要受到外部表查詢的影響。
- derived:from字句中出現的子查詢,也叫做派生表,其他資料庫中可能叫做內聯檢視或巢狀select。
table
輸出的行所引用的表。
- 顯示的查詢表名,如果查詢使用了別名,那麼這裡顯示的是別名。
- 如果不涉及對資料表的操作,那麼這顯示為null。
- 如果顯示為尖括號括起來的就表示這個是臨時表,後邊的N就是執行計劃中的id,表示結果來自於這個查詢產生。
- 如果是尖括號括起來的,與類似,也是一個臨時表,表示這個結果來自於union查詢的id為M,N的結果集。
partitions
版本5.7以前,該項是explain partitions顯示的選項,5.7以後成為了預設選項。該列顯示的是分割槽表命中的分割槽情況。非分割槽表該欄位為空(null)。
type
對錶訪問方式,表示MySQL在表中找到所需行的方式,又稱“訪問型別”。
效能依次由好到差:system,const,eq_ref,ref,fulltext,ref_or_null,unique_subquery,index_subquery,range,index_merge,index,all。
除了all之外,其他的type都可以使用到索引。除了index_merge之外,其他的type只可以用到一個索引。
- system:表中只有一行資料或者是空表,且只能用於myisam和memory表。
- const:查詢主鍵索引,返回的資料至多一條(0或者1條)。 屬於精確查詢。
- eq_ref:查詢唯一性索引,返回的資料至多一條。屬於精確查詢。
- ref:查詢非唯一性索引,返回匹配某一條件的多條資料。屬於精確查詢、資料返回可能是多條。
- fulltext:全文索引檢索,要注意,全文索引的優先順序很高,若全文索引和普通索引同時存在時,mysql不管代價,優先選擇使用全文索引。
- ref_or_null:與ref方法類似,只是增加了null值的比較。實際用的不多。
- unique_subquery:用於where中的in形式子查詢,子查詢返回不重複值唯一值。
- index_subquery:用於in形式子查詢使用到了輔助索引或者in常數列表,子查詢可能返回重複值,可以使用索引將子查詢去重。
- range:索引範圍掃描,常見於使用>,
- index_merge:表示查詢使用了兩個以上的索引,最後取交集或者並集,常見and ,or的條件使用了不同的索引,官方排序這個在ref_or_null之後,但是實際上由於要讀取所個索引,效能可能都不如range。
- index:索引全表掃描,把索引從頭到尾掃一遍,常見於使用索引列就可以處理不需要讀取資料檔案的查詢、可以使用索引排序或者分組的查詢。
- all:不使用任何索引,進行全表掃描,效能最差。
possible_keys
顯示可能應用在這張表中的索引,一個或多個。查詢涉及到的欄位上若存在索引,則該索引將被列出,但不一定被查詢實際使用。
該列完全獨立於EXPLAIN輸出所示的表的次序。這意味著在possible_keys中的某些鍵實際上不能按生成的表次序使用。
如果該列是NULL,則沒有相關的索引。在這種情況下,可以通過檢查WHERE子句是否引用某些列或適合索引的列來提高查詢效能
key
顯示MySQL實際決定使用的鍵(索引),必然包含在possible_keys中,如果沒有索引被選擇,是NULL。
type為index_merge時,這裡可能出現兩個以上的索引,其他的type這裡只會出現一個。
key_len
使用到索引欄位的長度。
如果是單列索引,那就返回整個索引長度;如果是多列索引,那麼查詢不一定都能使用到所有的列,返回具體使用索引的長度(沒有使用到的列,這裡不會計算進去)。對比這個數值和多列索引的總長度,就可以推測是否使用到所有的列。
mysql的ICP特性使用到的索引不會計入其中。
key_len只計算where條件用到的索引長度,而排序和分組就算用到了索引,也不會計算到key_len中。
key_len顯示的值為索引欄位的最大可能長度,並非實際使用長度,即key_len是根據表定義計算而得,不是通過表內檢索出的。
ref
顯示索引的那一列被使用了,如果可能的話,最好是一個常數。哪些列或常量被用於查詢索引列上的值。
rows
MySQL根據表統計資訊及索引選用情況,估算mysql查詢過程中遍歷的行數,不是準確值。
filtered
使用explain extended時會出現這個列,5.7之後的版本預設就有這個欄位,不需要使用explain extended了。這個欄位表示儲存引擎返回的資料在server層過濾後,剩下多少滿足查詢的記錄數量的比例,這個值是百分比,不是具體記錄數。
Extra
執行情況的說明和描述,顯示資訊種類非常多,下面只列舉常見的結果。
- distinct:在select部分使用了distinct關鍵字
- no tables used:不帶from字句的查詢或者From dual查詢。
- 使用not in()形式子查詢或not exists運算子的連線查詢,這種叫做反連線。即,一般連線查詢是先查詢內表,再查詢外表,反連線就是先查詢外表,再查詢內表。
- using filesort:排序時無法使用到索引時,就會出現這個。常見於order by和group by語句中。
- using index:查詢時不需要回表查詢,直接通過索引就可以獲取查詢的資料。
- using_union:表示使用or連線各個使用索引的條件時,該資訊表示從處理結果獲取並集
- using intersect:表示使用and的各個索引的條件時,該資訊表示是從處理結果獲取交集
- using sort_union和using sort_intersection:與前面兩個對應的類似,只是他們是出現在用and和or查詢資訊量大時,先查詢主鍵,然後進行排序合併後,才能讀取記錄並返回。
- using where:表示儲存引擎返回的記錄並不是所有的都滿足查詢條件,需要在server層進行過濾。查詢條件中分為限制條件和檢查條件,5.6之前,儲存引擎只能根據限制條件掃描資料並返回,然後server層根據檢查條件進行過濾再返回真正符合查詢的資料。5.6.x之後支援ICP特性,可以把檢查條件也下推到儲存引擎層,不符合檢查條件和限制條件的資料,直接不讀取,這樣就大大減少了儲存引擎掃描的記錄數量。extra列顯示using index condition
- using temporary:表示使用了臨時表儲存中間結果,通常是因為GROUP BY的列沒有索引,或者GROUP BY和ORDER BY的列不一樣,也需要建立臨時表,建議新增適當的索引。臨時表可以是記憶體臨時表和磁碟臨時表,執行計劃中看不出來,需要檢視status變數,used_tmp_table,used_tmp_disk_table才能看出來。
- firstmatch(tb_name):5.6.x開始引入的優化子查詢的新特性之一,常見於where字句含有in()型別的子查詢。如果內表的資料量比較大,就可能出現這個
- loosescan(m..n):5.6.x之後引入的優化子查詢的新特性之一,在in()型別的子查詢中,子查詢返回的可能有重複記錄時,就可能出現這個
- filtered:使用explain extended時會出現這個列,5.7之後的版本預設就有這個欄位,不需要使用explain extended了。這個欄位表示儲存引擎返回的資料在server層過濾後,剩下多少滿足查詢的記錄數量的比例,注意是百分比,不是具體記錄數。
優化示例
優化示例章節,節選“美團技術團隊”的“MySQL索引原理及慢查詢優化”文章,點選檢視(如果連結失效,請檢視原文)
慢查詢優化基本步驟:
- 先執行看看是否真的很慢,注意設定SQL_NO_CACHE
- where條件單表查,鎖定最小返回記錄表——把查詢語句的where都應用到表中返回的記錄數最小的表開始查起,單表每個欄位分別查詢,看哪個欄位的區分度最高
- explain檢視執行計劃,是否從鎖定記錄較少的表開始查詢。
- order by limit 形式的sql語句讓排序的表優先查
- 瞭解業務方使用場景
- 加索引時參照建索引的幾大原則
- 觀察結果,不符合預期繼續從0分析
不同的SQL語句書寫方式對於效率往往有本質的差別,這要求我們對mysql的執行計劃和索引原則有非常清楚的認識,請看下面的語句:
select distinct cert.emp_id from cm_log cl inner join ( select emp.id as emp_id, emp_cert.id as cert_id from employee emp left join emp_certificate emp_cert on emp.id = emp_cert.emp_id where emp.is_deleted=0 ) cert on ( cl.ref_table='Employee' and cl.ref_oid= cert.emp_id ) or ( cl.ref_table='EmpCertificate' and cl.ref_oid= cert.cert_id ) where cl.last_upd_date >='2013-11-07 15:03:00' and cl.last_upd_date<='2013-11-08 16:00:00';
1.直接執行嘗試
先執行一下,53條記錄 1.87秒,又沒有用聚合語句,比較慢
53 rows in set (1.87 sec)
2.執行explain檢視執行計劃
執行結果請點選文末“瞭解更多”。
簡述一下執行計劃,首先mysql根據idx_last_upd_date索引掃描cm_log表獲得379條記錄;然後查表掃描了63727條記錄,分為兩部分,derived表示構造表,也就是不存在的表,可以簡單理解成是一個語句形成的結果集,後面的數字表示語句的ID。derived2表示的是ID = 2的查詢構造了虛擬表,並且返回了63727條記錄。我們再來看看ID = 2的語句究竟做了寫什麼返回了這麼大量的資料,首先全表掃描employee表13317條記錄,然後根據索引emp_certificate_empid關聯emp_certificate表,rows = 1表示,每個關聯都只鎖定了一條記錄,效率比較高。獲得後,再和cm_log的379條記錄根據規則關聯。從執行過程上可以看出返回了太多的資料,返回的資料絕大部分cm_log都用不到,因為cm_log只鎖定了379條記錄。
3.優化分析
如何優化呢?可以看到在執行完後還是要和cm_log做join,那麼我們能不能之前和cm_log做join呢?仔細分析語句不難發現,其基本思想是如果cm_log的ref_table是EmpCertificate就關聯emp_certificate表,如果ref_table是Employee就關聯employee表,我們完全可以拆成兩部分,並用union連線起來,注意這裡用union,而不用union all是因為原語句有“distinct”來得到唯一的記錄,而union恰好具備了這種功能。如果原語句中沒有distinct不需要去重,就可以直接使用union all了,因為使用union需要去重的動作,會影響SQL效能。
優化過的語句如下:
select emp.id from cm_log cl inner join employee emp on cl.ref_table = 'Employee' and cl.ref_oid = emp.id where cl.last_upd_date >='2013-11-07 15:03:00' and cl.last_upd_date<='2013-11-08 16:00:00' and emp.is_deleted = 0 unionselect emp.id from cm_log cl inner join emp_certificate ec on cl.ref_table = 'EmpCertificate' and cl.ref_oid = ec.id inner join employee emp on emp.id = ec.emp_id where cl.last_upd_date >='2013-11-07 15:03:00' and cl.last_upd_date<='2013-11-08 16:00:00' and emp.is_deleted = 0
4.確保優化後的結果與之前結果一致
本次優化不需要了解業務場景,只需要改造的語句和改造之前的語句保持結果一致
5.判斷是否加索引
現有索引可以滿足,不需要建索引
6.觀察優化結果
用改造後的語句實驗一下,只需要10ms,降低了近200倍!
執行結果請點選文末“瞭解更多”。