1. 程式人生 > 實用技巧 >高效能Mysql閱讀筆記

高效能Mysql閱讀筆記

目錄

一、Mysql架構和歷史

二、Mysql基準測試

三、伺服器效能剖析

  1. 使用new relic工具可以進行sql效能剖析
  2. 慢查詢首先要確認是單條查詢的問題還是伺服器的問題。
  3. 效能的優化應該是基於高質量全方位的響應時間測量

四、Schema和資料型別優化

  1. 更小的通常更好,他們佔用更小的記憶體、磁碟等
  2. 簡單就好 例如整型比字串操作代價就更低
  3. 儘量避免null 查詢中包含null的列,對mysql來說更難優化,null的列使得索引和索引統計和值比較都更復雜。如果計劃建索引,就應該避免設計為可為null的列
  4. mysql可以為整型指定寬度。例如int(11),但是對於應用來說是沒有意義的,他不限制值的合法範圍,只是規定了mysql的互動顯示工具顯示字元的個數而已,對於儲存來說,int(1)和int(20)是相同的。
  5. 只有對小數進行精確計算時才使用decimal,但是這樣需要額外的開銷。如果資料量比較大的時候,可以考慮bigint代替decimal,將需要儲存的值根據小數位乘以相應的倍數即可。
  6. varchar varchar型別用於儲存可變長字串,他比定長的更節省空間。
  7. char型別是定長的,適合儲存很短的或者所有值都接近同一個長度的值,對於經常變更的資料,char比varchar更好,不易產生碎片。另外對於非常短的列,char也比varchar好,比如性別。char(1)需要一個位元組,但是varchar(1)卻需要兩個,因為還有一個記錄長度的額外位元組
  8. datetime和timestamp的區別。除了特殊行為外我們應該使用timestamp,因為它空間效率更高。
  • datetime能表示的值:1001到9999年,精度為秒,封裝到YYYYMMDDHHMMSS的整數中,與時區無關。
  • timestamp型別儲存了從1970年1月1日以來的秒數,它和UNIX時間戳相同。timestamp只使用4個位元組儲存,只能表示1970年到2038年。
  1. 應該使用無符號整數儲存ip,使用函式可以轉換。
SELECT INET_ATON('192.168.1.1')  
SELECT INET_NTOA(3232235777)

五、建立高效能的索引

  1. 索引的優點
  • 減少掃描的資料量
  • 可以幫助伺服器避免排序和臨時表
  • 將隨機I/O變成順序IO
  1. 查詢中應使用獨立的列,否則mysql不會使用索引。
    例如以下錯誤示例:
select actor_id from actor where actor_id+1=5
  1. 表film_actor在film_id和actor_id上各有一個單列索引。在mysql老版本中,mysql會使用全表查詢
select * from film_actor where film_id =1  or actor_id =1

除非改成

select * from film_actor where film_id =1  union all  actor_id =1 and film_id <> 1

在mysql5.0和更新的版本中,查詢能夠同時使用這兩個單列索引進行掃描,並將結果進行合併

  1. 聚集索引 聚集索引並不是一個單獨的索引型別,而是一種資料儲存實現方式。具體細節依賴於其實現方式,但InnoDB的聚集索引實際上是在同一個結構中儲存了索引和資料行。

InnoDb使用UUID作為主鍵值的缺點:

  • 寫入的目標頁可能已經刷到磁碟上並從快取中刪除,或是還沒有加入到快取中,Innodb在插入之前不得不先找到並從磁碟讀取目標頁到快取中,導致了大量的隨機IO。
  • 因為寫入是亂序的,InnoDB不得不頻繁的做頁分裂操作
  • 由於頻繁的頁分裂,頁會變得稀疏並被不規則的填充,導致資料有碎片。
  1. 使用索引掃描來做排序
  • 當索引的列順序和order by子句的順序完全一致,並且所有列的排序方向都一樣時,mysql才能使用索引來對結果做排序。
  1. 冗餘索引和重複索引
  • 如果建立了索引(A、B),又建立了索引(A)就是冗餘索引,因為A是前一個索引的字首索引。
  1. 未使用的索引 建議用工具排查然後刪除
  2. 當建立了(sex、country)列作為索引,如果不需要過濾性別(sex),那麼就用不到索引,但是我們可以使用sex in('男','女')的方法使他使用到索引。
  3. 儘可能將範圍查詢的列放在索引的後面,以便優化器能使用盡可能多的索引列。
  4. 避免多個範圍條件 如下
