1. 程式人生 > >MySQL 效能優化實戰

MySQL 效能優化實戰

鑑於公司專案及業務發展,技術人員從幾人到如今幾十人,後端團隊技術人員日益劇增,可是隨著專案人員的增長,大多研發人員及相關人員經常需要到測試環境使用 MySQL 資料庫,比如移動端、測試、產品,然而他們需要普及 MySQL 知識點及效能優化的知識,其實效能優化的目標是針對後端研發人員及一些資深的前端人員,可能會從如下大的知識點講解。

一、安裝說明

首先學習資料庫,當然是安裝軟體。千里之行,始於足下,如果連安裝都不會,如何進行後續的入門學習。可是對於安裝也有不同方式,比如 RPM 和原始碼編譯安裝呢。

1.1 RPM 安裝包和 Tar 安裝包的區別?

RPM 直接安裝,Tar 屬於原始碼安裝,可以設定更多的引數,可以和系統進行更緊密的優化。舉個例子,RPM 是 180/96、185/100 之類的標準版型,Tar 原始碼安裝類似於私人定製,量體裁衣的。其實個人覺得 RPM 安裝適合小白入門,簡單瞭解下 MySQL,不用做過多安裝上的瞭解,然而原始碼編譯安裝適合比如經常跟 MySQL 打交道的工程師,比如 web 研發人員、c++ 工程師等,總不能只是知道如何簡單的使用和 curd,其實對於技術人員的縱向知識體系打造是不好的,其實原始碼編譯安裝,還可能自己嘗試些具體引數的配置。關於 MySQL 官方下載地址:

1.2 安裝後需要配置哪些內容?

不管 RPM 還是原始碼編譯安裝後,有些東西必須要設定:

  • root 初始密碼問題:必須設定密碼,如果是自己玩還好,如果是線上系統必須設定密碼,要不然就是裸跑系統。
  • 預設安裝後會在指定檔案中生成,如果忘記或找不到可以對 root 密碼進行強制修改:
mysqld_safe –skip-grant-tables 2& 
  • 使用者遠端訪問問題:從安全性角度預設不允許遠端訪問,可以進行配置,允許遠端訪問,但是要注意安全性規範。
grant all privileges on . to ‘root’@’%’ identified by’Password’; 
flush privileges;
  • UTF-8 編碼問題: 關於字符集的問題,可能有些技術人員初次學習資料庫時或者初次從事研發工作時,偶爾會碰到,為什麼前端資訊是正常錄入,寫入資料庫時,變成了亂碼?檢視資料庫的編碼方式命令為:
show variables like 'character%';

引數說明:
character_set_client為客戶端編碼方式;
character_set_connection為建立連線使用的編碼;
character_set_database資料庫的編碼;
character_set_results結果集的編碼;
character_set_server資料庫務器的編碼;

1.3 my.cnf 檔案初始需要配置哪些內容?

  • 資料檔案位置: 確保資料不會把磁碟空間寫滿,如果有 ssd,可以充分利用 IO 優勢。
  • 日誌檔案位置: 快速定位錯誤日誌的位置,根據日誌排除錯誤的能力,是程式設計師的第一生產力。
  • 其他基礎引數
  • Myisam系列引數(表級鎖):事務--鎖--?
myisam_sort_buffer_size = 128M   
myisam_max_sort_file_size = 10G   
myisam_max_extra_sort_file_size = 10G
myisam_repair_threads = 1   
myisam_recover   
  • InnoDB系統引數(行級鎖?):
innodb_additional_mem_pool_size = 16M   
innodb_buffer_pool_size = 2048M   
innodb_data_file_path = ibdata1:1024M:autoextend   
innodb_file_io_threads = 4   
innodb_thread_concurrency = 8   
innodb_flush_log_at_trx_commit = 2  
innodb_log_buffer_size = 16M  
innodb_log_file_size = 128M   
innodb_log_files_in_group = 3   
innodb_max_dirty_pages_pct = 90   
innodb_lock_wait_timeout = 120   
innodb_file_per_table = 0 
  • 其他引數
