MYSQL性能優化系統整理

 

在實際開發過程當中,深深感到MySQL的性能優化對產品的性能重要性,設計好的表結構、索引以及SQL查詢對後臺接口性能是有很大的幫助。不少應用,數據量若是不大,不少時候是看不出MySQL設計上放方方面面的問題。當數據量達到千萬級別甚至是上億的數據量時,MySQL性能優化的技術就相當重要了。mysql

有感於MySQL性能優化的重要性,我特別花了不少時間,系統性地梳理了MySQL性能優化的方方面面知識,從配置文件的參數選擇、庫表結構的設計原則、索引的優化設計、SQL查詢的優化,以及上線後的監控指標等系統講解。redis

1、介紹

數據庫的優化能夠從如下四個方面作優化,其效果更投入成相反,即:算法

效果:sql

  1. SQL及索引 > 數據庫表結構 > 系統配置 > 硬件

成本:數據庫

  1. 硬件 > 系統配置 > 數據庫表結構 > SQL及索引

硬件不用說,固然是配置越高越好,最好是專門用於數據庫的服務器,並採用ssd的,不過這個成本是最高的,咱們主要講解剩下3種的優化思路。固然MySQL的優化工做是持續進行的,並非一次性完成。咱們能夠在3個階段持續進行優化,分別是:產品上線前的數據庫設計(系統配置、數據庫表結構、SQL及索引設計)以及上線後的持續監控優化(慢查詢、mysql指標監控)。centos

固然,對於業務、產品的優化,並不僅僅只是針對MySQL進行優化,還能夠經過其餘方面來進行,例如緩存的應用。緩存

總體的優化思路能夠按照以下的原則逐步進行:安全

  • 優先考慮經過緩存下降對數據庫的讀操做(如:redis)
  • 再考慮讀寫分離,下降數據庫寫操做
  • 最後開始數據拆分,切分模式:首先垂直(縱向)拆分、再次水平拆分
  • 首先考慮按照業務垂直拆分
  • 再考慮水平拆分:先分庫(設置數據路由規則,把數據分配到不一樣的庫中)
  • 最後再考慮分表,單表拆分到數據1000萬之內

2、配置文件優化

如下針對的InnoDB引擎的配置文件my.cnf:性能優化

  1. [client]
  2. socket = /var/lib/mysql/mysql.sock
  3. port = 3306
  4. [mysqld]
  5. # GENERAL
  6. datadir = /var/lib/mysql
  7. socket = /var/lib/mysql/mysql.sock
  8. pid_file = /var/lib/mysql/mysql.pid
  9. user = mysql
  10. port = 3306
  11. # INNODB
  12. innodb_buffer_pool_size = <value>
  13. innodb_log_file_size = <value>
  14. innodb_file_per_table = 1
  15. # LOGGING
  16. slow_query_log = ON
  17. slow_query_log = /var/lib/mysql/mysql-slow.log
  18. log_error = /var/lib/mysql/mysql-error.log
  19. # OTHER
  20. tmp_table_size = 32M
  21. max_heap_table_size = 32M
  22. # 禁用緩存
  23. query_cache_type = 0
  24. query_cache_size = 0
  25. max_connections = <value>
  26. thread_cache_size = <value>
  27. open_files_limit = 65535

 

一、innodb_buffer_pool_size

(1)介紹

InnoDB使用一個緩衝池來保存索引和原始數據,以下圖所示:服務器

InnoDB存儲引擎內存結構

(2)優缺點

緩衝池的做用能夠減小磁盤訪問,咱們知道內存讀寫速度比磁盤的讀寫速度快不少,因此這個參數對mysql性能有很大提高。固然,這裏不是越大越好,也要考慮實際的服務器狀況。總之,InnoDB嚴重依賴緩衝池,咱們必須爲它分配了足夠的內存。

更大的緩衝池會使得mysql服務在重啓和關閉的時候花費很長時間。

(3)如何配置

若是在一個獨立使用的mysql服務器上,這個變量按照流行的經驗法則,能夠把緩衝池大小設置爲服務器內存的約75%~80%。

可是,若是服務器上除了跑mysql服務,還有其餘服務也在運行,那麼在分配緩衝池空間時,須要減去這部分程序佔用的內存、mysql自身須要的內存以及減去足夠讓操做系統緩存InnoDB日誌文件的內存,至少是足夠緩存最近常常訪問的部分。

二、innodb_log_file_size和innodb_log_files_in_group

(1)介紹

InnoDB使用日誌來減小提交事務時的開銷。

InnoDB用日誌把隨機I/O變成順序I/O。

innodb_log_files_in_group 參數控制日誌文件數,通常默認爲2。mysql事務日誌文件是循環覆寫的,以下圖:

(2)優缺點

當一個日誌文件寫滿後,innodb會自動切換到另外一個日誌文件,並且會觸發數據庫的checkpoint,這回致使innodb緩存髒頁的小批量刷新,會明顯下降innodb的性能。若是innodb_log_file_size設置過小,就會致使innodb頻繁地checkpoint,致使性能下降。而若是設置太大,因爲事務日誌是順序I/O,大大提升了I/O性能,可是在崩潰恢復InnoDB時,會致使恢復時間變長。

若是InnoDB數據表有頻繁的寫操做,那麼選擇合適的innodb_log_file_size值對提高MySQL性能很重要。

(3)如何配置

做爲一個經驗法則,日誌文件的所有大小,應該足夠容納服務器一個小時的活動內容。方法以下:

首先,在業務高峯期,計算出1分鐘寫入事務日誌(redo log)的量,而後評估出一個小時的redo log量:

  1. # 使用pager以後,執行命令只顯示Log開頭的
  2. mysql> pager grep Log
  3. PAGER set to 'grep Log'
  4. mysql> show engine innodb status\G select sleep(60); show engine innodb status\G;
  5. Log sequence number 3257464291
  6. Log flushed up to 3257464278
  7. 1 row in set (0.00 sec)
  8. 1 row in set (1 min 0.00 sec)
  9. Log sequence number 3257550399
  10. Log flushed up to 3257550399
  11. 1 row in set (0.00 sec)

Log sequence number是寫入事務日誌的總字節數,經過1分鐘內兩個值的差值,咱們能夠看到每分鐘有多少KB日誌寫入到MySQL中(備註:該返回值是我環境下的,具體數值請參考本身環境的返回值)

  1. mysql> nopager
  2. PAGER set to stdout
  3. mysql> select (3257550399-3257464291)/1024 as KB;
  4. +---------+
  5. | KB |
  6. +---------+
  7. | 84.0898 |
  8. +---------+
  9. 1 row in set (0.00 sec)

那麼,1小時的事務日誌寫入量爲:84KB * 60 = 5040KB,約爲5MB。

因爲默認有兩個日誌文件,在日誌組中,兩個日誌文件的大小是一致的。因此咱們能夠大約設置innodb_log_file_size=3M。

