MySQL資料庫訪問效能優化
MYSQL如此方便和穩定,以至於我們在開發 WEB 程式的時候非常少想到它。即使想到優化也是程式級別的,比方不要寫過於消耗資源的SQL語句。可是除此之外,在整個系統上仍然有非常多能夠優化的地方。
1 優化原理
說起MySQL的查詢優化,相信大家會想到:不能使用SELECT *、不使用NULL欄位、合理建立索引、為欄位選擇合適的資料型別..... 你是否真的理解這些優化技巧?是否理解其背後的工作原理?在實際場景下效能真有提升嗎?我想未必。因而理解這些優化建議背後的原理就尤為重要,希望本文能讓你重新審視這些優化建議,並在實際業務場景下合理的運用。MySQL邏輯架構
如果能在頭腦中構建一幅MySQL各元件之間如何協同工作的架構圖,有助於深入理解MySQL伺服器。下圖展示了MySQL的邏輯架構圖。MySQL邏輯架構
MySQL邏輯架構整體分為三層,最上層為客戶端層,並非MySQL所獨有,諸如:連線處理、授權認證、安全等功能均在這一層處理。
MySQL大多數核心服務均在中間這一層,包括查詢解析、分析、優化、快取、內建函式(比如:時間、數學、加密等函式)。所有的跨儲存引擎的功能也在這一層實現:儲存過程、觸發器、檢視等。
最下層為儲存引擎,其負責MySQL中的資料儲存和提取。和Linux下的檔案系統類似,每種儲存引擎都有其優勢和劣勢。中間的服務層通過API與儲存引擎通訊,這些API介面遮蔽了不同儲存引擎間的差異。
MySQL查詢過程
我們總是希望MySQL能夠獲得更高的查詢效能,最好的辦法是弄清楚MySQL是如何優化和執行查詢的。一旦理解了這一點,就會發現:很多的查詢優化工作實際上就是遵循一些原則讓MySQL的優化器能夠按照預想的合理方式執行而已。當向MySQL傳送一個請求的時候,MySQL到底做了些什麼呢?
MySQL查詢過程
客戶端/服務端通訊協議
MySQL客戶端/服務端通訊協議是“半雙工”的:在任一時刻,要麼是伺服器向客戶端傳送資料,要麼是客戶端向伺服器傳送資料,這兩個動作不能同時發生。一旦一端開始傳送訊息,另一端要接收完整個訊息才能響應它,所以我們無法也無須將一個訊息切成小塊獨立傳送,也沒有辦法進行流量控制。客戶端用一個單獨的資料包將查詢請求傳送給伺服器,所以當查詢語句很長的時候,需要設定max_allowed_packet引數。但是需要注意的是,如果查詢實在是太大,服務端會拒絕接收更多資料並丟擲異常。
與之相反的是,伺服器響應給使用者的資料通常會很多,由多個數據包組成。但是當伺服器響應客戶端請求時,客戶端必須完整的接收整個返回結果,而不能簡單的只取前面幾條結果,然後讓伺服器停止傳送。因而在實際開發中,儘量保持查詢簡單且只返回必需的資料,減小通訊間資料包的大小和數量是一個非常好的習慣,這也是查詢中儘量避免使用SELECT *以及加上LIMIT限制的原因之一。
查詢快取
在解析一個查詢語句前,如果查詢快取是開啟的,那麼MySQL會檢查這個查詢語句是否命中查詢快取中的資料。如果當前查詢恰好命中查詢快取,在檢查一次使用者許可權後直接返回快取中的結果。這種情況下,查詢不會被解析,也不會生成執行計劃,更不會執行。
MySQL將快取存放在一個引用表(不要理解成table,可以認為是類似於HashMap的資料結構),通過一個雜湊值索引,這個雜湊值通過查詢本身、當前要查詢的資料庫、客戶端協議版本號等一些可能影響結果的資訊計算得來。所以兩個查詢在任何字元上的不同(例如:空格、註釋),都會導致快取不會命中。
如果查詢中包含任何使用者自定義函式、儲存函式、使用者變數、臨時表、mysql庫中的系統表,其查詢結果都不會被快取。比如函式NOW()或者CURRENT_DATE()會因為不同的查詢時間,返回不同的查詢結果,再比如包含CURRENT_USER或者CONNECION_ID()的查詢語句會因為不同的使用者而返回不同的結果,將這樣的查詢結果快取起來沒有任何的意義。
既然是快取,就會失效,那查詢快取何時失效呢?MySQL的查詢快取系統會跟蹤查詢中涉及的每個表,如果這些表(資料或結構)發生變化,那麼和這張表相關的所有快取資料都將失效。正因為如此,在任何的寫操作時,MySQL必須將對應表的所有快取都設定為失效。如果查詢快取非常大或者碎片很多,這個操作就可能帶來很大的系統消耗,甚至導致系統僵死一會兒。而且查詢快取對系統的額外消耗也不僅僅在寫操作,讀操作也不例外:
1.任何的查詢語句在開始之前都必須經過檢查,即使這條SQL語句永遠不會命中快取
2.如果查詢結果可以被快取,那麼執行完成後,會將結果存入快取,也會帶來額外的系統消耗
基於此,我們要知道並不是什麼情況下查詢快取都會提高系統性能,快取和失效都會帶來額外消耗,只有當快取帶來的資源節約大於其本身消耗的資源時,才會給系統帶來效能提升。但要如何評估開啟快取是否能夠帶來效能提升是一件非常困難的事情,也不在本文討論的範疇內。如果系統確實存在一些效能問題,可以嘗試開啟查詢快取,並在資料庫設計上做一些優化,比如:
1.用多個小表代替一個大表,注意不要過度設計
2.批量插入代替迴圈單條插入
3.合理控制快取空間大小,一般來說其大小設定為幾十兆比較合適
4.可以通過SQL_CACHE和SQL_NO_CACHE來控制某個查詢語句是否需要進行快取
最後的忠告是不要輕易開啟查詢快取,特別是寫密集型應用。如果你實在是忍不住,可以將query_cache_type設定為DEMAND,這時只有加入SQL_CACHE的查詢才會走快取,其他查詢則不會,這樣可以非常自由地控制哪些查詢需要被快取。
當然查詢快取系統本身是非常複雜的,這裡討論的也只是很小的一部分,其他更深入的話題,比如:快取是如何使用記憶體的?如何控制記憶體的碎片化?事務對查詢快取有何影響等等,讀者可以自行閱讀相關資料,這裡權當拋磚引玉吧。
語法解析和預處理
MySQL通過關鍵字將SQL語句進行解析,並生成一顆對應的解析樹。這個過程解析器主要通過語法規則來驗證和解析。比如SQL中是否使用了錯誤的關鍵字或者關鍵字的順序是否正確等等。預處理則會根據MySQL規則進一步檢查解析樹是否合法。比如檢查要查詢的資料表和資料列是否存在等等。查詢優化
經過前面的步驟生成的語法樹被認為是合法的了,並且由優化器將其轉化成查詢計劃。多數情況下,一條查詢可以有很多種執行方式,最後都返回相應的結果。優化器的作用就是找到這其中最好的執行計劃。MySQL使用基於成本的優化器,它嘗試預測一個查詢使用某種執行計劃時的成本,並選擇其中成本最小的一個。在MySQL可以通過查詢當前會話的last_query_cost的值來得到其計算當前查詢的成本。
mysql> select * from t_message limit 10; ...省略結果集 mysql> show status like 'last_query_cost'; +-----------------+-------------+ | Variable_name | Value | +-----------------+-------------+ | Last_query_cost | 6391.799000 | +-----------------+-------------+
示例中的結果表示優化器認為大概需要做6391個數據頁的隨機查詢才能完成上面的查詢。這個結果是根據一些列的統計資訊計算得來的,這些統計資訊包括:每張表或者索引的頁面個數、索引的基數、索引和資料行的長度、索引的分佈情況等等。
有非常多的原因會導致MySQL選擇錯誤的執行計劃,比如統計資訊不準確、不會考慮不受其控制的操作成本(使用者自定義函式、儲存過程)、MySQL認為的最優跟我們想的不一樣(我們希望執行時間儘可能短,但MySQL值選擇它認為成本小的,但成本小並不意味著執行時間短)等等。
MySQL的查詢優化器是一個非常複雜的部件,它使用了非常多的優化策略來生成一個最優的執行計劃:
1.重新定義表的關聯順序(多張表關聯查詢時,並不一定按照SQL中指定的順序進行,但有一些技巧可以指定關聯順序)
2.優化MIN()和MAX()函式(找某列的最小值,如果該列有索引,只需要查詢B+Tree索引最左端,反之則可以找到最大值,具體原理見下文)
3.提前終止查詢(比如:使用Limit時,查詢到滿足數量的結果集後會立即終止查詢)
4.優化排序(在老版本MySQL會使用兩次傳輸排序,即先讀取行指標和需要排序的欄位在記憶體中對其排序,然後再根據排序結果去讀取資料行,而新版本採用的是單次傳輸排序,也就是一次讀取所有的資料行,然後根據給定的列排序。對於I/O密集型應用,效率會高很多)
隨著MySQL的不斷髮展,優化器使用的優化策略也在不斷的進化,這裡僅僅介紹幾個非常常用且容易理解的優化策略,其他的優化策略,大家自行查閱吧。
查詢執行引擎
在完成解析和優化階段以後,MySQL會生成對應的執行計劃,查詢執行引擎根據執行計劃給出的指令逐步執行得出結果。整個執行過程的大部分操作均是通過呼叫儲存引擎實現的介面來完成,這些介面被稱為handler API。查詢過程中的每一張表由一個handler例項表示。實際上,MySQL在查詢優化階段就為每一張表建立了一個handler例項,優化器可以根據這些例項的介面來獲取表的相關資訊,包括表的所有列名、索引統計資訊等。儲存引擎介面提供了非常豐富的功能,但其底層僅有幾十個介面,這些介面像搭積木一樣完成了一次查詢的大部分操作。
返回結果給客戶端
查詢執行的最後一個階段就是將結果返回給客戶端。即使查詢不到資料,MySQL仍然會返回這個查詢的相關資訊,比如改查詢影響到的行數以及執行時間等等。如果查詢快取被開啟且這個查詢可以被快取,MySQL也會將結果存放到快取中。
結果集返回客戶端是一個增量且逐步返回的過程。有可能MySQL在生成第一條結果時,就開始向客戶端逐步返回結果集了。 這樣服務端就無須儲存太多結果而消耗過多記憶體,也可以讓客戶端第一時間獲得返回結果。需要注意的是,結果集中的每一行都會以一個滿足①中所描述的通訊協議的資料包傳送,再通過TCP協議進行傳輸,在傳輸過程中,可能對MySQL的資料包進行快取然後批量傳送。
回頭總結一下MySQL整個查詢執行過程,總的來說分為6個步驟:
1.客戶端向MySQL伺服器傳送一條查詢請求
2.伺服器首先檢查查詢快取,如果命中快取,則立刻返回儲存在快取中的結果。否則進入下一階段
3.伺服器進行SQL解析、預處理、再由優化器生成對應的執行計劃
4.MySQL根據執行計劃,呼叫儲存引擎的API來執行查詢
5.將結果返回給客戶端,同時快取查詢結果
2 事務及引擎
MySQL的儲存引擎可能是所有關係型資料庫產品中最具有特色的了,不僅可以同時使用多種儲存引擎,而且每種儲存引擎和MySQL之間使用外掛方式這種非常鬆的耦合關係。
由於各儲存引擎功能特性差異較大,需要關注如何來選擇合適的儲存引擎來應對不同的業務場景。
一、MyISAM
o 特性
1.不支援事務:MyISAM儲存引擎不支援事務,所以對事務有要求的業務場景不能使用
2.表級鎖定:其鎖定機制是表級索引,這雖然可以讓鎖定的實現成本很小但是也同時大大降低了其併發效能
3.讀寫互相阻塞:不僅會在寫入的時候阻塞讀取,MyISAM還會在讀取的時候阻塞寫入,但讀本身並不會阻塞另外的讀
4.只會快取索引:MyISAM可以通過key_buffer快取以大大提高訪問效能減少磁碟IO,但是這個快取區只會快取索引,而不會快取資料
o 適用場景
1.不需要事務支援(不支援)
2.併發相對較低(鎖定機制問題)
3.資料修改相對較少(阻塞問題)
4.以讀為主
5.資料一致性要求不是非常高
o 最佳實踐
1.儘量索引(快取機制)
2.調整讀寫優先順序,根據實際需求確保重要操作更優先
3.啟用延遲插入改善大批量寫入效能
4.儘量順序操作讓insert資料都寫入到尾部,減少阻塞
5.分解大的操作,降低單個操作的阻塞時間
6.降低併發數,某些高併發場景通過應用來進行排隊機制
7.對於相對靜態的資料,充分利用Query Cache可以極大的提高訪問效率
8.MyISAM的Count只有在全表掃描的時候特別高效,帶有其他條件的count都需要進行實際的資料訪問
二、InnoDB
o 特性
1.具有較好的事務支援:支援4個事務隔離級別,支援多版本讀
2.行級鎖定:通過索引實現,全表掃描仍然會是表鎖,注意間隙鎖的影響
3.讀寫阻塞與事務隔離級別相關
4.具有非常高效的快取特性:能快取索引,也能快取資料
5.整個表和主鍵以Cluster方式儲存,組成一顆平衡樹
6.所有Secondary Index都會儲存主鍵資訊
o 適用場景
1.需要事務支援(具有較好的事務特性)
2.行級鎖定對高併發有很好的適應能力,但需要確保查詢是通過索引完成
3.資料更新較為頻繁的場景
4.資料一致性要求較高
5.硬體裝置記憶體較大,可以利用InnoDB較好的快取能力來提高記憶體利用率,儘可能減少磁碟 IO
o 最佳實踐
1.主鍵儘可能小,避免給Secondary index帶來過大的空間負擔
2.避免全表掃描,因為會使用表鎖
3.儘可能快取所有的索引和資料,提高響應速度
4.在大批量小插入的時候,儘量自己控制事務而不要使用autocommit自動提交
5.合理設定innodb_flush_log_at_trx_commit引數值,不要過度追求安全性
6.避免主鍵更新,因為這會帶來大量的資料移動
三、NDBCluster
o 特性
1.分散式:分散式儲存引擎,可以由多個NDBCluster儲存引擎組成叢集分別存放整體資料的一部分
2.支援事務:和Innodb一樣,支援事務
3.可與mysqld不在一臺主機:可以和mysqld分開存在於獨立的主機上,然後通過網路和mysqld通訊互動
4.記憶體需求量巨大:新版本索引以及被索引的資料必須存放在記憶體中,老版本所有資料和索引必須存在與記憶體中
o 適用場景
1.具有非常高的併發需求
2.對單個請求的響應並不是非常的critical
3.查詢簡單,過濾條件較為固定,每次請求資料量較少,又不希望自己進行水平Sharding
o 最佳實踐
1.儘可能讓查詢簡單,避免資料的跨節點傳輸
2.儘可能滿足SQL節點的計算效能,大一點的叢集SQL節點會明顯多餘Data節點
3.在各節點之間儘可能使用萬兆網路環境互聯,以減少資料在網路層傳輸過程中的延時
3 快取引數優化
從記憶體中讀取一個數據庫的時間是微秒級別,而從一塊普通硬碟上讀取一個IO是在毫秒級別,二者相差3個數量級。所以,要優化資料庫,首先第一步需要優化的就是IO,儘可能將磁碟IO轉化為記憶體IO。從MySQL 資料庫IO相關引數(快取引數)的角度來看看可以通過以下引數進行IO優化(建議級):
· query_cache_type : 如果全部使用innodb儲存引擎,建議為0,如果使用MyISAM 儲存引擎,建議為2,同時在SQL語句中顯式控制是否使用query cache;
· query_cache_size: 根據命中率(Qcache_hits/(Qcache_hits+Qcache_inserts)*100))進行調整,一般不建議太大,256MB可能已經差不多了,大型的配置型靜態資料可適當調大;
· binlog_cache_size: 一般環境2MB~4MB是一個合適的選擇,事務較大且寫入頻繁的資料庫環境可以適當調大,但不建議超過32MB;
· key_buffer_size: 如果不使用MyISAM儲存引擎,16MB足以,用來快取一些系統表資訊等。如果使用 MyISAM儲存引擎,在記憶體允許的情況下,儘可能將所有索引放入記憶體,簡單來說就是“越大越好”;
· bulk_insert_buffer_size: 如果經常性的需要使用批量插入的特殊語句(上面有說明)來插入資料,可以適當調大該引數至16MB~32MB,不建議繼續增大,某人8MB;
· innodb_buffer_pool_size: 如果不使用InnoDB儲存引擎,可以不用調整這個引數,如果需要使用,在記憶體允許的情況下,儘可能將所有的InnoDB資料檔案存放如記憶體中,同樣將但來說也是“越大越好”;
· innodb_additional_mem_pool_size: 一般的資料庫建議調整到8MB~16MB,如果表特別多,可以調整到32MB,可以根據error log中的資訊判斷是否需要增大;
· innodb_log_buffer_size: 預設是1MB,系的如頻繁的系統可適當增大至4MB~8MB。當然如上面介紹所說,這個引數實際上還和另外的flush引數相關。一般來說不建議超過32MB;
· innodb_max_dirty_pages_pct: 根據以往的經驗,重啟恢復的資料如果要超過1G的話,啟動速度會比較慢,幾乎難以接受,所以建議不大於1GB/innodb_buffer_pool_size(GB)*100這個值。當然,如果你能夠忍受啟動時間比較長,而且希望儘量減少記憶體至磁碟的flush,可以將這個值調整到90,但不建議超過90。
注:以上取值範圍僅僅只是根據以往遇到的資料庫場景所得到的一些優化經驗值,並不一定適用於所有場景,所以在實際優化過程中還需要大家自己不斷的調整分析。
4 SQL優化
4.1 優化目標
1、減少 IO 次數
IO永遠是資料庫最容易瓶頸的地方,這是由資料庫的職責所決定的,大部分資料庫操作中超過90%的時間都是 IO 操作所佔用的,減少 IO 次數是 SQL 優化中需要第一優先考慮,當然,也是收效最明顯的優化手段。
2、降低 CPU 計算
除了 IO 瓶頸之外,SQL優化中需要考慮的就是 CPU 運算量的優化了。order by,group by,distinct … 都是消耗 CPU 的大戶(這些操作基本上都是 CPU 處理記憶體中的資料比較運算)。當我們的 IO 優化做到一定階段之後,降低 CPU計算也就成為了我們 SQL 優化的重要目標。
4.2 優化方法
一、監控分析
1、硬體資源監控
關注的主要資料庫伺服器在IO和CPU方面的指標。
2、mysql效能分析器
可以利用mysql profiling(mysql效能分析器)來優化sql語句,即檢視SQL執行消耗系統資源的資訊(需要開啟才能應用該功能)。
3、慢查詢分析
通過慢日誌查詢可以知道哪些SQL語句執行效率低下,那些sql語句使用的頻率高等。
對MySQL查詢語句的監控、分析、優化是MySQL優化非常重要的一步。開啟慢查詢日誌後,由於日誌記錄操作,在一定程度上會佔用CPU資源影響mysql的效能,但是可以階段性開啟來定位效能瓶頸。
二、改變 SQL 執行計劃
明確了優化目標之後,我們需要確定達到我們目標的方法。對於 SQL 語句來說,達到上述2個優化目標的方法其實只有一個,那就是改變 SQL 的執行計劃,讓他儘量“少走彎路”,儘量通過各種“捷徑”來找到我們需要的資料,以達到“減少IO次數”和“降低 CPU 計算”的目標。
使用explain命令檢視query語句的效能:
EXPLAIN select * from tablename;##檢視執行計劃中的sql效能
上面這是最簡單的執行計劃例項,來分析一下上面的這幾個欄位。
1、id:id主要是用來標識sql執行順序,如果沒有子查詢,一般來說id只有1個,執行順序也是從上到下。
2、select_type:每個select子句的型別,主要分成下面幾種:
a:SIMPLE:查詢中不包含任何子查詢或者union
b:PRIMARY:查詢中包含了任何複雜的子部分,最外層的就會變成PRIMARY
c:SUBQUERY:在SELECT或者WHERE列表中包含了子查詢
d:DERIVED:在FROM中包含了子查詢
e:UNION:如果第二個SELECT出現在UNION之後,則被標記為UNION,如果UNION包含在FROM子句的子查詢中,外層SELECT會被標記為:DERIVED
f:UNION RESULT從UNION表獲取結果的select
3、type:是指MySQL在表中找到所需行的方式,也就是訪問行的“型別”,從a開始,效率逐漸上升:
a:all:全表掃描,效率最低
b:index:index會根據索引樹遍歷
c:range:索引範圍掃描,返回匹配值域的行。
d:ref:非唯一性索引掃描,返回匹配某個單獨值的所有行。一般是指多列的唯一索引中的某一列。
e:eq_ref:唯一性索引掃描,表中只有一條記錄與之匹配。
f:const、system:主要針對查詢中有常量的情況,如果結果只有一行會變成system
g:NULL:顯而易見,既不走表,也不走索引
4、possible_keys
possible_keys列預估了mysql能夠為當前查詢選擇的索引,這個欄位是完全獨立於執行計劃中輸出的表的順序,意味著在實際查詢中可能用不到這些索引。
如果該欄位為空則意味著沒有可使用的索引,這個時候你可以考慮為where後面的欄位建立索引。
5、key
這個欄位表示了mysql真實使用的索引(如果為NULL,則沒有使用索引)。如果mysql優化過程中沒有加索引,可以強制加hint使用索引。
6、key_len
索引長度欄位顧名思義,表示了mysql查詢中使用的索引的長度(最大可能長度),並非實際使用長度,理論上長度越短越好。key_len是根據表定義計算而得的,不是通過表內檢索出的。
7、ref
這個欄位一般是指一些常量用於選擇過濾(顯示索引的那一列被使用了,如果可能,是一個常量const)。
8、rows
預估結果集的條數,可能不一定完全準確(根據表統計資訊及索引選用情況,大致估算出找到所需的記錄所需要讀取的行數)。
9、Extra
不適合在其他欄位中顯示,但是十分重要的額外資訊:
a:Using filesort:mysql對資料使用一個外部的索引排序,而不是按照表內的索引進行排序讀取。也就是說mysql無法利用索引完成的排序操作成為“檔案排序”。
b:Using temporary:使用臨時表儲存中間結果,也就是說mysql在對查詢結果排序時使用了臨時表,常見於order by 和 group by。
c:Using index:表示相應的select操作中使用了覆蓋索引(Covering Index),避免了訪問表的資料行,效率高(不要使用select *);如果同時出現Using where,表明索引被用來執行索引鍵值的查詢;如果沒用同時出現Using where,表明索引用來讀取資料而非執行查詢動作。
d:Using join buffer:使用了連結快取。
e:eq_ref:唯一性索引掃描,表中只有一條記錄與之匹配。
f:Impossible WHERE:where子句的值總是false,不能用來獲取任何元祖。
g:select tables optimized away:在沒有group by子句的情況下,基於索引優化MIN/MAX操作或者對於MyISAM儲存引擎優化COUNT(*)操作,不必等到執行階段在進行計算,查詢執行計劃生成的階段即可完成優化。
H:distinct:優化distinct操作,在找到第一個匹配的元祖後即停止找同樣值得動作。
4.3 常見誤區
1、count(1)和count(primary_key)優於count(*)
很多人為了統計記錄條數,就使用 count(1) 和count(primary_key) 而不是 count(*) ,他們認為這樣效能更好,其實這是一個誤區。對於有些場景,這樣做可能效能會更差,應為資料庫對 count(*) 計數操作做了一些特別的優化。
2、count(column)和count(*)是一樣的
這個誤區甚至在很多的資深工程師或者是 DBA 中都普遍存在,很多人都會認為這是理所當然的。實際上,count(column) 和 count(*) 是一個完全不一樣的操作,所代表的意義也完全不一樣。
count(column) 是表示結果集中有多少個column欄位不為空的記錄;
count(*) 是表示整個結果集有多少條記錄。
3、select a,bfrom …比 selecta,b,c from …可以讓資料庫訪問更少的資料量
這個誤區主要存在於大量的開發人員中,主要原因是對資料庫的儲存原理不是太瞭解。
實際上,大多數關係型資料庫都是按照行(row)的方式儲存,而資料存取操作都是以一個固定大小的IO單元(被稱作 block 或者 page)為單位,一般為4KB,8KB…大多數時候,每個IO單元中儲存了多行,每行都是儲存了該行的所有欄位(lob等特殊型別欄位除外)。
所以,我們是取一個欄位還是多個欄位,實際上資料庫在表中需要訪問的資料量其實是一樣的。
當然,也有例外情況,那就是我們的這個查詢在索引中就可以完成,也就是說當只取 a,b兩個欄位的時候,不需要回表,而c這個欄位不在使用的索引中,需要回表取得其資料。在這樣的情況下,二者的IO量會有較大差異。
4、order by一定需要排序操作
我們知道索引資料實際上是有序的,如果我們的需要的資料和某個索引的順序一致,而且我們的查詢又通過這個索引來執行,那麼資料庫一般會省略排序操作,而直接將資料返回,因為資料庫知道資料已經滿足我們的排序需求了。
實際上,利用索引來優化有排序需求的 SQL,是一個非常重要的優化手段
5、執行計劃中有 filesort 就會進行磁碟檔案排序
有這個誤區其實並不能怪我們,而是因為 MySQL 開發者在用詞方面的問題。filesort是我們在使用 explain 命令檢視一條 SQL 的執行計劃的時候可能會看到在“Extra”一列顯示的資訊。
實際上,只要一條 SQL 語句需要進行排序操作,都會顯示“Using filesort”,這並不表示就會有檔案排序操作。
1、儘量少 join
MySQL 的優勢在於簡單,但這在某些方面其實也是其劣勢。MySQL優化器效率高,但是由於其統計資訊的量有限,優化器工作過程出現偏差的可能性也就更多。對於複雜的多表 Join,一方面由於其優化器受限,再者在Join這方面所下的功夫還不夠,所以效能表現離Oracle等關係型資料庫前輩還是有一定距離。但如果是簡單的單表查詢,這一差距就會極小甚至在有些場景下要優於這些資料庫前輩。
2、儘量少排序
排序操作會消耗較多的 CPU 資源,所以減少排序可以在快取命中率高等 IO 能力足夠的場景下會較大影響 SQL的響應時間。
對於MySQL來說,減少排序有多種辦法,比如:
o 上面誤區中提到的通過利用索引來排序的方式進行優化
o 減少參與排序的記錄條數
o 非必要不對資料進行排序
o 避免使用耗費資源的操作,帶有DISTINCT,UNION,MINUS,INTERSECT,ORDERBY的SQL語句會啟動SQL引擎 執行,耗費資源的排序(SORT)功能. DISTINCT需要一次排序操作, 而其他的至少需要執行兩次排序
o …
3、儘量避免 select *
很多人看到這一點後覺得比較難理解,上面不是在誤區中剛剛說 select 子句中欄位的多少並不會影響到讀取的資料嗎?
是的,大多數時候並不會影響到 IO 量,但是當我們還存在order by 操作的時候,select 子句中的欄位多少會在很大程度上影響到我們的排序效率。
此外,上面誤區中不是也說了,只是大多數時候是不會影響到IO量,當我們的查詢結果僅僅只需要在索引中就能找到的時候,還是會極大減少IO量的。
4、儘量用join代替子查詢
雖然Join效能並不佳,但是和MySQL的子查詢比起來還是有非常大的效能優勢。MySQL的子查詢執行計劃一直存在較大的問題,雖然這個問題已經存在多年,但是到目前已經發布的所有穩定版本中都普遍存在,一直沒有太大改善。雖然官方也在很早就承認這一問題,並且承諾儘快解決,但是至少到目前為止我們還沒有看到哪一個版本較好的解決了這一問題。
5、儘量少or
當 where 子句中存在多個條件以“或”並存的時候,MySQL 的優化器並沒有很好的解決其執行計劃優化問題,再加上 MySQL 特有的 SQL 與 Storage 分層架構方式,造成了其效能比較低下,很多時候使用 union all 或者是union(必要的時候)的方式來代替“or”會得到更好的效果。
6、儘量用 union all 代替 union
union 和 union all 的差異主要是前者需要將兩個(或者多個)結果集合並後再進行唯一性過濾操作,這就會涉及到排序,增加大量的 CPU 運算,加大資源消耗及延遲。所以當我們可以確認不可能出現重複結果集或者不在乎重複結果集的時候,儘量使用 union all 而不是 union。
7、儘量早過濾
這一優化策略其實最常見於索引的優化設計中(將過濾性更好的欄位放得更靠前)。在 SQL 編寫中同樣可以使用這一原則來優化一些 Join 的 SQL。比如我們在多個表進行分頁資料查詢的時候,我們最好是能夠在一個表上先過濾好資料分好頁,然後再用分好頁的結果集與另外的表 Join,這樣可以儘可能多的減少不必要的 IO 操作,大大節省 IO 操作所消耗的時間。
8、避免型別轉換
這裡所說的“型別轉換”是指 where 子句中出現 column 欄位的型別和傳入的引數型別不一致的時候發生的型別轉換:
o 人為在column_name 上通過轉換函式進行轉換
直接導致 MySQL(實際上其他資料庫也會有同樣的問題)無法使用索引,如果非要轉換,應該在傳入的引數上進行轉換
o 由資料庫自己進行轉換
如果我們傳入的資料型別和欄位型別不一致,同時我們又沒有做任何型別轉換處理,MySQL 可能會自己對我們的資料進行型別轉換操作,也可能不進行處理而交由儲存引擎去處理,這樣一來,就會出現索引無法使用的情況而造成執行計劃問題。
SELECT emp.ename, emp.job FROM emp WHERE emp.empno = 7369;
不要使用:SELECT emp.ename, emp.job FROM emp WHEREemp.empno = '7369'
9、能用DISTINCT的就不用GROUP BY
group by 操作特別慢,比如:
SELECT OrderID FROM DetailsWHERE UnitPrice > 10 GROUP BY OrderID
可改為:
SELECT DISTINCT OrderID FROMDetails WHERE UnitPrice > 10
10、儘量不要用SELECT INTO語句
SELECT INOT 語句會導致表鎖定,阻止其他使用者訪問該表
11、優先優化高併發的SQL,而不是執行頻率低某些“大”SQL
對於破壞性來說,高併發的 SQL 總是會比低頻率的來得大,因為高併發的SQL一旦出現問題,甚至不會給我們任何喘息的機會就會將系統壓跨。而對於一些雖然需要消耗大量 IO 而且響應很慢的 SQL,由於頻率低,即使遇到,最多就是讓整個系統響應慢一點,但至少可能撐一會兒,讓我們有緩衝的機會。
12、從全域性出發優化,而不是片面調整
SQL 優化不能是單獨針對某一個進行,而應充分考慮系統中所有的 SQL,尤其是在通過調整索引優化 SQL的執行計劃的時候,千萬不能顧此失彼,因小失大。
13、儘可能對每一條執行在資料庫中的SQL進行explain
優化 SQL,需要做到心中有數,知道 SQL的執行計劃才能判斷是否有優化餘地,才能判斷是否存在執行計劃問題。在對資料庫中執行的 SQL 進行了一段時間的優化之後,很明顯的問題 SQL 可能已經很少了,大多都需要去發掘,這時候就需要進行大量的 explain 操作收集執行計劃,並判斷是否需要進行優化。
14、其他優化方式
(1)適當使用檢視加速查詢:把表的一個子集進行排序並建立檢視,有時能加速查詢(特別是要被多次執行的查詢)。它有助於避免多重排序操作,而且在其他方面還能簡化優化器的工作。檢視中的行要比主表中的行少,而且物理順序就是所要求的順序,減少了磁碟I/O,所以查詢工作量可以得到大幅減少。
(2)演算法優化:儘量避免使用遊標,因為遊標的效率較差,如果遊標操作的資料超過1萬行,那麼就應該考慮改寫。.使用基於遊標的方法或臨時表方法之前,應先尋找基於集的解決方案來解決問題,基於集的方法通常更有效。與臨時表一樣,遊標並不是不可使用。對小型資料集使用 FAST_FORWARD 遊標通常要優於其他逐行處理方法,尤其是在必須引用幾個表才能獲得所需的資料時。在結果集中包括“合計”的例程通常要比使用遊標執行的速度快。如果開發時間允許,基於遊標的方法和基於集的方法都可以嘗試一下,看哪一種方法的效果更好。
遊標提供