當MySQL單表記錄數過大時,增加、刪除、修改和查詢的性能將急劇下降,您可以參考以下步驟進行優化。
單表優化
除非你預計未來你的單表數據會不斷的持續上漲,否則不要一開始就考慮做拆分,拆分將增加邏輯、部署、操作和維護上的各種複雜性。一般以整數值為主的表在千萬級以下,以字符串為主的表在五百萬以下不是什麼大問題。
字段
索引
- 索引並不是越多越好,要根據查詢有針對性的創建,考慮在WHERE和ORDER BY命令上涉及的列建立索引,可根據EXPLAIN來查看是否用了索引還是全表掃描
- 儘量避免對where子句中的字段進行空值判斷,否則引擎將放棄索引並掃描整個表
- 具有稀疏值分佈的字段不適合索引,例如只有兩個值的“性別”
- 字符字段只建前綴索引
- 字符字段最好不要是主鍵
- 不用使用數據庫自帶的約束外鍵,由程序邏輯層面實現約束
- 使用多列索引時主意順序和查詢條件保持一致,刪除不必要的單列索引
查詢SQL
引擎
目前被廣泛使用的引擎是MyISAM和InnoDB:
MyISAM
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測試工具
具體的調優參數內容較多,詳情請參閱官方文檔,以下是一些常見重要的參數:
升級硬件
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 |
+----+-------------+----------------+------------+-------+---------------+---------+---------+------+------+--------------------------+
1row in set (0.00 sec)
分區的好處是:
- 可以讓單表存儲更多的數據
- 分區表的數據更容易維護,可以通過清楚整個分區批量刪除大量數據,也可以增加新的分區來支持新插入的數據。另外,還可以對一個獨立分區進行優化、檢查、修復等操作
- 部分查詢能夠從查詢條件確定只落在少數分區上,速度會很快
- 分區表的數據還可以分佈在不同的物理設備上,從而搞笑利用多個硬件設備
- 可以使用分區表賴避免某些特殊瓶頸,例如InnoDB單個索引的互斥訪問、ext3文件系統的inode鎖競爭
- 可以備份和恢復單個分區
分區的限制和缺點:
- 一個表最多隻能有1024個分區
- 如果分區字段中有主鍵或者唯一索引的列,那麼所有主鍵列和唯一索引列都必須包含進來
- 分區表無法使用外鍵約束
- NULL值會使分區過濾無效
- 所有分區必須使用相同的存儲引擎
分區的類型:
- RANGE分區:基於屬於一個給定連續區間的列值,把多行分配給分區
- LIST分區:類似於按RANGE分區,區別在於LIST分區是基於列值匹配一個離散值集合中的某個值來進行選擇
- HASH分區:基於用戶定義的表達式的返回值來進行選擇的分區,該表達式使用將要插入到表中的這些行的列值進行計算。這個函數可以包含MySQL中有效的、產生非負整數值的任何表達式
- KEY分區:類似於按HASH分區,區別在於KEY分區只支持計算一列或多列,且MySQL服務器提供其自身的哈希函數。必須有一列或多列包含整數值
分區適合的場景有:
- 最適合的場景數據的時間序列性比較強,則可以按時間來分區,如下所示:
CREATE TABLE members (
firstname VARCHAR(25) NOT NULL,
lastname VARCHAR(25) NOT NULL,
username VARCHAR(16) NOT NULL,
email VARCHAR(35),
joined DATE NOT NULL
)
PARTITION BY RANGE( YEAR(joined) )
(
PARTITION p0 VALUES LESS THAN (1960),
PARTITION p1 VALUES LESS THAN (1970),
PARTITION p2 VALUES LESS THAN (1980),
PARTITION p3 VALUES LESS THAN (1990),
PARTITION p4 VALUES LESS THAN MAXVALUE
);
查詢時加上時間範圍條件效率會非常高,同時對於不需要的歷史數據能很容的批量刪除。
- 如果數據有明顯的熱點,而且除了這部分數據,其他數據很少被訪問到,那麼可以將熱點數據單獨放在一個分區,讓這個分區的數據能夠有機會都緩存在內存中,查詢時只訪問一個很小的分區表,能夠有效使用索引和緩存
另外MySQL有一種早期的簡單的分區實現 - 合併表(merge table),限制較多且缺乏優化,不建議使用,應該用新的分區機制來替代
閱讀更多 IT大派 的文章