[client]
port = 3306
socket = /data/3306/mysql.sock
[mysqld]
user = mysql
port = 3306
socket = /data/3306/mysql.sock
basedir = /usr/local/mysql
datadir = /data/3306/data
open_files_limit = 10240
[mysqldump]
max_allowed_packet = 32M
[mysqld_safe]
log-error=/data/mysql_err.log
pid-file=/data/mysqld.pid
  • 常見的 my.cnf 檔案型別
my-small.cnf 
my-medium.cnf 
my-large.cnf
my-huge.cnf

1.4 MySQL 的版本選擇

  • 5.6 更成熟、更穩定,缺乏一些5.7開始支援的新特性。
  • 5.7 支援更多新特性,支援 MGR、JSON 欄位格式等。從 5.7 開始,MySQL 對 SQL 語法的檢查變為嚴格,之前一些存在潛在問題和錯誤的 SQL 會無法執行。
  • 8.0 擁有很多新的功能,包括 SQL 方面、JSON 方面以及 DevOps 方面,據說效能提升長達 15 倍。

1.5 MySQL 之外的選擇

  • Oracle: 免費下載,但是商業用途收費(按 CPU 收費),功能和穩定性更佳,免費和收費的培訓資料很多。由於 Oracle 的系統架構較老,程式碼難以進行整體顛覆性的修改,所以只能在每個版本中進行較小的改進。

  • PostgreSQL:(國內有依託阿里德哥推廣的強大知識分享社群) 和 MySQL 一樣,社群版程式碼開源,SQL 風格和 Oracle 更加接近,功能和效能也比 MySQL 更加強大,支援 MPP(Greenplum)、LLVM、GIS、列式儲存、圖計算等特性。普及率相對較低,文件和資料比 Oracle、MySQL 要少。

二、MySQL 引擎選擇和表設計上的優化

大多 Web 工程師,使用更多的引擎選擇和表設計,並且隨著業務量發展,會進行不同型別或程度上的優化。5.7 之後預設儲存引擎為 InnoDB,主要 InnoDB 能應用絕大數場景。

2.1 Myisam 和 InnoDB 的區別?

其實關於這兩個最常用的儲存引擎,無非就是看場景,其實沒有絕對的好與壞,不要教條主義,適合自己業務的就是最好的。

  • Myisam:表級鎖,不支援事務,讀效能更好,讀寫分離中做讀(從)節點。老版本 MySQL 的預設儲存引擎。表的儲存分為三個檔案:frm表格式,MYD/MYData 資料檔案,myi 索引檔案。

  • InnoDB:有條件的行級鎖,支援事務,更適合作為讀寫分離中的寫(主)節點。新版本 MySQL(5.7開始)的預設儲存引擎。

2.2 其他的引擎介紹

  • XtraDB:XtraDB 是一個 MySQL 的儲存引擎,由 Percona 公司對於 InnoDB 儲存引擎進行改進加強後的產品,其設計的主要目的是用以替代現在的 InnoDB。XtraDB 相容 InnoDB 的所有特性,並且在 IO 效能,鎖效能,記憶體管理等多個方面進行了增強。
  • TokuDB:TokuDB 是一個高效能、支援事務處理的 MySQL 和 MariaDB 的儲存引擎。TokuDB 的主要特點則是對高寫壓力的支援。

三、MySQL SQL 語句的優化

關於 SQL 優化,對於基本大多的 Web 研發人員,注意一個核心點:減少 IO 請求,網路傳輸。

1.應儘量避免在 where 子句中使用 != 或 <> 操作符,否則將引擎放棄使用索引而進行全表掃描

2.應儘量避免在 where 子句中對欄位進行 null 值判斷,否則將導致引擎放棄使用索引而進行全表掃描。如:

