詳解MySQL大表優化方案

當MySQL單表記錄數過大時,增刪改查性能都會急劇降低,能夠參考如下步驟來優化:html

單表優化前端

除非單表數據將來會一直不斷上漲,不然不要一開始就考慮拆分,拆分會帶來邏輯、部署、運維的各類複雜度,通常以整型值爲主的表在千萬級如下,字符串爲主的表在五百萬如下是沒有太大問題的。而事實上不少時候MySQL單表的性能依然有很多優化空間,甚至能正常支撐千萬級以上的數據量:node

字段python

  • 儘可能使用TINYINT、SMALLINT、MEDIUM_INT做爲整數類型而非INT,若是非負則加上UNSIGNED
  • VARCHAR的長度只分配真正須要的空間
  • 使用枚舉或整數代替字符串類型
  • 儘可能使用TIMESTAMP而非DATETIME,
  • 單表不要有太多字段,建議在20之內
  • 避免使用NULL字段,很難查詢優化且佔用額外索引空間
  • 用整型來存IP

索引mysql

  • 索引並非越多越好,要根據查詢有針對性的建立,考慮在WHERE和ORDER BY命令上涉及的列創建索引,可根據EXPLAIN來查看是否用了索引仍是全表掃描
  • 應儘可能避免在WHERE子句中對字段進行NULL值判斷,不然將致使引擎放棄使用索引而進行全表掃描
  • 值分佈很稀少的字段不適合建索引,例如"性別"這種只有兩三個值的字段
  • 字符字段只建前綴索引
  • 字符字段最好不要作主鍵
  • 不用外鍵,由程序保證約束
  • 儘可能不用UNIQUE,由程序保證約束
  • 使用多列索引時主意順序和查詢條件保持一致,同時刪除沒必要要的單列索引

查詢SQLgit

  • 可經過開啓慢查詢日誌來找出較慢的SQL
  • 不作列運算:SELECT id WHERE age + 1 = 10,任何對列的操做都將致使表掃描,它包括數據庫教程函數、計算表達式等等,查詢時要儘量將操做移至等號右邊
  • sql語句儘量簡單:一條sql只能在一個cpu運算;大語句拆小語句,減小鎖時間;一條大sql能夠堵死整個庫
  • 不用SELECT *
  • OR改寫成IN:OR的效率是n級別,IN的效率是log(n)級別,in的個數建議控制在200之內
  • 不用函數和觸發器,在應用程序實現
  • 避免%xxx式查詢
  • 少用JOIN
  • 使用同類型進行比較,好比用'123'和'123'比,123和123比
  • 儘可能避免在WHERE子句中使用!=或<>操做符,不然將引擎放棄使用索引而進行全表掃描
  • 對於連續數值,使用BETWEEN不用IN:SELECT id FROM t WHERE num BETWEEN 1 AND 5
  • 列表數據不要拿全表,要使用LIMIT來分頁,每頁數量也不要太大

引擎github

目前普遍使用的是MyISAM和InnoDB兩種引擎:web

MyISAMsql

MyISAM引擎是MySQL 5.1及以前版本的默認引擎,它的特色是:數據庫

  • 不支持行鎖,讀取時對須要讀到的全部表加鎖,寫入時則對錶加排它鎖
  • 不支持事務
  • 不支持外鍵
  • 不支持崩潰後的安全恢復
  • 在表有讀取查詢的同時,支持往表中插入新紀錄
  • 支持BLOB和TEXT的前500個字符索引,支持全文索引
  • 支持延遲更新索引,極大提高寫入性能
  • 對於不會進行修改的表,支持壓縮表,極大減小磁盤空間佔用

InnoDB

InnoDB在MySQL 5.5後成爲默認索引,它的特色是:

  • 支持行鎖,採用MVCC來支持高併發
  • 支持事務
  • 支持外鍵
  • 支持崩潰後的安全恢復
  • 不支持全文索引

整體來說,MyISAM適合SELECT密集型的表,而InnoDB適合INSERT和UPDATE密集型的表

系統調優參數

