注:這篇文章是以 MySQL 爲背景,不少內容同時適用於其餘關係型數據庫,須要有一些索引知識爲基礎 mysql
- 優化目標
- 減小 IO 次數
IO永遠是數據庫最容易瓶頸的地方,這是由數據庫的職責所決定的,大部分數據庫操做中超過90%的時間都是 IO 操做所佔用的,減小 IO 次數是 SQL 優化中須要第一優先考慮,固然,也是收效最明顯的優化手段。
- 下降 CPU 計算
除了 IO 瓶頸以外,SQL優化中須要考慮的就是 CPU 運算量的優化了。order by, group by,distinct … 都是消耗 CPU 的大戶(這些操做基本上都是 CPU 處理內存中的數據比較運算)。當咱們的 IO 優化作到必定階段以後,下降 CPU 計算也就成爲了咱們 SQL 優化的重要目標
- 優化方法
- 改變 SQL 執行計劃
明確了優化目標以後,咱們須要肯定達到咱們目標的方法。對於 SQL 語句來講,達到上述2個目標的方法其實只有一個,那就是改變 SQL 的執行計劃,讓他儘可能「少走彎路」,儘可能經過各類「捷徑」來找到咱們須要的數據,以達到 「減小 IO 次數」 和 「下降 CPU 計算」 的目標
- 常見誤區
- count(1)和count(primary_key) 優於 count(*)
不少人爲了統計記錄條數,就使用 count(1) 和 count(primary_key) 而不是 count(*) ,他們認爲這樣性能更好,其實這是一個誤區。對於有些場景,這樣作可能性能會更差,應爲數據庫對 count(*) 計數操做作了一些特別的優化。
- count(column) 和 count(*) 是同樣的
這個誤區甚至在不少的資深工程師或者是 DBA 中都廣泛存在,不少人都會認爲這是理所固然的。實際上,count(column) 和 count(*) 是一個徹底不同的操做,所表明的意義也徹底不同。
count(column) 是表示結果集中有多少個column字段不爲空的記錄
count(*) 是表示整個結果集有多少條記錄
- select a,b from … 比 select a,b,c from … 可讓數據庫訪問更少的數據量
這個誤區主要存在於大量的開發人員中,主要緣由是對數據庫的存儲原理不是太瞭解。
實際上,大多數關係型數據庫都是按照行(row)的方式存儲,而數據存取操做都是以一個固定大小的IO單元(被稱做 block 或者 page)爲單位,通常爲4KB,8KB… 大多數時候,每一個IO單元中存儲了多行,每行都是存儲了該行的全部字段(lob等特殊類型字段除外)。
因此,咱們是取一個字段仍是多個字段,實際上數據庫在表中須要訪問的數據量實際上是同樣的。
固然,也有例外狀況,那就是咱們的這個查詢在索引中就能夠完成,也就是說當只取 a,b兩個字段的時候,不須要回表,而c這個字段不在使用的索引中,須要回表取得其數據。在這樣的狀況下,兩者的IO量會有較大差別。
- order by 必定須要排序操做
咱們知道索引數據其實是有序的,若是咱們的須要的數據和某個索引的順序一致,並且咱們的查詢又經過這個索引來執行,那麼數據庫通常會省略排序操做,而直接將數據返回,由於數據庫知道數據已經知足咱們的排序需求了。
實際上,利用索引來優化有排序需求的 SQL,是一個很是重要的優化手段
延伸閱讀:MySQL ORDER BY 的實現分析 ,MySQL 中 GROUP BY 基本實現原理 以及 MySQL DISTINCT 的基本實現原理 這3篇文章中有更爲深刻的分析,尤爲是第一篇
- 執行計劃中有 filesort 就會進行磁盤文件排序
有這個誤區其實並不能怪咱們,而是由於 MySQL 開發者在用詞方面的問題。filesort 是咱們在使用 explain 命令查看一條 SQL 的執行計劃的時候可能會看到在 「Extra」 一列顯示的信息。
實際上,只要一條 SQL 語句須要進行排序操做,都會顯示「Using filesort」,這並不表示就會有文件排序操做。
延伸閱讀:理解 MySQL Explain 命令輸出中的filesort,我在這裏有更爲詳細的介紹
- 基本原則
- 儘可能少 join
MySQL 的優點在於簡單,但這在某些方面其實也是其劣勢。MySQL 優化器效率高,可是因爲其統計信息的量有限,優化器工做過程出現誤差的可能性也就更多。對於複雜的多表 Join,一方面因爲其優化器受限,再者在 Join 這方面所下的功夫還不夠,因此性能表現離 Oracle 等關係型數據庫前輩仍是有必定距離。但若是是簡單的單表查詢,這一差距就會極小甚至在有些場景下要優於這些數據庫前輩。
- 儘可能少排序
排序操做會消耗較多的 CPU 資源,因此減小排序能夠在緩存命中率高等 IO 能力足夠的場景下會較大影響 SQL 的響應時間。
對於MySQL來講,減小排序有多種辦法,好比:
- 上面誤區中提到的經過利用索引來排序的方式進行優化
- 減小參與排序的記錄條數
- 非必要不對數據進行排序
- …
- 儘可能避免 select *
不少人看到這一點後以爲比較難理解,上面不是在誤區中剛剛說 select 子句中字段的多少並不會影響到讀取的數據嗎?
是的,大多數時候並不會影響到 IO 量,可是當咱們還存在 order by 操做的時候,select 子句中的字段多少會在很大程度上影響到咱們的排序效率,這一點能夠經過我以前一篇介紹 MySQL ORDER BY 的實現分析 的文章中有較爲詳細的介紹。
此外,上面誤區中不是也說了,只是大多數時候是不會影響到 IO 量,當咱們的查詢結果僅僅只須要在索引中就能找到的時候,仍是會極大減小 IO 量的。
- 儘可能用 join 代替子查詢
雖然 Join 性能並不佳,可是和 MySQL 的子查詢比起來仍是有很是大的性能優點。MySQL 的子查詢執行計劃一直存在較大的問題,雖然這個問題已經存在多年,可是到目前已經發布的全部穩定版本中都廣泛存在,一直沒有太大改善。雖然官方也在很早就認可這一問題,而且承諾儘快解決,可是至少到目前爲止咱們尚未看到哪個版本較好的解決了這一問題。
- 儘可能少 or
當 where 子句中存在多個條件以「或」並存的時候,MySQL 的優化器並無很好的解決其執行計劃優化問題,再加上 MySQL 特有的 SQL 與 Storage 分層架構方式,形成了其性能比較低下,不少時候使用 union all 或者是union(必要的時候)的方式來代替「or」會獲得更好的效果。
- 儘可能用 union all 代替 union
union 和 union all 的差別主要是前者須要將兩個(或者多個)結果集合並後再進行惟一性過濾操做,這就會涉及到排序,增長大量的 CPU 運算,加大資源消耗及延遲。因此當咱們能夠確認不可能出現重複結果集或者不在意重複結果集的時候,儘可能使用 union all 而不是 union。
- 儘可能早過濾
這一優化策略其實最多見於索引的優化設計中(將過濾性更好的字段放得更靠前)。
在 SQL 編寫中一樣可使用這一原則來優化一些 Join 的 SQL。好比咱們在多個表進行分頁數據查詢的時候,咱們最好是可以在一個表上先過濾好數據分好頁,而後再用分好頁的結果集與另外的表 Join,這樣能夠儘量多的減小沒必要要的 IO 操做,大大節省 IO 操做所消耗的時間。
- 避免類型轉換
這裏所說的「類型轉換」是指 where 子句中出現 column 字段的類型和傳入的參數類型不一致的時候發生的類型轉換:
- 人爲在column_name 上經過轉換函數進行轉換
直接致使 MySQL(實際上其餘數據庫也會有一樣的問題)沒法使用索引,若是非要轉換,應該在傳入的參數上進行轉換
- 由數據庫本身進行轉換
若是咱們傳入的數據類型和字段類型不一致,同時咱們又沒有作任何類型轉換處理,MySQL 可能會本身對咱們的數據進行類型轉換操做,也可能不進行處理而交由存儲引擎去處理,這樣一來,就會出現索引沒法使用的狀況而形成執行計劃問題。
- 優先優化高併發的 SQL,而不是執行頻率低某些「大」SQL
對於破壞性來講,高併發的 SQL 老是會比低頻率的來得大,由於高併發的 SQL 一旦出現問題,甚至不會給咱們任何喘息的機會就會將系統壓跨。而對於一些雖然須要消耗大量 IO 並且響應很慢的 SQL,因爲頻率低,即便遇到,最多就是讓整個系統響應慢一點,但至少可能撐一下子,讓咱們有緩衝的機會。
- 從全局出發優化,而不是片面調整
SQL 優化不能是單獨針對某一個進行,而應充分考慮系統中全部的 SQL,尤爲是在經過調整索引優化 SQL 的執行計劃的時候,千萬不能顧此失彼,因小失大。
- 儘量對每一條運行在數據庫中的SQL進行 explain 優化 SQL,須要作到心中有數,知道 SQL 的執行計劃才能判斷是否有優化餘地,才能判斷是否存在執行計劃問題。在對數據庫中運行的 SQL 進行了一段時間的優化以後,很明顯的問題 SQL 可能已經不多了,大多都須要去發掘,這時候就須要進行大量的 explain 操做收集執行計劃,並判斷是否須要進行優化。