select id from t where ais null

可以在 a 上設定預設值 0,確保表中 a 列沒有 null 值,然後這樣查詢:

select id from t where a=0

3.儘量避免在 where 子句中使用 or 來連線條件,否則將導致引擎放棄使用索引而進行全表掃描,如:

select id from t where a=10 or a=20

可以這樣查詢:

select id from t where a=10
union all
select id from t where a=20

4.下面的查詢也將導致全表掃描:

select id from t where name like‘%c%’

下面走索引

select id from t where name like‘c%’

若要提高效率,可以考慮全文檢索。

5.in 和 not in 也要慎用,否則會導致全表掃描,如:

select id from t where a in(1,2,3)

對於連續的數值,能用 between 就不要用 in 了:

select id from t where a between 1 and 3 

如果在 where 子句中使用引數,也會導致全表掃描。因為 SQL 只有在執行時才會解析區域性變數,但優化程式不能將訪問計劃的選擇推遲到執行時;它必須在編譯時進行選擇。然而,如果在編譯時建立訪問計劃,變數的值還是未知的,因而無法作為索引選擇的輸入項。如下面語句將進行全表掃描:

select id from t where [email protected]

可以改為強制查詢使用索引:

select id from t with(index(索引名)) where [email protected]

6.應儘量避免在 where 子句中對欄位進行表示式操作,這將導致引擎放棄使用索引而進行全表掃描。如:

select id from t where a/2=100

應改為:

select id from t where a=100*2

7.應儘量避免在 where 子句中對欄位進行函式操作,這將導致引擎放棄使用索引而進行全表掃描。如:

select id from t where substring(name,1,3)='abc'  name以abc開頭的id
select id from t where datediff(day,createdate,'2005-11-30')= 0  '2005-11-30'生成的id

應改為:

select id from t where name like‘abc%’
select id from t where createdate>='2005-11-30′ and createdate<'2005-12-1′

8.不要在 where 子句中的“=”左邊進行函式、算術運算或其他表示式運算,否則系統將可能無法正確使用索引。

9.在使用索引欄位作為條件時,如果該索引是複合索引,那麼必須使用到該索引中的第一個欄位作為條件時才能保證系統使用該索引,否則該索引將不會被使用,並且應儘可能的讓欄位順序與索引順序相一致。

10.很多時候用 exists 代替 in 是一個好的選擇:

select num from a where num in (select num from b)

用下面的語句替換:

select num from a where exists (select 1 from b where num=a.num)

11.並不是所有索引對查詢都有效,SQL 是根據表中資料來進行查詢優化的,當索引列有大量資料重複時,SQL 查詢可能不會去利用索引,如一表中有欄位 sex,male、female 幾乎各一半,那麼即使在sex上建了索引也對查詢效率起不了作用。

12.索引並不是越多越好,索引固然可以提高相應的 select 的效率,但同時也降低了 insert 及 update 的效率,因為 insert 或 update 時有可能會重建索引,所以怎樣建索引需要慎重考慮,視具體情況而定。一個表的索引數最好不要超過6個,若太多則應考慮一些不常使用到的列上建的索引是否有 必要。

13.應儘可能的避免更新 clustered 索引資料列,因為 clustered 索引資料列的順序就是表記錄的物理儲存順序,一旦該列值改變將導致整個表記錄的順序的調整,會耗費相當大的資源。若應用系統需要頻繁更新 clustered 索引資料列,那麼需要考慮是否應將該索引建為 clustered 索引。

14.儘量使用數字型欄位,若只含數值資訊的欄位儘量不要設計為字元型,這會降低查詢和連線的效能,並會增加儲存開銷。這是因為引擎在處理查詢和連線時會 逐個比較字串中每一個字元,而對於數字型而言只需要比較一次就夠了。

15.儘可能的使用 varchar 代替 char,因為首先變長欄位儲存空間小,可以節省儲存空間,其次對於查詢來說,在一個相對較小的欄位內搜尋效率顯然要高些。