可使用下面幾個工具來作基準測試:

  • sysbench:一個模塊化,跨平臺以及多線程的性能測試工具
  • iibench-mysql:基於 Java 的 MySQL/Percona/MariaDB 索引進行插入性能測試工具
  • tpcc-mysql:Percona開發的TPC-C測試工具

具體的調優參數內容較多,具體可參考官方文檔,這裏介紹一些比較重要的參數:

  • back_log:back_log值指出在MySQL暫時中止回答新請求以前的短期內多少個請求能夠被存在堆棧中。也就是說,若是MySql的鏈接數據達到max_connections時,新來的請求將會被存在堆棧中,以等待某一鏈接釋放資源,該堆棧的數量即back_log,若是等待鏈接的數量超過back_log,將不被授予鏈接資源。能夠從默認的50升至500
  • wait_timeout:數據庫鏈接閒置時間,閒置鏈接會佔用內存資源。能夠從默認的8小時減到半小時
  • max_user_connection: 最大鏈接數,默認爲0無上限,最好設一個合理上限
  • thread_concurrency:併發線程數,設爲CPU核數的兩倍
  • skip_name_resolve:禁止對外部鏈接進行DNS解析,消除DNS解析時間,但須要全部遠程主機用IP訪問
  • key_buffer_size:索引塊的緩存大小,增長會提高索引處理速度,對MyISAM表性能影響最大。對於內存4G左右,可設爲256M或384M,經過查詢show status like 'key_read%',保證key_reads / key_read_requests在0.1%如下最好
  • innodb_buffer_pool_size:緩存數據塊和索引塊,對InnoDB表性能影響最大。經過查詢show status like 'Innodb_buffer_pool_read%',保證 (Innodb_buffer_pool_read_requests – Innodb_buffer_pool_reads) / Innodb_buffer_pool_read_requests越高越好
  • innodb_additional_mem_pool_size:InnoDB存儲引擎用來存放數據字典信息以及一些內部數據結構的內存空間大小,當數據庫對象很是多的時候,適當調整該參數的大小以確保全部數據都能存放在內存中提升訪問效率,當太小的時候,MySQL會記錄Warning信息到數據庫的錯誤日誌中,這時就須要該調整這個參數大小
  • innodb_log_buffer_size:InnoDB存儲引擎的事務日誌所使用的緩衝區,通常來講不建議超過32MB
  • query_cache_size:緩存MySQL中的ResultSet,也就是一條SQL語句執行的結果集,因此僅僅只能針對select語句。當某個表的數據有任何任何變化,都會致使全部引用了該表的select語句在Query Cache中的緩存數據失效。因此,當咱們的數據變化很是頻繁的狀況下,使用Query Cache可能會得不償失。根據命中率(Qcache_hits/(Qcache_hits+Qcache_inserts)*100))進行調整,通常不建議太大,256MB可能已經差很少了,大型的配置型靜態數據可適當調大.
  • 能夠經過命令show status like 'Qcache_%'查看目前系統Query catch使用大小
  • read_buffer_size:MySql讀入緩衝區大小。對錶進行順序掃描的請求將分配一個讀入緩衝區,MySql會爲它分配一段內存緩衝區。若是對錶的順序掃描請求很是頻繁,能夠經過增長該變量值以及內存緩衝區大小提升其性能
  • sort_buffer_size:MySql執行排序使用的緩衝大小。若是想要增長ORDER BY的速度,首先看是否可讓MySQL使用索引而不是額外的排序階段。若是不能,能夠嘗試增長sort_buffer_size變量的大小
  • read_rnd_buffer_size:MySql的隨機讀緩衝區大小。當按任意順序讀取行時(例如,按照排序順序),將分配一個隨機讀緩存區。進行排序查詢時,MySql會首先掃描一遍該緩衝,以免磁盤搜索,提升查詢速度,若是須要排序大量數據,可適當調高該值。但MySql會爲每一個客戶鏈接發放該緩衝空間,因此應儘可能適當設置該值,以免內存開銷過大。
  • record_buffer:每一個進行一個順序掃描的線程爲其掃描的每張表分配這個大小的一個緩衝區。若是你作不少順序掃描,可能想要增長該值
  • thread_cache_size:保存當前沒有與鏈接關聯可是準備爲後面新的鏈接服務的線程,能夠快速響應鏈接的線程請求而無需建立新的
  • table_cache:相似於thread_cache_size,但用來緩存表文件,對InnoDB效果不大,主要用於MyISAM