三、innodb_log_buffer_size

innodb_log_buffer_size能夠控制日誌緩衝區的大小。

一般不須要把日誌緩衝區設置得很是大。推薦的範圍是1MB~8MB,通常來講是足夠了,MySQL默認是8MB。

四、innodb_flush_log_at_trx_commit

(1)介紹

MySQL支持用戶自定義在commit時如何將log buffer中的日誌刷到log file中。這種控制經過變量:innodb_flush_log_at_trx_commit 來決定,該變量有:0、一、2三種值,默認爲1。注意,這個變量只是控制commit動做是否刷新log buffer到磁盤中。

  • 設置爲0。把日誌緩衝寫到日誌文件中,而且每秒鐘刷新一次,可是事務提交時不作任何事,該設置是3者中性能最好的。也就是說設置爲0時是(大約)每秒刷新寫入到磁盤中的,當系統崩潰,會丟失1秒鐘的數據。
  • 設置爲1。將日誌緩衝寫入到日誌文件,而且每次事務提交都刷新到持久化存儲。這是默認的設置(而且是最安全的),該設置能保證不會丟失任何已經提交的事務。
  • 設置爲2。每次提交時把日誌緩衝寫到日誌文件,但並不刷新。InnoDB每秒鐘作一次刷新。

(2)如何配置

日誌緩衝必須被刷新到持久化存儲(磁盤),以確保提交的事務徹底被持久化了。若是和持久化相比更在意性能,則能夠修改該參數來控制日誌緩衝刷新的頻繁程度。

五、thread_cache_size

(1)介紹

線程緩存保存哪些當前沒有與鏈接關聯可是準備爲後面新的鏈接服務的線程。當一個新的連接建立時,若是緩存中有線程存在,MySQL從緩存中刪除一個線程,而且把它分配給這個新的鏈接。當鏈接關閉時,若是線程緩存還有空間的話,MySQL又會把線程放回緩存。若是沒有空間的話,MySQL就會銷燬這個線程。

只要MySQL在緩存中還有空閒的線程,它就能夠迅速地響應鏈接請求,由於這樣就不用爲每一個鏈接建立新的線程。

(2)如何配置

thread_cache_size指定了MySQL能夠保存在緩存中的線程數。通常不須要配置這個值,除非服務器會有不少鏈接請求。

通常,能夠根據機器的內存進行設置:

  1. # 1G —> 8
  2. # 2G —> 16
  3. # 3G —> 32
  4. # 大於3G —> 64或更大

六、tmp_table_size和max_heap_table_size

(1)介紹

tmp_table_size:臨時表的內存緩存大小,臨時表是指sql執行時生成的臨時數據表。在優化sql時,應該儘可能避免臨時表。
max_heap_table_size:該參數也會影響到臨時表的內存緩存大小。在增長tmp_table_size的同時,也須要增長max_heap_table_size的大小。

(2)如何配置

能夠經過Created_tmp_disk_tables和Created_tmp_tables狀態來分析是否須要增長tmp_table_size和max_heap_table_size。

  1. #Created_tmp_disk_tables : 磁盤臨時表的數量
  2. #Created_tmp_tables : 內存臨時表的數量
  3. mysql> show global status like 'Created_tmp_disk_tables';
  4. +-------------------------+-------+
  5. | Variable_name | Value |
  6. +-------------------------+-------+
  7. | Created_tmp_disk_tables | 15668 |
  8. +-------------------------+-------+
  9. 1 row in set (0.00 sec)
  10. mysql> show global status like 'Created_tmp_tables';
  11. +--------------------+--------+
  12. | Variable_name | Value |
  13. +--------------------+--------+
  14. | Created_tmp_tables | 737670 |
  15. +--------------------+--------+
  16. 1 row in set (0.00 sec)

七、max_connections

(1)介紹

MySQL的max_connections參數用來設置最大鏈接數。若是該參數設置過小,會致使出現「Too many connections」的錯誤。

若是服務器的併發鏈接請求量比較大,建議提升此值,以增長並行鏈接數量。可是這個是要創建在機器的性能能支撐的狀況下,由於MySQL會爲每個鏈接提供鏈接緩衝區,若是併發鏈接數量過高,會致使消耗內存過多。

(2)如何配置

如何判斷max_connections設置的是否合理?

首先,查看最大鏈接上限:

  1. mysql> show variables like 'max_connections';
  2. +-----------------+-------+
  3. | Variable_name | Value |
  4. +-----------------+-------+
  5. | max_connections | 1000 |
  6. +-----------------+-------+

而後,能夠查看服務器響應的最大鏈接數:

  1. mysql> show global status like 'max_used_connections';
  2. +----------------------+-------+
  3. | Variable_name | Value |
  4. +----------------------+-------+
  5. | Max_used_connections | 3 |
  6. +----------------------+-------+

可見,服務器歷史最大鏈接數遠遠低於mysql服務器容許的最大鏈接上限。

對於mysql服務器最大鏈接上限的設置範圍,最理想的是:服務器響應的最大鏈接數值佔服務器上限鏈接數值的比例值在10%以上,若是在10%如下,說明mysql服務器最大鏈接上限值設置太高。

八、總結

若是使用的是InnoDB,在參數優化的選項裏面,最重要的就是以下兩個:

  • innodb_buffer_pool_size
  • innodb_log_file_size

3、數據庫表結構設計優化

一、InnoDB邏輯存儲結構

InnoDB全部數據都存放在一個叫表空間(tablespace)的地方(ibdata1)。表空間由段(segment)、區(extent)、頁(page)組成。InnoDB邏輯存儲存儲結構以下圖:

1560834709696

注意,若是用戶啓用了參數innodb_file_per_table,則每張表內的數據(包括數據、索引和插入緩衝Bitmap頁)能夠單獨放到一個表空間內,可是其餘數據,如回滾信息、插入緩衝索引頁、系統事務信息等仍是存放在原來的共享表空間內。

(1)段(segment)

常見的段有數據段、索引段、回滾段等

(2)區(extent)

區是由連續的頁組成的空間,每一個區大小都爲1MB。爲了保證區中頁的連續性,InnoDB存儲引擎一次從磁盤中申請4~5個區。在默認狀況下,InnoDB存儲引擎頁大小爲16KB,即一個區中一共有64個連續的頁。

(3)頁(page)

頁是InnoDB磁盤管理的最小單位,默認每一個頁大小爲16KB。常見的頁類型有:

  • 數據頁(B-tree Node)
  • undo頁(undo Log Page)
  • 系統頁(System Page)
  • 事務數據頁(Transaction system Page)
  • 插入緩衝位圖頁(Insert Buffer Bitmap)
  • 插入緩衝空閒列表頁(Insert Buffer Free List)
  • 未壓縮的二進制大對象頁(Uncompressed BLOB Page)
  • 壓縮的二進制大對象頁(compressed BLOB Page)