16.任何地方都不要使用 select * from t ,用具體的欄位列表代替 *,不要返回用不到的任何欄位。這裡就是典型的減少網路傳輸,尤其大多數業務中,使用者優惠券列表,如果全是*,如果使用者資料過多,程式在網路傳輸過程中會超時。

17.儘量使用表變數來代替臨時表。如果表變數包含大量資料,請注意索引非常有限(只有主鍵索引)。

18.避免頻繁建立和刪除臨時表,以減少系統表資源的消耗。

19.臨時表並不是不可使用,適當地使用它們可以使某些例程更有效,例如,當需要重複引用大型表或常用表中的某個資料集時。但是,對於一次性事件,最好使 用匯出表。

20.在新建臨時表時,如果一次性插入資料量很大,那麼可以使用 select into 代替 create table,避免造成大量 log,以提高速度;如果資料量不大,為了緩和系統表的資源,應先create table,然後insert。

21.如果使用到了臨時表,在儲存過程的最後務必將所有的臨時表顯式刪除,先 truncate table ,然後 drop table ,這樣可以避免系統表的較長時間鎖定。

22.當只要一行資料時使用 Limit 1。當查詢表已經知道結果只會有一條結果,在這種情況下,加上 Limit 1 可以增加效能。MySQ L資料庫引擎會在找到一條資料後停止搜尋,而不是繼續往後查少下一條符合記錄的資料。

23.如果應用程式有很多 Join 查詢,應該確認兩個表中 Join 的欄位是被建過索引的。這樣,MySQL 內部會啟動優化 Join 的 SQL 語句的機制。這些被用來 Join 的欄位,應該是相同的型別的。例如:如果要把 DECIMAL 欄位和一個 INT 欄位 Join 在一起,MySQL 就無法使用它們的索引。對於那些 STRING 型別,還需要有相同的字符集才行。

24.固定長度的表會更快,如果表中的所有欄位都是 “固定長度” 的,整個表會被認為是 static 或 fixed-length。例如,表中沒有如下型別的欄位: VARCHAR,TEXT,BLOB。只要你包括了其中一個這些欄位,那麼這個表就不是 “固定長度靜態表” 了,這樣,MySQL 引擎會用另一種方法來處理。固定長度的表會提高效能,因為 MySQL 搜尋得會更快一些,因為這些固定的長度是很容易計算下一個資料的偏移量的,所以讀取的自然也會很快。而如果欄位不是定長的,那麼,每一次要找下一條的話,需要程式找到主鍵。並且,固定長度的表也更容易被快取和重建。不過,唯一的副作用是,固定長度的欄位會浪費一些空間,因為定長的欄位無論你用不用,他都是要分配那麼多的空間。

25.儘量避免向客戶端返回大資料量,若資料量過大,應該考慮相應需求是否合理。

26.儘量避免大事務操作,提高系統併發能力。

27.不同資料庫的 SQL 執行順序的差別。

28.MySQL Explain 執行計劃 type 型別區別:

效能從好到差:systemconsteq_refreffulltextref_or_nullunique_subqueryindex_subqueryrangeindex_mergeindexall