where sex in('M','F') and last_online > DATE_SUB(NOW(),INTERVAL 7 DAY) and age between 18 and 25    

他有兩個範圍條件,last_online和age,mysql可以使用last_online列索引或者age列索引,但無法同時使用他們。

  1. 優化排序
select * from profiles where sex='m' order by rating limit 10

這個查詢同時使用了order by 和limit,如果沒有索引會很慢。即使有索引,當翻頁到很後面的時候也會很慢。如:

select * from profiles where sex='m' order by rating limit 100000,10

解決方法1:限制使用者能夠翻頁的數量
解決方法2:通過覆蓋索引返回需要的主鍵,再根據主鍵取查詢所需要的資料

select * from profiles inner join (select id from profiles where x.sex='m' order by rating limit 10000,10) as x USING (id);

六、查詢效能優化

  1. 是否向資料庫請求了不需要的資料
  • 查詢不需要的記錄
  • 多表關聯時返回全部列
  • 重複查詢相同的資料
  1. 掃描行數的問題
select actor_id,count(*) from film_actor group by actor_id

這個查詢需要讀取幾千行資料,但是僅返回200行。沒有什麼索引可以讓這樣的查詢減少掃描的行數

  • 使用索引覆蓋掃描,把需要的列都放在索引裡
  • 改變表結構。例如:單獨的彙總表
  • 重寫這個複雜的查詢
  1. 一個複雜查詢還是多個簡單查詢

mysql對於連線和斷開連線都是很輕量的,而且現在網路速度比以前要快很多。在一個通用伺服器上,每秒能執行超過10萬的查詢,千兆網絡卡也可以支援每秒2000次的查詢。所以拆成多個簡單查詢是沒問題的,但是如果一個查詢能夠勝任時還寫成多個獨立查詢是不明智的。
4. 切分查詢

如刪除資料,如果用一個大語句一次性完成,可能會一次性鎖住很多資料,佔用系統資源,阻塞查詢。所以可以拆分為多個小的語句來處理。
5. 分解關聯查詢

分解查詢的優勢:

  • 讓快取的效率更高
  • 將查詢分解後,執行單個查詢可以減少鎖的競爭
  • 在應用層做關聯可以更容易對資料庫進行拆分
  • 查詢本身的效率會提升,這個例子中使用IN()代替關聯查詢,讓mysql按照id順序進行查詢,比隨機關聯更高效
  • 可以減少冗餘資料查詢,在應用層做關聯,意味著某條記錄應用只需要查詢一次,而在資料庫關聯,可能會重複地訪問一部分資料。
  • 更進一步,這樣做相當於在應用中實現了雜湊關聯,而不是使用mysql的巢狀迴圈。
  1. 查詢執行的基礎

查詢執行的路徑

  1. Mysql客戶端/服務端通訊協議

Mysql客戶端和服務端之間的協議是"半雙工"的,這意味著任何一個時刻,要麼是由伺服器向客戶端傳送資料,要麼是由客戶端向服務端傳送資料,這兩個動作不能同時發生。這樣的一個明顯的限制是一旦一端開始發生訊息,另一端要接收完整個訊息才能響應它。
8. 查詢優化器

可以通過Last_query_cost的值得知Mysql計算的當前查詢的成本。

SELECT SQL_NO_CACHE COUNT(*) FROM trade_order;
SHOW STATUS LIKE 'Last_query_cost';


這樣的結果是認為1040個數據頁的隨機查詢才能完成以上的查詢。