這裏重點將數據也的存儲結構,該類型存放的是表中行的實際數據。

InnoDB數據也由如下7個部分組成,以下:

1560657805690

  • File Header用來記錄頁的一些頭信息
  • Page Header用來記錄數據頁的狀態信息
  • 每一個頁中都有兩個虛擬行記錄,用來限定記錄的邊界:Infimum和Supremum Record,以下:

1560836385634

  • User Record是實際存儲行記錄的內容。
  • Page Directory存放了記錄的相對位置,這些記錄指針也稱爲Slots(槽)。在Slots中記錄按照索引鍵值順序存放,這樣能夠利用二叉查找迅速找到記錄的指針。
  • File Trailer用於檢測頁是否完整地寫入磁盤

(4)行

InnoDB存儲引擎是按行進行存放的。一個頁中存放的行數據越多,其性能越高,這也是爲何建立字段的時候應該按照最小可用原則
InnoDB存儲引擎提供了Compact和Redundant兩種格式來存放行記錄數據,默認設置爲Compact行格式。能夠經過以下命令查看:

  1. mysql> show table status like 't_ci_sessions'\G;
  2. *************************** 1. row ***************************
  3. Name: t_ci_sessions
  4. Engine: InnoDB
  5. Version: 10
  6. Row_format: Compact
  7. Rows: 9
  8. Avg_row_length: 1820
  9. Data_length: 16384
  10. Max_data_length: 0
  11. Index_length: 16384
  12. Data_free: 0
  13. Auto_increment: NULL
  14. Create_time: 2019-06-10 13:04:27
  15. Update_time: NULL
  16. Check_time: NULL
  17. Collation: utf8_general_ci
  18. Checksum: NULL
  19. Create_options:
  20. Comment:
  21. 1 row in set (0.00 sec)

Compact行記錄格式:

1560655448122

Compact記錄頭信息的格式以下:

1560655619940

數據庫表結構設計原則以下,具體可參考另外一篇博客文章:《MYSQL性能優化學習筆記-(1)數據庫設計

  • 儘可能避免過分設計,例如設計極其複雜查詢的schema設計,或者有不少列的表設計
  • 使用小而簡單的合適數據類型,除非真實數據模型中有確切的須要,不然應該儘量地避免使用NULL值
  • 儘可能使用相同的數據類型存儲類似的值,尤爲是要在關聯條件中使用的列
  • 注意可變長字符串,其在臨時表和排序時可能致使悲觀的按最大長度分配內存
  • 儘可能使用整形定義標識列
  • 當心使用ENUM和SET,最好避免使用BIT
  • 範式是好的,可是反範式有時也是必須的,而且能帶來好處。預先計算、緩存或生成彙總表也可能得到大的好處
  • ALTER TABLE是讓人痛苦的操做,由於大部分狀況下,它都會鎖表而且重建整張表。 解決方法有兩種:一種是在一臺不提供服務的機器上執行ALTER TABLE操做,而後和提供服務的主庫進行切換;另外一種方法是「影子拷貝」,即用要求的表結構建立一張和源表無關的新表,而後經過重命名和刪表操做交換兩張表
  • 在查找表時採用整數逐漸而避免採用基於字符串的值進行關聯

4、索引設計優化

InnoDB存儲引擎支持如下幾種常見索引:

  • B+樹索引
  • 全文索引
  • 哈希索引

這裏咱們重點介紹B+樹索引,也是使用最頻繁的。

B+樹索引,能夠分爲彙集索引(clustered index)和輔助索引(secondary index)。二者區別是,葉子節點存放的是否一整行的信息。

一、數據結構和算法

B+樹索引是目前關係型數據庫系統中查找最爲經常使用和最爲有效的索引。B+樹索引相似於二叉樹,根據鍵值快速找到數據。

注意,B+樹自己補丁找到具體的一條記錄,能找到的只是該記錄所在的頁。數據庫把頁加載到內存,而後經過Page Directory再進行二分查找。

(1)二分查找法

也叫折半查找法。詳細算法原理不展開講,網上有不少,能夠查看:二分查找法

這裏爲何要介紹二分查找法,是由於:

咱們知道每一個數據頁中包含Page Directory,Page Directory中的槽是按照主鍵的順序存放的,對於某一條具體記錄的查詢是經過對Page Directory進行二分查找法獲得的。也就是說,經過B+樹索引定位到具體頁之後,就須要引用二分查找法定位具體的鍵值。

(2)二叉查找樹

二叉查找樹的原理,請自行查閱。

(3)平衡二叉樹

平衡二叉樹的原理,請自行查閱。

(4)B+樹

以下是一顆B+索引樹,若是主鍵是按照順序遞增地,則新插入的數據只需追加到末尾或生成新的葉子節點便可,不會對前面的節點形成修改。而假如主鍵是隨機並不是遞增的,則新插入的主鍵有可能須要插入到以前的葉子節點中,這就可能致使葉子節點的分裂以及B+樹的從新平衡,這形成的代價是比較大的。這也是爲何主鍵索引建議順序遞增,而不建議採用相似md5做爲主鍵或索引

​ 一樣的,刪除或者修改操做都會致使B+樹進行從新平衡,也須要付出必定的代價。因此,使用索引,對於查詢是很是友好的,能夠極大提升速度,可是對於增、刪、改操做者成本變高

1560994779063

二、彙集索引

InnoDB的彙集索引實際上在同一個結構中保存了B-Tree索引和數據行,一個表只能有一個彙集索引。

以下圖,彙集索引,葉子頁包含了行的所有數據,但節點頁只包含索引列。

![156090D:\Dropbox\shuwoom\imagesom\images\1560906184777.png)

三、輔助索引

輔助索引也稱爲非彙集索引,葉子節點並不包含行記錄的所有數據。每一個葉子節點除了索引列還包含主鍵列,經過這個主鍵列就能夠找到一個完整的行記錄。也就是說,經過輔助索引,除了首先要在輔助索引樹中查找到主鍵,還須要在彙集索引樹中經過該主鍵找到對應的完整行數據所在的頁。這也是爲何輔助索引須要進行兩次索引查找的緣由。

以下圖所示:

1560907290323

四、例子說明

以下表,咱們有一個主鍵col1以及三個索引。假設主鍵取值爲1~10000,按隨機順序插入並使用OPTIMIZE TABLE命令作優化。name和age隨機賦值,數據以下表所示。

  1. CREATE TABLE layout_test (
  2. id int NOT NULL,
  3. col2 varchar(16) NOT NULL DEFAULT '',
  4. col3 varchar(16) NOT NULL DEFAULT '',
  5. PRIMARY KEY(id),
  6. KEY(col2),
  7. KEY(col3)
  8. );
行號 id col2 col3
0 99 Tom apple
1 12 Mary banana
2 3000 John milk
9997 18 Judi tea
9998 4700 Tom apple
9999 3 Peter coffee