升級硬件

Scale up,這個很少說了,根據MySQL是CPU密集型仍是I/O密集型,經過提高CPU和內存、使用SSD,都能顯著提高MySQL性能

讀寫分離

也是目前經常使用的優化,從庫讀主庫寫,通常不要採用雙主或多主引入不少複雜性,儘可能採用文中的其餘方案來提升性能。同時目前不少拆分的解決方案同時也兼顧考慮了讀寫分離

緩存

緩存能夠發生在這些層次:

  • MySQL內部:在系統調優參數介紹了相關設置
  • 數據訪問層:好比MyBatis針對SQL語句作緩存,而Hibernate能夠精確到單個記錄,這裏緩存的對象主要是持久化對象Persistence Object
  • 應用服務層:這裏能夠經過編程手段對緩存作到更精準的控制和更多的實現策略,這裏緩存的對象是數據傳輸對象Data Transfer Object
  • Web層:針對web頁面作緩存
  • 瀏覽器客戶端:用戶端的緩存

能夠根據實際狀況在一個層次或多個層次結合加入緩存。這裏重點介紹下服務層的緩存實現,目前主要有兩種方式:

  • 直寫式(Write Through):在數據寫入數據庫後,同時更新緩存,維持數據庫與緩存的一致性。這也是當前大多數應用緩存框架如Spring Cache的工做方式。這種實現很是簡單,同步好,但效率通常。
  • 回寫式(Write Back):當有數據要寫入數據庫時,只會更新緩存,而後異步批量的將緩存數據同步到數據庫上。這種實現比較複雜,須要較多的應用邏輯,同時可能會產生數據庫與緩存的不一樣步,但效率很是高。

表分區

MySQL在5.1版引入的分區是一種簡單的水平拆分,用戶須要在建表的時候加上分區參數,對應用是透明的無需修改代碼

對用戶來講,分區表是一個獨立的邏輯表,可是底層由多個物理子表組成,實現分區的代碼其實是經過對一組底層表的對象封裝,但對SQL層來講是一個徹底封裝底層的黑盒子。MySQL實現分區的方式也意味着索引也是按照分區的子表定義,沒有全局索引

用戶的SQL語句是須要針對分區表作優化,SQL條件中要帶上分區條件的列,從而使查詢定位到少許的分區上,不然就會掃描所有分區,能夠經過EXPLAIN PARTITIONS來查看某條SQL語句會落在那些分區上,從而進行SQL優化,以下圖5條記錄落在兩個分區上:

mysql> explain partitions select count(1) from user_partition where id in (1,2,3,4,5);

+----+-------------+----------------+------------+-------+---------------+---------+---------+------+------+--------------------------+

| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | Extra |

+----+-------------+----------------+------------+-------+---------------+---------+---------+------+------+--------------------------+

| 1 | SIMPLE | user_partition | p1,p4 | range | PRIMARY | PRIMARY | 8 | NULL | 5 | Using where; Using index |

+----+-------------+----------------+------------+-------+---------------+---------+---------+------+------+--------------------------+

1 row in set (0.00 sec)

分區的好處是:

  • 可讓單表存儲更多的數據
  • 分區表的數據更容易維護,能夠經過清楚整個分區批量刪除大量數據,也能夠增長新的分區來支持新插入的數據。另外,還能夠對一個獨立分區進行優化、檢查、修復等操做
  • 部分查詢可以從查詢條件肯定只落在少數分區上,速度會很快
  • 分區表的數據還能夠分佈在不一樣的物理設備上,從而搞笑利用多個硬件設備
  • 可使用分區表賴避免某些特殊瓶頸,例如InnoDB單個索引的互斥訪問、ext3文件系統的inode鎖競爭
  • 能夠備份和恢復單個分區