除了 all 之外,其他的 type 都可以使用到索引,除了 index_merge 之外,其他的 type 只可以用到一個索引。

  • system:表中只有一行資料或者是空表,且只能用於 myisam 和 memory 表。如果是 InnoDB 引擎表,type 列在這個情況通常都是 all 或者 index
  • const:使用唯一索引或者主鍵,返回記錄一定是 1 行記錄的等值 where 條件時,通常 type 是 const。也叫做唯一索引掃描。
  • eq_ref:出現在要連線多個表的查詢計劃中,驅動表只返回一行資料,且這行資料是第二個表的主鍵或者唯一索引,且必須為 not null,唯一索引和主鍵是多列時,只有所有的列都用作比較時才會出現 eq_ref
  • ref:不像 eq_ref 那樣要求連線順序,也沒有主鍵和唯一索引的要求,只要使用相等條件檢索時就可能出現。常見與輔助索引的等值查詢,或者多列主鍵、唯一索引中,使用第一個列之外的列作為等值查詢也會出現,總之,返回資料不唯一的等值查詢就可能出現。
  • fulltext:全文索引檢索,要注意,全文索引的優先順序很高,若全文索引和普通索引同時存在時,MySQL不管代價,優先選擇使用全文索引。
  • ref_or_null:與 ref 方法類似,只是增加了 null 值的比較。實際用的不多。
  • unique_subquery:用於 where 中的 in 形式子查詢,子查詢返回不重複值唯一值
  • index_subquery:用於 in 形式子查詢使用到了輔助索引或者 in 常數列表,子查詢可能返回重複值,可以使用索引將子查詢去重。
  • range:索引範圍掃描,常見於使用 >,<,is null,between ,in ,like 等運算子的查詢中。
  • index_merge:表示查詢使用了兩個以上的索引,最後取交集或者並集,常見 and、or 的條件使用了不同的索引,官方排序這個在 ref_or_null之後,但是實際上由於要讀取所個索引,效能可能大部分時間都不如 range
  • index:索引全表掃描,把索引從頭到尾掃一遍,常見於使用索引列就可以處理不需要讀取資料檔案的查詢、可以使用索引排序或者分組的查詢。
  • all:這個就是全表掃描資料檔案,然後再在 server 層進行過濾返回符合要求的記錄。

四、MySQL 的缺陷與不足

  1. 不支援 hash join,大表之間不適合做 joi n操作,沒辦法滿足複雜的OLAP要求。
  2. MySQL 不支援函式索引,也不支援並行更新
  3. MySQL 連線的 8 小時問題,相對於使用 Oracle 資料庫,使用MySQL需要注意更多的細節問題。
  4. 對於 SQL 批處理和預編譯,支援程度不如 Oracle 資料庫。
  5. MySQL 優化器還是比較欠缺,不及 Oracle 資料庫。

五、MySQL 的優點

  1. 網際網路領域使用較多,文件資料豐富,使用案例非常多,對潛在的問題比較容提前做出應對方案。
  2. 由於 MySQL 是開源的資料庫,因此很多網際網路公司都根據自己的業務需求,開發出了自己的 MySQL 版本,例如阿里雲上的 RDS、騰訊雲、美團雲等。
  3. MySQL 相關的開源解決方案眾多,無需重複造輪子既可以獲得包括讀寫分離、分庫分表等高階特性,例如 Mycat、Sharding-JDBC 等。同時,MySQL 官方的解決方案也越來越豐富,例如 MySQL-Router 等。

六、MySQL 讀寫分離、分庫分表

  1. 讀寫分離的資料複製延遲問題 MySQL 通過 binlog 實現資料的複製,也就是主從節點間的資料同步。由於 binlog 複製預設是非同步的,因此主從節點之間的資料存在延遲。
  2. 分庫分錶帶來的複雜性,難以執行全域性的排序、聚合等操作? ==> 由於同一個表的資料被寫到了不同的表,不同的資料庫(有可能在不同的伺服器節點上),因此如果需要一個同一個表進行聚合操作或者全域性的排序,會非常困難,且效能較差。如果是對多個表進行 join 操作,由於每個表都可能儲存在多個伺服器節點上,因此 join 操作的複雜度會變得很高,需要藉助 MPP 的引擎才能完成 join 操作。
  3. 目前市面最常用的 MySQL 中介軟體,無非 mycat(基於阿里 cobar 二次開發)、onesql(業界大牛樓方鑫基於 MySQL 官方,用 c/c++ 二次開發,不過是收費版,功能很強大,不過好像作者重回阿里了)、360 基於 MySQL 分支 Atlas 等。

七、MySQL 高可用