彙集索引葉子節點分佈以下圖所示:

1560993470695

以col2爲索引列的輔助索引葉子節點分佈圖:

1560993817672

以col3爲索引列的輔助索引葉子節點分佈圖:

1560993904422

(1)覆蓋索引

嚴格地說,覆蓋索引只是輔助索引的一個衍生定義,即從輔助索引中就能夠獲得查詢的記錄,而不須要查詢彙集索引中的記錄。使用覆蓋索引的一個好處是輔助索引不包含整行記錄的全部信息,因此它要比彙集索引小不少,所以一頁包含的輔助索引數量更多,從而能夠減小大量的IO操做。

五、總結

設計和使用索引時,能夠遵循如下原則:

  • 選擇數據類型遵循小而簡單的原則,這樣作的好處是能夠節省索引空間,對於較短的鍵值,索引頁中能容納更多的鍵值,這樣查找速度也會提高。下表是mysql各個字段類型的大小和使用範圍: 字段 存儲大小(單位:字節) 最小值 最大值 TINYINT 1 -128 127 SMALLINT 2 -32768 32767 MEDIUMINT 3 -8,388,608 8,388,607 (838萬) INT 4 -2,147,483,648 2,147,483,647 (21億) BIGINT 8 -9,223,372,036,854,775,808 9,223,372,036,854,775,807 (922京) FLOAT 4 -3.402823466E+38 3.402823466E+38 DOUBLE 8 -1.7976931348623157E+308 1.7976931348623157E+308 DECIMAL* 每9個數字4個字節 ~ -1E+66 ~ 1E+66 TIMESTAMP 4 1970(unix時間戳) 2038(unix時間戳) DATETIME 8 1001年 9999年 因此,若是存儲IP地址,使用UNSIGNED INT存儲,恰好夠用,比使用字符串佔用更少空間,搜索更快。 一樣的。而時間使用DATETIME存儲,比使用字符串(19字節)足足少了11字節。
  • 整形數據比起字符,處理開銷更小,在MySQL中,建議使用內置的日期和時間數據類型,而不是用字符串來存儲時間。
  • 利用覆蓋索引進行查詢,避免回表。Explain返回的Using index就表明從索引中查詢。這也是爲何要避免使用SELECT *的緣由之一。
  • 儘可能指定列爲NOT NULL,NULL會使索引、索引統計和值更加複雜,而且須要額外的存儲空間。這個能夠查看這篇文章《一千個不用NULL的理由》
  • 建議在選擇性高的列上創建索引,最好是惟一索引,區分度越大,則咱們掃描的記錄數越少,例如性別區分度不大,就不適合作索引。
  • 更新很是頻繁的數據不適合建索引。頻繁更新會致使變動B+索引樹,重建索引,這個過程很消耗數據庫性能。
  • 利用最左前綴原則,好比創建一個聯合索引(a,b,c),咱們能夠利用的索引就有(a),(a,b),(a,b,c)
  • 若是肯定有多少條數據,使用limit限制一下,MySQL在查找到對應條數的數據的時候,會中止繼續查找
  • 刪除再也不使用的索引
  • join語法,儘可能將小的表放在前面,在須要on的字段上,數據類型保持一致,並設置素銀,不然MySQL沒法使用索引來Join查詢
  • like 「xxx%」能夠用到索引,like」%xxx%」則不行
  • 在設計開發階段,數據庫字段的定義要避免出現由數據類型定義不當形成的隱式轉換

詳細的原理及其設計能夠查看另外一篇個人博客文章: 《MySQL性能優化學習筆記-(2)索引優化》

這裏推薦一個MySQL大牛的技術教程,做者是阿里的技術專家,目前在騰訊任職數據庫的負責人,對MySQL的原理由深刻的研究瞭解。

5、SQL查詢優化

一、分解關聯查詢

不少高性能的應用都會對關聯查詢進行分解。簡單地,能夠對每個表進行一次單表查詢,而後將結果都再應用程序中進行管理。以下這個查詢:

  1. mysql> SELECT * FROM tag
  2. -> JOIN tag_post ON tag_post.tag_id=tag.id
  3. -> JOIN post ON tag_post.post_id=post.id
  4. -> WHERE tag.tag='mysql;

能夠分解成下面這些查詢來代替:

  1. mysql> SELECT * FROM tag WHERE tag='mysql';
  2. mysql> SELECT * FROM tag_post WHERE tag_id=1234;
  3. mysql> SELECT * FROM post WHERE post.id in (123,456,567,9098,8904);

用分解關聯查詢的方式重構查詢有以下的優點:

  • 讓緩存的效率更高。
  • 將查詢分解後,執行單個查詢能夠減小鎖的競爭。
  • 在應用層作關聯,能夠更容易對數據庫進行拆分,更容易作到高性能和可擴展。
  • 查詢效率也有可能會有所提高。
  • 能夠減小冗餘記錄的查詢。
  • 更進一步,這樣作至關於在應用層中實現了哈希關聯,而不是是使用MySQL的嵌套循環關聯。

二、優化COUNT()查詢

count()能夠統計某個列值的數量,也能夠統計行數。

一個常見的錯誤是,在括號內指定了一個列卻但願統計結果集的行數。若是但願知道的是結果集的行數,最好使用COUNT(*),照片和楊寫意義清晰,性能頁會很好。

通常,COUNT都需掃描大量的行才能得到精確的結果,所以很難優化。在MySQL層面還能作的就只有索引覆蓋掃描(不回表查詢)。若是這樣還不能知足,就須要考慮修改應用架構或者增長緩存(如redis)統計。

三、關聯查詢優化

  • 確保ON或者USING子句的列上有索引。通常來講,除非有其餘理由,不然只須要在關聯順序中的第二個表的相應列上建立索引
  • 確保任何GROUP BY和ORDER BY中的表達式值涉及到一個表中的列,這樣MySQL纔有可能使用索引來優化這個過程。

四、優化子查詢

關於子查詢優化,建議就是儘量使用關聯查詢代替。

五、優化GROUP BY和DISTINCT

  • 若是須要對關聯查詢作分組(GROUP BY),而且是按照查找表中的某個列進行分組,那麼一般採用查找表的主鍵分組的效率比其餘列更高。
  • 若是沒有經過ORDER BY子句顯示地指定排序列,當查詢使用GROUP BY子句的時候,結果集會自動按照分組的字段進行排序。若是不關心結果集的順尋,而這種默認又致使了須要文件排序,則可使用ORDER BY NULL,讓MySQL再也不進行文件排序。

六、優化LIMIT分頁