有如下原因導致Mysql選擇錯誤的執行計劃,如下所示:

  • 統計資訊不準確
  • 執行計劃中的成本估算不等同於實際執行的成本
  • Mysql的最優可能跟你想的不一樣。你可能希望執行時間最短,但是Mysql只是基於成本模型選擇最優的執行計劃
  • Mysql不考慮其他併發執行的查詢
  • Mysql不是任何時候都基於成本的優化,有時有一些固定規則。例如:存在全文搜尋的MATCH()子句,則存在全文索引的時候就使用全文索引。即使有時候使用其他索引更快
  • Mysql不會考慮不受其控制的操作的成本,例如執行儲存過程或者使用者自定義函式的成本
  • 優化器有時候無法估算所有可能的執行計劃
  1. 查詢優化的型別
  • 重新定義關聯表的順序
  • 將外連線轉化為內連線 並不是所有的OUTER JOIN都必須以外連線的方式執行。有時因為庫表結構外連線等價於內連線,mysql能夠識別到並重寫查詢
  • 使用等價變換規則 如:(5=5 and a>5)等價與a>5
  • 優化COUNT(),MIN(),MAX() 例如要找到某一列的最小值,只需要找到對應B Tree索引最左端的記錄。
  • 預估並轉化為常數表示式
  • 覆蓋索引掃描
  • 子查詢優化
  • 提前終止查詢 如:使用了limit子句的時候
  • 等值傳播
select film.film_id from film inner join film_actor using(film_id) where film.film_id>500

mysql知道這個>500不僅適用於film表還適用於film_actor表,它會自動取優化,沒必要這樣寫,反而更難維護

select film.film_id from film inner join film_actor using(film_id) where film.film_id>500 and film_actor.film_id>500
  • 列表IN()的比較
    在很多資料庫中,IN()完全等同於多個OR條件的子句。但是Mysql中不一樣,Mysql會將IN()列表中的資料先排序,然後通過二分查詢的方式確定列表中的值是否滿足條件。這是O(log n),而OR條件是O(n)
  1. Mysql如何執行關聯查詢

Mysql對任何關聯都執行巢狀迴圈關聯操作,即Mysql先在一個表中取出單條資料,再巢狀迴圈到下一個表尋找匹配的行。依次下去,直到找出所有的行為止
11. 關聯查詢優化器

可以優化關聯表的順序
12. 排序優化

排序是一個成本很高的操作,儘可能避免排序或儘可能避免大量資料的排序。如果不能使用索引排序,就需要mysql自己排序,資料量小則在記憶體中進行,大的話則需要使用磁碟。先讀取查詢所需要的所有列,然後根據給定列進行排序,最後返回排序結果。

關聯查詢的時候,如果排序的所有列都來自一個表,那麼在處理這個表時會先進行排序。Explain 的Extra欄位會有"Using filesort" 除此之外的情況,Mysql會將關聯查詢後的結果放在臨時表裡,然後進行排序。
13. 返回結果給客戶端

查詢執行的最後一個階段是將結果返回給客戶端。如果查詢可以被快取,那麼Mysql在這個階段放結果到快取中。Mysql將結果集返回客戶端是一個增量、逐步返回的過程。這樣優點是服務端無須儲存太多的結果,另外客戶端可以在第一時間收到返回結果。

結果中的每一行都會以滿足Mysql通訊協議的封包傳送,在通過TCP協議進行傳輸。
14. 關聯子查詢優化

select * from film where film_id in(select film_id from film_actor where actor_id=1)

Mysql有時不會先查詢in裡面的結果,會將外層表壓到子查詢中。所以我們可以這樣改寫:

select film.* from film inner join film_actor USING(film_id) where actor_id=1

另一種優化方法:

select * from film where exists(select * from film_actor where actor_id=1 and film_actor.film_id=film.film_id)

不同的案例會有不同,有時用子查詢也很快。我們應該通過測試來確定。

  1. UNION的限制

  2. 等值傳遞

有些時候等值傳遞會帶來一些意想不到的額外損耗。例如有一個非常大的IN()列表,而mysql優化器發現存在Where、ON或者USING的子句,將這個列表的值跟另一個表的某個列相關聯。
17. 並行執行 Mysql無法利用多核特性進行並行執行。
18. 在同一個表上查詢和更新