當大部分優化或者簡單架構設計完成後,再就剩下資料的高可用,畢竟不能由於資料庫的不可用導致業務的不可用,並且業務的不可用必然會導致企業損失大量使用者,然而這也是技術人員最不願意看到的,也是技術人員成長過程中的痛點。

在考慮 MySQL 資料庫的高可用的架構時,主要要考慮如下幾方面:

  • 如果資料庫發生了宕機或者意外中斷等故障,能儘快恢復資料庫的可用性,儘可能的減少停機時間,保證業務不會因為資料庫的故障而中斷
  • 用作備份、只讀副本等功能的非主節點的資料應該和主節點的資料實時或者最終保持一致。
  • 當業務發生資料庫切換時,切換前後的資料庫內容應當一致,不會因為資料缺失或者資料不一致而影響業務。

關於MySQL高可用,常用架構方案如下:

7.1 主從或主主半同步複製

主從架構基本是基於 binlog,最核心的就是 SQL 執行緒和 IO 執行緒。

7.2 半同步複製優化

普通的 replication,即 MySQL 的非同步複製,依靠 MySQL 二進位制日誌也即 binary log 進行資料複製。比如兩臺機器,一臺主機 (master),另外一臺是從機 (slave)。

  • 正常的複製為:事務一(t1)寫入 binlog buffer;dumper 執行緒通知 slave 有新的事務 t1;binlog buffer 進行 checkpoint;slave 的 io 執行緒接收到 t1 並寫入到自己的的 relay log;slave 的 sql 執行緒寫入到本地資料庫。 這時,master 和 slave 都能看到這條新的事務,即使 master 掛了,slave 可以提升為新的 master。
  • 異常的複製為:事務一(t1)寫入 binlog buffer;dumper 執行緒通知 slave 有新的事務 t1;binlog buffer 進行 checkpoint;slave 因為網路不穩定,一直沒有收到 t1;master 掛掉,slave 提升為新的 master,t1 丟失。
  • 很大的問題是:主機和從機事務更新的不同步,就算是沒有網路或者其他系統的異常,當業務併發上來時,slave 因為要順序執行 master 批量事務,導致很大的延遲。

為了彌補以上幾種場景的不足,MySQL 從 5.5 開始推出了半同步。即在 master 的 dumper 執行緒通知 slave 後,增加了一個 ack,即是否成功收到 t1 的標誌碼。也就是 dumper 執行緒除了傳送 t1 到 slave,還承擔了接收 slave 的 ack 工作。如果出現異常,沒有收到 ack,那麼將自動降級為普通的複製,直到異常修復。

7.3 高可用架構(MHA + 多節點叢集)

MHA Manager 會定時探測叢集中的 master 節點,當 master 出現故障時,它可以自動將最新資料的 slave 提升為新的 master,然後將所有其他的 slave 重新指向新的 master,整個故障轉移過程對應用程式完全透明。

7.4 zookeeper+proxy

Zookeeper 使用分散式演算法保證叢集資料的一致性,使用 zookeeper 可以有效的保證 proxy 的高可用性,可以較好的避免網路分割槽現象的產生。

7.5 共享儲存(SAN 共享儲存、DRBD 磁碟複製)

共享儲存實現了資料庫伺服器和儲存裝置的解耦,不同資料庫之間的資料同步不再依賴於 MySQL 的原生複製功能,而是通過磁碟資料同步的手段,來保證資料的一致性。

基本上,上述三類架構是最常用的了,對於中小型公司,然而筆者公司也就是用到了上述三種了。

基本,講述到這裡,基本上從 MySQL 基本的安裝到引擎選擇乃至效能優化及高可用架構等,都捎帶詳細普及了下,想要 hold 住大部分的效能優化,要考慮的東西還是很多的。畢竟效能優化是個整體概念,巨集觀層面系統優化:應用伺服器,資料庫層面:MySQL、Oracle、PG 等。