一個很是常見的問題是,在偏移量很大的時候,例如:limit 10000,20這樣的查詢,MySQL須要查詢10020條記錄後只返回最後20條,前面10000條記錄都將被拋棄,這樣代價很是高。針對這個問題,有如下幾個方法:

  • 最簡單的辦法就是儘量地使用覆蓋掃描,而不是查詢全部的列。 例以下面的查詢:
  1. mysql> SELECT file_id,description FROM sakila.film ORDER BY title LIMIT 50,5

若是表數據很是大,能夠改爲以下:

  1. mysql> SELECT file_id,description FROM sakila.film
  2. -> INNER JOIN(
  3. -> SELECT file_id FROM sakila.film ORDER BY title LIMIT 50,5
  4. ) AS lim USING(film_id);

這裏「延遲關聯」大大提升了查詢效率,它讓MySQL掃描儘量少的頁面,獲取須要訪問的記錄後在根據關聯列回原表查詢須要的全部列

  • 可使用書籤記錄上次取數據的位置,那麼下次就能夠直接從該書籤記錄的位置開始掃描,這樣就能夠避免OFFSET致使MySQL掃描大量不須要的行而後拋棄掉。 例如:
  1. mysql> SELECT * FROM sakila.rental ORDER BY rental_id DESC LIMIT 20;

假如上面的擦汗尋返回的是主鍵爲16049到16030的租借記錄,那麼下一頁擦汗尋就能夠從16030這個點開始:

  1. mysql> SELECT * FROM sakila.rental WHERE rental_id < 16030 ORDER BY rental_id DSEC LIMIT 20;

該技術的好處是,不管翻頁到多麼後面,性能都會很好。

七、優化UNION查詢

  • MySQL老是經過建立並填充臨時表的方式來執行UNION查詢。在UNION查詢優化中,常常須要手工地將WHERE,LIMIT,ORDER BY等子句下推到UNION的各個子查詢中,一邊優化器能夠充分利用這些條件進行優化。
  • 除非確實須要服務器消除重複的行,不然就必定要使用UNION ALL。若是沒有ALL關鍵字,MySQL會給臨時表加上DISTINCT選項,這回致使對整個臨時表的數據作惟一性檢查,代價很高。

擴展:

詳細的原理和方法能夠查看我寫的博客文章: 《MySQL性能優化學習筆記-(3)查詢優化》

而對於SQL語句的性能分析能夠查看我專門寫的Explain的博客文章:《MySQL性能優化學習筆記-(4)性能分析》

6、上線後監控優化

MySQL數據庫上線後,能夠等其穩定運行一段時間後再根據服務器的status裝填進行適當優化,能夠用以下命令列出MySQL服務器運行的各類狀態值:

  1. # 查詢全部狀態值
  2. show global status;
  3. # 查詢具體某個狀態值
  4. show status like '%xx%';

一、慢查詢

爲了定位系統中效率比較低下的查詢語句,須要經過慢查詢日誌來定位:

  1. mysql> show variables like '%slow%';
  2. +---------------------------+---------------------------------------------+
  3. | Variable_name | Value |
  4. +---------------------------+---------------------------------------------+
  5. | log_slow_admin_statements | OFF |
  6. | log_slow_slave_statements | OFF |
  7. | slow_launch_time | 2 |
  8. | slow_query_log | ON |
  9. | slow_query_log_file | /data1/mysql_root/data/20144/slow_query.log |
  10. +---------------------------+---------------------------------------------+
  1. mysql> show global status like '%slow%';
  2. +---------------------+-------+
  3. | Variable_name | Value |
  4. +---------------------+-------+
  5. | Slow_launch_threads | 0 |
  6. | Slow_queries | 18014 |
  7. +---------------------+-------+

一個好的性能分析工具,能很好地提升數據庫性能的管理效率,而pt-query-diget就是專門針對MySQL數據庫慢查詢的一個分析工具,相比於官方的mysqldumpslow,這個工具分析結果更加具體完善。

(1)pt-query-digest工具安裝

  1. # 安裝最新的 percona release package
  2. yum -y install https://repo.percona.com/yum/percona-release-latest.noarch.rpm
  3. # 安裝 percona toolkit
  4. yum -y install percona-toolkit
  5. # 驗證 pt-queyr-digest 是否可使用
  6. pt-query-digest --help

(2)慢查詢日誌分析

首先獲取MySQL服務器上慢查詢日誌的路徑:

  1. mysql> show variables like '%slow_query_log%';
  2. +---------------------+----------------------+
  3. | Variable_name | Value |
  4. +---------------------+----------------------+
  5. | slow_query_log | ON |
  6. | slow_query_log_file | /data/mysql/slow.log |
  7. +---------------------+----------------------+

然後通過pt-query-digest工具分析下,看看是否有什麼問題:

  1. pt-query-digest --report /data/mysql/slow.log /data/mysql/report.log

分析報告分爲三部分:

第一部分:整體統計結果
  1. # 700ms user time, 20ms system time, 27.61M rss, 214.82M vsz
  2. # Current date: Mon Jun 10 09:13:13 2019
  3. # Hostname: VM_16_17_centos
  4. # Files: /data/mysql/slow.log
  5. # Overall: 3.24k total, 12 unique, 0.01 QPS, 0.01x concurrency ___________
  6. # Time range: 2019-06-03 13:18:15 to 2019-06-08 17:25:52
  7. # Attribute total min max avg 95% stddev median
  8. # ============ ======= ======= ======= ======= ======= ======= =======
  9. # Exec time 4296s 1s 194s 1s 2s 5s 1s
  10. # Lock time 244ms 0 3ms 75us 103us 72us 66us
  11. # Rows sent 3.13k 0 10 0.99 0.99 0.22 0.99
  12. # Rows examine 4.14G 0 1.38M 1.31M 1.32M 228.71k 1.32M
  13. # Query size 781.01k 15 425 247.14 246.02 27.68 246.02

參數說明:

  • Overall: 總共有多少條查詢,上例爲總共3.24k個查詢。
  • Time range: 查詢執行的時間範圍。
  • unique: 惟一查詢數量,即對查詢條件進行參數化之後,總共有多少個不一樣的查詢,該例爲12。
  • total: 總計 min:最小 max: 最大 avg:平均
  • 95%: 把全部值從小到大排列,位置位於95%的那個數,這個數通常最具備參考價值。
  • median: 中位數,把全部值從小到大排列,位置位於中間那個數。
第二部分: 查詢分組統計結果:
  1. # Profile
  2. # Rank Query ID Response time Calls R/Call V/M I
  3. # ==== ============================ =============== ===== ======== ===== =
  4. # 1 0x523892349F733C254576AB7... 3731.1533 86.8% 3140 1.1883 0.08 SELECT t_vul_scan_result_? t_vul_scan_plugin_mng_?
  5. # 2 0xA72A99A2AD696A84F9CCD57... 360.7725 8.4% 2 180.3862 2.08 DELETE t_vul_scan_result_?
  6. # 4 0x40485EE0C3F3C643A5035DC... 62.4520 1.5% 34 1.8368 0.56 SELECT
  7. # 5 0x266AA5D17D7ACC44DEC38DA... 53.5156 1.2% 40 1.3379 0.02 REPLACE t_vul_scan_result_?
  8. # MISC 0xMISC 88.5263 2.1% 20 4.4263 0.0 <8 ITEMS>