Mysql不允許在同一個表進行查詢和更新

update tb1 as outer_tb1 set cnt=(select count(*) from tb1 as inner_tb1 where inner_tb1.type=outer_tb1.type);

可以通過生成表的形式來繞過上面的限制。

update tb1 inner join (select type,count(*) as cnt from tb1 group by type) as der using(type) set tb1.cnt=der.cnt
  1. 優化COUNT()函式

count()函式是一個特殊函式,可以統計列的數量也可以統計行的數量。我們在使用count()的時候,這種情況萬用字元不會擴充套件成所有的列,實際上他會忽略所有的列直接統計所有行數。所以,統計行數最好使用count(*),意義清晰,效能也會很好。

如果在count函式中指定了列值,則統計的就是這個列有值的結果數。

19.1 簡單的優化

例1:

有時候可以使用MyISAM在count(*)全表非常快的特性,來加快一些特定條件的count()查詢。

比如要查詢id>5的城市,我們可以條件反轉。以減少掃描的條數

select count(*) from city >5;
select (select count(*) from city) -count(*) from city where id<=5;

例2:在同一個查詢中統計同一列不同值的數量

查詢不同顏色的商品數量,有以下兩種寫法:

select sum(if(color='blue',1,0)) as blue,sum(if(color='red',1,0)) as red from items;
select count(color ='blue' or null) as blue,count(color='red' or null) as red from items;

19.2 使用近似值

有的業務不需要準確的值的時候,可以使用近似值,explain 出來的優化器估算行數就是不錯的近似值,執行explain不會真正的去執行查詢,所以成本很低。

19.3 使用快取或彙總表

  1. 優化關聯查詢

特別注意:

  • 確保ON或者USING子句中的列上有索引。當表A和B用列c關聯,如果優化器的關聯順序是B、A,那麼就不需要在B表的對應列上建立索引。除非其他理由,則只需要在關聯順序的第二個表的對應列上建立索引。
  • 確保group by和order by中的表示式只涉及一個表中的列,這樣Mysql才有可能用索引優化這個過程
  • 升級Mysql的時候需要注意:關聯語法、運算子優先順序等可能會發生變化的地方。
  1. 優化Group by和Distinct

在很多場景下,Mysql都使用同樣的辦法優化這兩種查詢。Mysql優化器會在內部處理的時候相互轉化這兩類查詢。他們都可以使用索引來優化。

在Mysql中,當無法使用索引的時候,Group by使用兩種策略來完成:使用臨時表或者檔案排序來做分組。

如果需要對關聯查詢分組,那麼通常採用查詢表的標識列分組效率會比較高。

反例:

select actor.first_name,actor.last_name,count(*) from film_actor inner join actor using(actor_id) group by actor.first_name,actor.last_name

正例:

select first_name,last_name,count(*) from film_actor inner join actor using(actor_id) group by actor.actor_id

這個查詢利用了id和演員姓名的直接相關的特點,因此改寫後結果不受影響,但顯然不是所有的關聯語句分組查詢都可以改寫成非分組列的形式。

在使用group by的時候如果後面不跟order by,他會根據分組列的欄位進行排序。如果不需要排序可以加個order by null.

  1. 優化limit分頁

在系統需要分頁的時候,我們通常用limit實現,同時加上合適的order by語句,如果有對應的索引,效率通常還不錯。

但是如果偏移量非常大的時候,例如limit 10000,20。這時mysql需要查詢10020條記錄,然後返回最後20條,這樣的代價非常高。

解決方法:

  1. 限制分頁數量
  2. 優化大偏移量的查詢

考慮改寫下面的查詢:

select film_id,description from film order by title limit 50,5;

先獲取需要訪問的記錄再根據關聯列去查詢

select film.film_id,film.description from film inner join(select film_id from film order by title limit 50,5) as lim using(film_id);
  1. 有時候可以將limit查詢轉換為已知位置的查詢