分區的限制和缺點:

  • 一個表最多隻能有1024個分區
  • 若是分區字段中有主鍵或者惟一索引的列,那麼全部主鍵列和惟一索引列都必須包含進來
  • 分區表沒法使用外鍵約束
  • NULL值會使分區過濾無效
  • 全部分區必須使用相同的存儲引擎

分區的類型:

  • RANGE分區:基於屬於一個給定連續區間的列值,把多行分配給分區
  • LIST分區:相似於按RANGE分區,區別在於LIST分區是基於列值匹配一個離散值集合中的某個值來進行選擇
  • HASH分區:基於用戶定義的表達式的返回值來進行選擇的分區,該表達式使用將要插入到表中的這些行的列值進行計算。這個函數能夠包含MySQL中有效的、產生非負整數值的任何表達式
  • KEY分區:相似於按HASH分區,區別在於KEY分區只支持計算一列或多列,且MySQL服務器提供其自身的哈希函數。必須有一列或多列包含整數值

分區適合的場景有:

  • 最適合的場景數據的時間序列性比較強,則能夠按時間來分區,以下所示: 
 
  1. CREATE TABLE members ( 
  2.     firstname VARCHAR(25) NOT NULL, 
  3.     lastname VARCHAR(25) NOT NULL, 
  4.     username VARCHAR(16) NOT NULL, 
  5.     email VARCHAR(35), 
  6.     joined DATE NOT NULL 
  7. PARTITION BY RANGE( YEAR(joined) ) ( 
  8.     PARTITION p0 VALUES LESS THAN (1960), 
  9.     PARTITION p1 VALUES LESS THAN (1970), 
  10.     PARTITION p2 VALUES LESS THAN (1980), 
  11.     PARTITION p3 VALUES LESS THAN (1990), 
  12.     PARTITION p4 VALUES LESS THAN MAXVALUE 
  13. ); 

查詢時加上時間範圍條件效率會很是高,同時對於不須要的歷史數據能很容的批量刪除。

  • 若是數據有明顯的熱點,並且除了這部分數據,其餘數據不多被訪問到,那麼能夠將熱點數據單獨放在一個分區,讓這個分區的數據可以有機會都緩存在內存中,查詢時只訪問一個很小的分區表,可以有效使用索引和緩存

另外MySQL有一種早期的簡單的分區實現 - 合併表(merge table),限制較多且缺少優化,不建議使用,應該用新的分區機制來替代

垂直拆分

垂直分庫是根據數據庫裏面的數據表的相關性進行拆分,好比:一個數據庫裏面既存在用戶數據,又存在訂單數據,那麼垂直拆分能夠把用戶數據放到用戶庫、把訂單數據放到訂單庫。垂直分表是對數據表進行垂直拆分的一種方式,常見的是把一個多字段的大表按經常使用字段和很是用字段進行拆分,每一個表裏面的數據記錄數通常狀況下是相同的,只是字段不同,使用主鍵關聯

好比原始的用戶表是:

垂直拆分後是:

垂直拆分的優勢是:

  • 可使得行數據變小,一個數據塊(Block)就能存放更多的數據,在查詢時就會減小I/O次數(每次查詢時讀取的Block 就少)
  • 能夠達到最大化利用Cache的目的,具體在垂直拆分的時候能夠將不常變的字段放一塊兒,將常常改變的放一塊兒
  • 數據維護簡單

缺點是:

  • 主鍵出現冗餘,須要管理冗餘列
  • 會引發錶鏈接JOIN操做(增長CPU開銷)能夠經過在業務服務器上進行join來減小數據庫壓力
  • 依然存在單表數據量過大的問題(須要水平拆分)
  • 事務處理複雜

水平拆分

概述

水平拆分是經過某種策略將數據分片來存儲,分庫內分表和分庫兩部分,每片數據會分散到不一樣的MySQL表或庫,達到分佈式的效果,可以支持很是大的數據量。前面的表分區本質上也是一種特殊的庫內分表

庫內分表,僅僅是單純的解決了單一表數據過大的問題,因爲沒有把表的數據分佈到不一樣的機器上,所以對於減輕MySQL服務器的壓力來講,並無太大的做用,你們仍是競爭同一個物理機上的IO、CPU、網絡,這個就要經過分庫來解決

前面垂直拆分的用戶表若是進行水平拆分,結果是:

實際狀況中每每會是垂直拆分和水平拆分的結合,即將Users_A_M和Users_N_Z再拆成Users和UserExtras,這樣一共四張表

水平拆分的優勢是:

  • 不存在單庫大數據和高併發的性能瓶頸
  • 應用端改造較少
  • 提升了系統的穩定性和負載能力

缺點是:

  • 分片事務一致性難以解決
  • 跨節點Join性能差,邏輯複雜
  • 數據屢次擴展難度跟維護量極大

分片原則

  • 能不分就不分,參考單表優化
  • 分片數量儘可能少,分片儘可能均勻分佈在多個數據結點上,由於一個查詢SQL跨分片越多,則整體性能越差,雖然要好於全部數據在一個分片的結果,只在必要的時候進行擴容,增長分片數量
  • 分片規則須要慎重選擇作好提早規劃,分片規則的選擇,須要考慮數據的增加模式,數據的訪問模式,分片關聯性問題,以及分片擴容問題,最近的分片策略爲範圍分片,枚舉分片,一致性Hash分片,這幾種分片都有利於擴容
  • 儘可能不要在一個事務中的SQL跨越多個分片,分佈式事務一直是個很差處理的問題
  • 查詢條件儘可能優化,儘可能避免Select * 的方式,大量數據結果集下,會消耗大量帶寬和CPU資源,查詢儘可能避免返回大量結果集,而且儘可能爲頻繁使用的查詢語句創建索引。
  • 經過數據冗餘和表分區賴下降跨庫Join的可能

這裏特別強調一下分片規則的選擇問題,若是某個表的數據有明顯的時間特徵,好比訂單、交易記錄等,則他們一般比較合適用時間範圍分片,由於具備時效性的數據,咱們每每關注其近期的數據,查詢條件中每每帶有時間字段進行過濾,比較好的方案是,當前活躍的數據,採用跨度比較短的時間段進行分片,而歷史性的數據,則採用比較長的跨度存儲。

整體上來講,分片的選擇是取決於最頻繁的查詢SQL的條件,由於不帶任何Where語句的查詢SQL,會遍歷全部的分片,性能相對最差,所以這種SQL越多,對系統的影響越大,因此咱們要儘可能避免這種SQL的產生。

解決方案

因爲水平拆分牽涉的邏輯比較複雜,當前也有了很多比較成熟的解決方案。這些方案分爲兩大類:客戶端架構和代理架構。

客戶端架構

經過修改數據訪問層,如JDBC、Data Source、MyBatis,經過配置來管理多個數據源,直連數據庫,並在模塊內完成數據的分片整合,通常以Jar包的方式呈現

這是一個客戶端架構的例子:

能夠看到分片的實現是和應用服務器在一塊兒的,經過修改Spring JDBC層來實現

客戶端架構的優勢是:

  • 應用直連數據庫,下降外圍系統依賴所帶來的宕機風險
  • 集成成本低,無需額外運維的組件

缺點是:

  • 限於只能在數據庫訪問層上作文章,擴展性通常,對於比較複雜的系統可能會力不從心
  • 將分片邏輯的壓力放在應用服務器上,形成額外風險

代理架構

經過獨立的中間件來統一管理全部數據源和數據分片整合,後端數據庫集羣對前端應用程序透明,須要獨立部署和運維代理組件

這是一個代理架構的例子:

代理組件爲了分流和防止單點,通常以集羣形式存在,同時可能須要Zookeeper之類的服務組件來管理

代理架構的優勢是:

  • 可以處理很是複雜的需求,不受數據庫訪問層原來實現的限制,擴展性強
  • 對於應用服務器透明且沒有增長任何額外負載

缺點是:

  • 需部署和運維獨立的代理中間件,成本高
  • 應用需通過代理來鏈接數據庫,網絡上多了一跳,性能有損失且有額外風險

各方案比較

  出品方 架構模型 支持數據庫 分庫 分表 讀寫分離 外部依賴 是否開源 實現語言 支持語言 最後更新 Github星數
MySQL Fabric MySQL官方 代理架構 MySQL python 無限制 4個月前 35
Cobar 阿里巴巴 代理架構 MySQL Java 無限制 兩年前 1287
Cobar Client 阿里巴巴 客戶端架構 MySQL Java Java 三年前 344
TDDL 淘寶 客戶端架構 無限制 Diamond 只開源部分 Java Java 未知 519
Atlas 奇虎360 代理架構 MySQL C 無限制 10個月前 1941
Heisenberg 百度熊照 代理架構 MySQL Java 無限制 2個月前 197
TribeDB 我的 代理架構 MySQL NodeJS 無限制 3個月前 126
ShardingJDBC 噹噹 客戶端架構 MySQL Java Java 當天 1144
Shark 我的 客戶端架構 MySQL Java Java 兩天前 84
KingShard 我的 代理架構 MySQL Golang 無限制 兩天前 1836
OneProxy 平民軟件 代理架構 MySQL 未知 無限制 未知 未知
MyCat 社區 代理架構 MySQL Java 無限制 兩天前 1270
Vitess Youtube 代理架構 MySQL Golang 無限制 當天 3636
Mixer 我的 代理架構 MySQL Golang 無限制 9個月前 472
JetPants Tumblr 客戶端架構 MySQL Ruby Ruby 10個月前 957
HibernateShard Hibernate 客戶端架構 無限制 Java Java 4年前 57
MybatisShard MakerSoft 客戶端架構 無限制 Java Java 11個月前 119
Gizzard Twitter 代理架構 無限制 Java 無限制 3年前 2087
 

如此多的方案,如何進行選擇?能夠按如下思路來考慮:

  1. 肯定是使用代理架構仍是客戶端架構。中小型規模或是比較簡單的場景傾向於選擇客戶端架構,複雜場景或大規模系統傾向選擇代理架構
  2. 具體功能是否知足,好比須要跨節點ORDER BY,那麼支持該功能的優先考慮
  3. 不考慮一年內沒有更新的產品,說明開發停滯,甚至無人維護和技術支持
  4. 最好按大公司->社區->小公司->我的這樣的出品方順序來選擇
  5. 選擇口碑較好的,好比github星數、使用者數量質量和使用者反饋
  6. 開源的優先,每每項目有特殊需求可能須要改動源代碼

按照上述思路,推薦如下選擇:

  • 客戶端架構
  • 代理架構

兼容MySQL且可水平擴展的數據庫

目前也有一些開源數據庫兼容MySQL協議,如:

  • TiDB
  • Cubrid

但其工業品質和MySQL尚有差距,且須要較大的運維投入,若是想將原始的MySQL遷移到可水平擴展的新數據庫中,能夠考慮一些雲數據庫:

  • 阿里雲PetaData
  • 阿里雲OceanBase
  • 騰訊雲DCDB

NoSQL

在MySQL上作Sharding是一種戴着鐐銬的跳舞,事實上不少大表自己對MySQL這種RDBMS的需求並不大,並不要求ACID,能夠考慮將這些表遷移到NoSQL,完全解決水平擴展問題,例如:

  • 日誌類、監控類、統計類數據
  • 非結構化或弱結構化數據
  • 對事務要求不強,且無太多關聯操做的數據

【編輯推薦】

  1. MySQL innodb引擎備份工具XtraBackup之一(Install)
  2. MySQL innodb引擎備份工具XtraBackup之二(數據庫全備)
  3. MySQL索引之彙集索引
  4. 小白學習mysql之高新能索引基礎篇
  5. GitHub 開源的 MySQL 在線更改 Schema 工具
相關文章
相關標籤/搜索