這部分對查詢進行參數化並分組,而後對各種查詢的執行狀況進行分析,結果按總執行時長,從大到小排序。

參數說明:

  • Response: 總的響應時間。
  • time: 該查詢在本次分析中總的時間佔比。
  • calls: 執行次數,即本次分析總共有多少條這種類型的查詢語句。
  • R/Call: 平均每次執行的響應時間。
  • Item : 查詢對象
第三部分:每一種查詢的詳細統計結果:

這裏咱們以第一個查詢爲例:

  1. # Query 1: 0.06 QPS, 0.07x concurrency, ID 0x523892349F733C254576AB7240430D5A at byte 204852
  2. # This item is included in the report because it matches --limit.
  3. # Scores: V/M = 0.08
  4. # Time range: 2019-06-04 19:44:09 to 2019-06-05 10:20:42
  5. # Attribute pct total min max avg 95% stddev median
  6. # ============ === ======= ======= ======= ======= ======= ======= =======
  7. # Count 97 3140
  8. # Exec time 86 3731s 1s 3s 1s 2s 314ms 1s
  9. # Lock time 97 237ms 48us 2ms 75us 103us 53us 66us
  10. # Rows sent 98 3.07k 1 1 1 1 0 1
  11. # Rows examine 99 4.13G 677.82k 1.38M 1.35M 1.32M 72.24k 1.32M
  12. # Query size 97 760.47k 248 248 248 248 0 248
  13. # String:
  14. # Databases vul_scan
  15. # Hosts localhost
  16. # Users scan
  17. # Query_time distribution
  18. # 1us
  19. # 10us
  20. # 100us
  21. # 1ms
  22. # 10ms
  23. # 100ms
  24. # 1s ################################################################
  25. # 10s+
  26. # Tables
  27. # SHOW TABLE STATUS FROM `vul_scan` LIKE 't_vul_scan_result_0'\G
  28. # SHOW CREATE TABLE `vul_scan`.`t_vul_scan_result_0`\G
  29. # SHOW TABLE STATUS FROM `vul_scan` LIKE 't_vul_scan_plugin_mng_0'\G
  30. # SHOW CREATE TABLE `vul_scan`.`t_vul_scan_plugin_mng_0`\G
  31. # EXPLAIN /*!50100 PARTITIONS*/
  32. SELECT count(distinct resultId) as cnt, plugin.level
  33. FROM `t_vul_scan_result_0` as `result`
  34. JOIN `t_vul_scan_plugin_mng_0` as `plugin` ON (`result`.`pluginId`=plugin.pluginId)
  35. WHERE `taskInstanceId` = '100032'
  36. GROUP BY `level`
  37. ORDER BY `level` DESC\G

咱們經過explain來分析該sql語句:

  1. mysql> explain SELECT count(distinct resultId) as cnt, plugin.level FROM `t_vul_scan_result_0` as `result` JOIN `t_vul_scan_plugin_mng_0` as `plugin` ON (`result`.`pluginId`=plugin.pluginId) WHERE `taskInstanceId` = '100032' GROUP BY `level` ORDER BY `level` DESC\G
  2. *************************** 1. row ***************************
  3. id: 1
  4. select_type: SIMPLE
  5. table: result
  6. type: ref
  7. possible_keys: taskInstanceId,pluginId
  8. key: taskInstanceId
  9. key_len: 4
  10. ref: const
  11. rows: 1
  12. Extra: Using temporary; Using filesort
  13. *************************** 2. row ***************************
  14. id: 1
  15. select_type: SIMPLE
  16. table: plugin
  17. type: eq_ref
  18. possible_keys: PRIMARY,level
  19. key: PRIMARY
  20. key_len: 4
  21. ref: vul_scan.result.pluginId
  22. rows: 1
  23. Extra: NULL
  24. 2 rows in set (0.00 sec)

可見,這裏使用了文件排序,致使了查詢速度變慢。

經過pt-query-digest定位到具體的問題sql語句,而後經過explain具體分析sql語句並優化。

二、鏈接數

若是常常遇到MySQL:ERROR 1040:Too many connections的狀況,通常有兩種可能:

  • 訪問量確實很高,MySQL服務器扛不住,這個時候須要考慮增長從服務器分散讀壓力
  • MySQL配置文件中max_connections的值太小,達到了max_connections限制。此時,它就會開始拒絕新鏈接,同時Connection_errors_max_connections指標會開始增長,追蹤全部失敗鏈接嘗試的Aborted_connects指標也會開始增長。

(1)查詢MySQL服務器的最大鏈接數

  1. mysql> show variables like 'max_connections';
  2. +-----------------+-------+
  3. | Variable_name | Value |
  4. +-----------------+-------+
  5. | max_connections | 1000 |
  6. +-----------------+-------+
  1. mysql> show global status like '%errors_max_connections%';
  2. +-----------------------------------+-------+
  3. | Variable_name | Value |
  4. +-----------------------------------+-------+
  5. | Connection_errors_max_connections | 0 |
  6. +-----------------------------------+-------+
  7. mysql> show global status like '%Aborted_connects%';
  8. +------------------+-------+
  9. | Variable_name | Value |
  10. +------------------+-------+
  11. | Aborted_connects | 216 |
  12. +------------------+-------+

(2)查詢MySQL服務器歷史的最大鏈接數

  1. mysql> show global status like 'max_used_connections';
  2. +----------------------+-------+
  3. | Variable_name | Value |
  4. +----------------------+-------+
  5. | Max_used_connections | 3 |
  6. +----------------------+-------+

因爲MySQL歷史最大的鏈接數只有3,沒有達到MySQL服務器最大鏈接數上限1000,因此不會出現1040錯誤。

三、緩存使用狀況

key_buffer_size是設置MyISAM表索引緩存空間的大小,此參數對MyISAM表性能影響最大。下面是一臺MyISAM爲主要存儲引擎服務器的配置:

  1. mysql> show variables like 'key_buffer_size';
  2. +-----------------+-----------+
  3. | Variable_name | Value |
  4. +-----------------+-----------+
  5. | key_buffer_size | 536870912 |
  6. +-----------------+-----------+

從上面知道,分配了512MB內存給key_buffer_size。再來看看key_buffer_size的使用狀況:

  1. mysql> show global status like 'key_read%';
  2. +-------------------+--------------+
  3. | Variable_name | Value |
  4. +-------------------+-------+
  5. | Key_read_requests | 27813678766 |
  6. | Key_reads | 6798830 |
  7. +-------------------+--------------+