select film_id,description from film where position between 50 and 54 order by position;
  1. 書籤記錄上次讀取位置
select * from rental order by rental_id desc limit 20;

假設上面返回的是主鍵為16049到16030的租借記錄,那麼下一頁查詢就可以從16030開始。

select * from rental where rental_id <16030 order by rental_id desc limit 20;
  1. 優化UNION查詢

Mysql總是通過建立並填充臨時表的方式執行UNION查詢。

  1. 經常需要手工的把where、limit、order by等子句下推到union的各個子查詢去。
  2. 除非確實需要伺服器消除重複的行,否則一定要使用union all,如果不加All的話,Mysql會給臨時表加Distinct選項,這會對整個臨時表做唯一性檢查,這樣做的代價非常高。
  3. 編寫偷懶的UNION查詢

下面的查詢會在兩個地方查詢同一個使用者,一個主使用者表,一個長時間不活躍的使用者表。

select id from user where id=123
union all 
select id from user_archived where id=123;

上面的查詢可以正常工作,但是即使在user表裡找到了資料,他還是會去掃描user_archived。

select GREATEST(@found := -1,id) as id,'users' as which_tb1 from users where id=1 union all select id ,'users_archived' from users_archived where id=1 and @found is null union all select 1 ,'reset' from dual where (@found := null) is not null;

在查詢的末尾將變數重置為null,保證遍歷時不干擾後面的結果。

  1. 綜合案例學習

通常我們要做的不是查詢優化,不是庫表結構優化,也不是索引優化,實際情況中要面對的是所有這些都攪在一起的情況。

24.1 一個表包含多種型別的記錄:未處理記錄、已處理記錄、正在處理記錄等。隨著表越來越大和索引深度加深,找到未處理記錄就會變慢,可以將已處理記錄歸檔或者移到歷史表。

24.2 使用資料庫計算兩地的經緯度?這類查詢耗費cpu資源,且無法使用索引,建議不用資料庫來做。

七、Mysql高階特性

  1. 外來鍵操作

如果想確保兩個表始終有一致的資料,那麼使用外來鍵比在應用程式中檢查一致性效能高得多。如果只是使用外來鍵做約束,通常在應用程式裡實現約束會比較好。外來鍵會帶來很大的額外消耗。
2. 分散式(XA)事務

Mysql在5.0之後開始支援XA事務了。實際上,在Mysql中有兩種XA事務了。

  • 內部XA事務

Mysql中各個儲存引擎是完全獨立的,彼此不知道對方的存在。所以一個跨儲存引擎的事務就需要一個外部的協調者。如果不使用XA協議,就會破壞事務的特性。即使是同一個儲存引擎,他在提交的時候還需要將提交資訊寫入二進位制日誌,這也是一個分散式事務。
-外部XA事務
Mysql能夠作為參與者完成一個外部的分散式事務。但他對XA協議支援並不完整。而且很耗費效能,只有在Mysql效能不是瓶頸的時候可以嘗試使用。
3. 查詢快取

很多資料庫產品能夠快取查詢的執行計劃,相同型別的sql就可以跳過sql解析和執行計劃生成階段。

  • 3.1 Mysql如何判斷快取命中

Mysql會根據sql語句和客戶端傳送過來的其他原始資訊去判斷,任何的字元上的不同,註釋、空格都會導致快取不命中。如何查詢之中包含任何不確定的函式,那麼查詢快取之中是不可能找到快取的。

  • 3.2 如何配置和維護快取

query_cache_type 是否開啟查詢快取
query_cache_size 查詢快取使用的總位元組空間。
query_cache_min_res_unit 查詢快取中分配記憶體塊時的最小單位
query_cache_limit mysql能夠快取的最大查詢結果

八、優化伺服器設定

  1. 檢視mysql安裝位置

which mysqld

  1. 檢視系統變數

show global variables

  1. 不建議使用調優指令碼。

九、作業系統和硬體優化

  1. 什麼限制了Mysql效能?

CPU、IO、網路頻寬

後面內容略