一共有27813678766個索引讀取請求,有6798830個請求在內存中沒有找到,直接從硬盤讀取索引。

  1. key_cache_miss_rate = key_reads /key_read_requests * 100%

好比上面的數據,key_cache_miss_rate爲0.0244%,效果很是好,key_cache_miss_rate在0.1%如下都很好,若是key_cache_miss_rate在0.01%如下的話,說明key_cache_miss_rate分配得過多,能夠適當減小。

四、臨時表

每次建立臨時表,Created_tmp_tables都會增長,若是在磁盤上建立臨時表,Created_tmp_disk_tables也會增長,Created_tmp_files表示MySQL服務建立臨時文件數。

  1. mysql> show global status like 'created_tmp%';
  2. +-------------------------+-------+
  3. | Variable_name | Value |
  4. +-------------------------+-------+
  5. | Created_tmp_disk_tables | 0 |
  6. | Created_tmp_files | 5 |
  7. | Created_tmp_tables | 502 |
  8. +-------------------------+-------+

理想的配置以下:

  1. Created_tmp_disk_tables/ Created_tmp_files * 100% <= 25%

查看MySQL服務器對臨時表的配置:

  1. mysql> show variables where Variable_name in ('tmp_table_size','max_heap_table_size');
  2. +---------------------+----------+
  3. | Variable_name | Value |
  4. +---------------------+----------+
  5. | max_heap_table_size | 16777216 |
  6. | tmp_table_size | 16777216 |
  7. +---------------------+----------+

五、線程使用狀況

若是在MySQL服務器的配置中設置了thread_cache_size,當客戶端斷開時,服務器處理此客戶請求的線程將會緩存起來以響應下一個客戶而不是銷燬(前提是緩存數未達到上線)。便可以從新利用保存在緩存中線程的數量,當斷開鏈接時若是緩存中還有空間,那麼客戶端的線程將被放到緩存中,若是線程從新被請求,那麼請求將從緩存中讀取,若是緩存中是空的或者是新的請求,那麼這個線程將被從新建立,若是有不少新的線程,增長這個值能夠改善系統性能。查看命令以下:

  1. mysql> show variables like 'thread_cache_size';
  2. +-------------------+-------+
  3. | Variable_name | Value |
  4. +-------------------+-------+
  5. | thread_cache_size | 512 |
  6. +-------------------+-------+

查看當前MySQL服務器的線程使用狀況:

  1. mysql> show global status like 'thread%';
  2. +-------------------------+-------+
  3. | Variable_name | Value |
  4. +-------------------------+-------+
  5. | Threadpool_idle_threads | 0 |
  6. | Threadpool_threads | 0 |
  7. | Threads_cached | 381 |
  8. | Threads_connected | 5 |
  9. | Threads_created | 386 |
  10. | Threads_running | 2 |
  11. +-------------------------+-------+
  12. mysql> show global status like 'Connection_errors_internal';
  13. +----------------------------+-------+
  14. | Variable_name | Value |
  15. +----------------------------+-------+
  16. | Connection_errors_internal | 0 |
  17. +----------------------------+-------+
  18. 1 row in set (0.00 sec)
  19. mysql> show global status like 'Aborted_connects';
  20. +------------------+---------+
  21. | Variable_name | Value |
  22. +------------------+---------+
  23. | Aborted_connects | 1044566 |
  24. +------------------+---------+
  25. 1 row in set (0.00 sec)
  26. mysql> show global status like 'Connection_error_max_connections';
  27. Empty set (0.00 sec)

說明:

  • Threads_cached:表明當前此時此刻線程緩存中有多少空閒線程
  • Threads_connected:表明當前已創建鏈接的線程數量,每一個鏈接對應一個線程。當全部可用鏈接都被佔用時,若是一個客戶端試圖鏈接至MySQL,後者會返回」Too many connections」的錯誤,同時將Connection_errors_max_connections的值增長
  • Threads_created:表明從最近一次服務啓動,已建立線程的數量
  • Threads_running:當前運行的鏈接
  • Connection_errors_internal:因爲服務器內部自己致使的錯誤
  • Aborted_connects:嘗試與服務器創建鏈接可是失敗的次數

六、緩衝池利用狀況

InnoDB存儲引擎內存結構

InnoDB在內存中使用一片區域做爲緩衝區,用來緩存數據表、索引等數據(如上表),緩衝區過小,會致使數據庫性能降低,導致磁盤IO增長。

咱們知道,內存的讀取速度比磁盤讀取速度要快不少,當Innodb_buffer_pool_reads的值開始增長,就意味着數據庫性能有可能出現問題。

  1. mysql> show global status like 'Innodb_buffer_pool_read%';
  2. +---------------------------------------+---------------+
  3. | Variable_name | Value |
  4. +---------------------------------------+---------------+
  5. | Innodb_buffer_pool_read_ahead_rnd | 0 |
  6. | Innodb_buffer_pool_read_ahead | 60272 |
  7. | Innodb_buffer_pool_read_ahead_evicted | 0 |
  8. | Innodb_buffer_pool_read_requests | 3959122635741 |
  9. | Innodb_buffer_pool_reads | 30673 |
  10. +---------------------------------------+---------------+

說明:

  • Innodb_buffer_pool_read_requests:總共從緩衝池中緩存的頁面中讀取出的頁數
  • Innodb_buffer_pool_reads:從磁盤上一頁一頁的讀取的頁數,從緩衝池中讀取頁面, 但緩衝池裏面沒, 就會從磁盤讀取

緩衝池利用率是在考慮擴大緩衝池以前應該檢查的重要指標,能夠經過以下方式計算獲得:

  1. (Innodb_buffer_pool_pages_total - Innodb_buffer_pool_pages_free) /
  2. Innodb_buffer_pool_pages_total

另外一個重要的指標,緩衝池的命中率計算方法:

  1. (Innodb_buffer_pool_read_requests - Innodb_buffer_pool_reads) /
  2. Innodb_buffer_pool_read_requests * 100%
  1. mysql> show global status like '%buffer_pool_pages%';
  2. +----------------------------------+-------------+
  3. | Variable_name | Value |
  4. +----------------------------------+-------------+
  5. | Innodb_buffer_pool_pages_data | 852799 |
  6. | Innodb_buffer_pool_pages_dirty | 31 |
  7. | Innodb_buffer_pool_pages_flushed | 30796616008 |
  8. | Innodb_buffer_pool_pages_free | 9544 |
  9. | Innodb_buffer_pool_pages_misc | 52537 |
  10. | Innodb_buffer_pool_pages_total | 914880 |
  11. +----------------------------------+-------------+

緩衝池轉化爲字節大小的計算公式:

  1. Innodb_buffer_pool_pages_total * innodb_page_size
  2. # 上面的配置對應的緩衝池大小爲:
  3. # 16KB * 914880 / 1024 / 1024 = 14.295GB

innodb_page_size頁面大小是可調整的,默認是16384字節,即16KB,能夠經過以下命令查看:

  1. mysql> show variables like 'innodb_page_size';
  2. +------------------+-------+
  3. | Variable_name | Value |
  4. +------------------+-------+
  5. | innodb_page_size | 16384 |
  6. +------------------+-------+

也能夠經過innodb_buffer_pool_size直接獲取緩衝池大小:

  1. mysql> show variables like '%innodb_buffer_pool_size%';
  2. +-------------------------+-------------+
  3. | Variable_name | Value |
  4. +-------------------------+-------------+
  5. | innodb_buffer_pool_size | 14989393920 |
  6. +-------------------------+-------------+

七、磁盤排序

監控磁盤是否出現磁盤排序命令:

  1. mysql> show global status like 'sort_merge_passes';
  2. +-------------------+---------+
  3. | Variable_name | Value |
  4. +-------------------+---------+
  5. | Sort_merge_passes | 2169684 |
  6. +-------------------+---------+
  7. 1 row in set (0.00 sec)

sort_merge_passes:必需要作歸併排序的次數

八、查看網絡傳輸量

  1. mysql> show global status like 'bytes_received';
  2. +----------------+---------------+
  3. | Variable_name | Value |
  4. +----------------+---------------+
  5. | Bytes_received | 2604213103777 |
  6. +----------------+---------------+
  7. 1 row in set (0.00 sec)
  8. mysql> show global status like 'bytes_sent';
  9. +---------------+---------------+
  10. | Variable_name | Value |
  11. +---------------+---------------+
  12. | Bytes_sent | 7074371933580 |
  13. +---------------+---------------+
  14. 1 row in set (0.00 sec)

九、吞吐量

MySQL中有各類不一樣的統計指標,其監控指標多以Com_xxx的方式命名,比較經常使用的有QPS和TPS。

MySQL與QPS相關的三個監控項,分別爲Queries、Questions、Com_select,通常咱們採用Com_select做爲QPS的指標。一樣,對於TPS,採用Com_insert + Com_update + Com_delete三個統計項之和做爲指標。

  1. mysql> show global status like 'Com_select';
  2. +---------------+------------+
  3. | Variable_name | Value |
  4. +---------------+------------+
  5. | Com_select | 6552507251 |
  6. +---------------+------------+
  7. 1 row in set (0.00 sec)
  8. mysql> show global status like 'Com_insert';
  9. +---------------+------------+
  10. | Variable_name | Value |
  11. +---------------+------------+
  12. | Com_insert | 1940243238 |
  13. +---------------+------------+
  14. 1 row in set (0.00 sec)
  15. mysql> show global status like 'Com_update';
  16. +---------------+------------+
  17. | Variable_name | Value |
  18. +---------------+------------+
  19. | Com_update | 1493408493 |
  20. +---------------+------------+
  21. 1 row in set (0.00 sec)
  22. mysql> show global status like 'Com_delete';
  23. +---------------+---------+
  24. | Variable_name | Value |
  25. +---------------+---------+
  26. | Com_delete | 1966462 |
  27. +---------------+---------+
  28. 1 row in set (0.00 sec)

7、分庫分表

一、垂直拆分

垂直拆分是指數據表列的拆分,把一張列比較多的表拆分爲多張表,以下圖所示:

一般按照如下原則進行垂直拆分:

  • 把不經常使用的字段單獨放在一張表
  • 把text,blob等大字段拆分出來放在附表中
  • 常常組合查詢的列放在一張表中

二、水平拆分

水平拆分是把一張表的數據拆分紅多張表來存放,以下圖:

有兩種水平拆分方式:

方法1:經過取模方式進行表的拆分

例如一張800w的用戶表users,爲提升效率,將表分紅users1,user2,user3,user4。經過ID取模的方法把數據分散到四張表內 Id % 4 + 1 = [1,2,3,4]。查詢、更新、刪除也是經過取模的方法來查詢:

  1. $_GET['id'] = 17,
  2. 17%4 + 1 = 2,
  3. $tableName = 'users'.'2'
  4. Select * from users2 where id = 17;

方法2:經過InnoDB存儲引擎支持的分區功能進行

該方法,就訪問數據庫的應用而言,從邏輯上將只有一個表或索引,可是在物理上這個表或索引可能由多個物理分區組成,每一個分區都是獨立的對象,能夠獨自處理。

當前MySQL數據庫支持如下幾種類型的分區:

  • RANGE分區:行數據基於屬於一個給定連續區間的列值被放入分區。
  • LIST分區:和RANGE分區相似,只是LIST分區面向的是離散的值。
  • HASH分區:根據用戶自定義的表達式的返回值來進行分區,返回值不能爲負數。
  • KEY分區:根據MySQL數據庫自定義的哈希函數來進行分區。

這裏舉HASH分區爲例子:

  1. CREATE TABLE t_hash(
  2. a INT,
  3. b DATETIME
  4. )ENGINE=InnoDB
  5. PARTITION BY HASH(Year(b))
  6. PARTITIONS 4;

若是插入一個列b爲2010-04-01的記錄到t_hash中,那麼保存該記錄的分區以下:

  1. MOD(YEAR('2010-04-01'),4)
  2. = MOD(2010,4)
  3. = 2

所以記錄會放入分區p2中,

  1. mysql> INSERT INTO t_hash(a,b) VALUES(1,'2010-04-01');
  2. Query OK, 1 row affected (0.03 sec)
  1. mysql> SELECT table_name,partition_name,table_rows FROM information_schema.PARTITIONS WHERE table_schema=DATABASE() AND table_name='t_hash';
  2. +------------+----------------+------------+
  3. | table_name | partition_name | table_rows |
  4. +------------+----------------+------------+
  5. | t_hash | p0 | 0 |
  6. | t_hash | p1 | 0 |
  7. | t_hash | p2 | 1 |
  8. | t_hash | p3 | 0 |
  9. +------------+----------------+------------+
  10. 4 rows in set (0.04 sec)
 

8、海量架構

一、MySql讀寫分離

二、MySQL分佈式集羣

三、NoSQL海量架構方案

若是到了要考慮對MySQL作讀寫分離或者要搭建分佈式集羣的地步,是否是要開始考慮採用海量的NoSQL架構方案。要知道讀寫分離、分佈式集羣這個方案會致使更復雜的問題,並且還存在主從之間數據同步時延的問題。

而目前的NewSQL數據庫方案,是很好的海量架構選擇。據我一個在微信工做的同事瞭解,目前微信是基本上都採用NewSQL的存儲數據庫,不多用MySQL,並且TiDB是兼容MySQL協議,遷移也很方便,基本上不須要改動代碼。

最關鍵的是NewSQL支持水平彈性擴展、數據強一致性保證、海量數據高併發實時寫入與實時查詢。

轉載請備註來源: 《MySQL性能優化系統整理》 | shuwoom.com

相關文章
相關標籤/搜索