做爲傳統的關係型數據庫,MySQL因其體積小、速度快、整體擁有成本低受到中小企業的熱捧,可是對於大數據量(百萬級以上)的操做顯得有些力不從心,這裏我結合以前開發的一個web系統來介紹一下MySQL數據庫在千萬級數據量的狀況下如何優化提高查詢速度。node
該系統包括硬件系統和軟件系統,由中科院計算所開發的無線傳感器網絡負責實時數據的監測和回傳到MySQL數據庫,咱們開發的軟件系統負責對數據進行實時計算,可視化展現及異常事件報警監測。宮殿的溫溼度等數據都存儲在data表中,因爲業務須要,data表中舊的數據要求不能刪除,通過初步估算,一年的數據量大概爲1200萬條,以前的系統當數據量到達百萬級時查詢響應速度很慢,致使數據加載延遲很大,因此頗有必要進行數據庫的優化查詢,提高響應速度。web
結合故宮溫溼度監測系統EasiWeb 7.1的data表查詢,這裏主要從如下三個方面詳解MySQL的分區優化技術:數據庫
(1)EasiWeb 7.1系統data表基於分表、分區和索引的優化方案對比。安全
(2)EasiWeb 7.1系統中採用的優化方案及實施步驟網絡
(3)系統模擬產生1500萬數據的優化先後對比測試併發
針對故宮系統大數據量時提高響應速度及運行性能的問題,咱們團隊經過研究和論證,提出了三種方案:less
分表即將一個表結構分解爲多個子表,這些子表能夠同一個數據庫下,也能夠在不一樣的數據庫下,查詢的時候經過代碼控制,生成多條查詢語句,進行多項子表聯查,最後彙總結果,總體上的查詢結果與單表同樣,但平均相應速度更快。函數
實現方式高併發
採用merge分表,劃分的標準能夠選取時間(collectTime)做爲參數。主表相似於一個殼子,邏輯上封裝了子表,實際上數據都是存儲在子表中。咱們在每一年的1月1日建立一個子表data_20XX,而後將這些子表union起來構成一個主表。對於插入操做,最新的數據將會被插入到最後一個子表中;對於查詢操做,經過'data'主表查詢的時候,查詢引擎會根據查詢語句口控制選取要查詢的子表集合,實現等效查詢。性能
優缺點
優勢是merge分表能夠很方便得實現分表,在進行查詢的時候封裝了查詢過程,用戶編寫的代碼較少。
缺點是破壞了data表的結構,而且在新建子表的時候因爲定時器延遲,可能致使個別數據被錯誤的存儲。
2.2 data表分區存儲
原理解釋
分區把存放數據的文件分紅了許多小塊,存儲在磁盤中不一樣的區域,經過提高磁盤I/O能力來提高查詢速度。分區不會更改data表的結構,發生變化的是存儲方式。
實現方式
採用range分區,根據數據的時間字段(collectTime)實現分區存儲,以年份爲基準,不一樣的區域存儲的是不一樣年份的數據,能夠採用合併語句進行分區的合併,分區操做由MySQL暗箱完成,從用戶的角度看,data表不會改變,程序代碼無需更改。
優缺點
優勢是range分區實現方便,沒有破壞data表的結構,用戶無需更改dao層代碼和查詢方式。並且能夠提早預設分區,好比今年是2017年,用戶能夠將數據分區預設到2020年,方式靈活,便於擴充。
缺點是數據存儲依賴於分區的存儲磁盤,一旦磁盤損壞,則會形成數據的丟失。
2.3 data表更換爲Myisam搜索引擎
原理解釋
MySQL提供Myisam和InnoDB類型的搜索引擎,兩種搜索引擎側重點不一樣,能夠根據實際的須要搭配使用,以達到最優的相應效果。Myisam引擎能夠平均分佈I/O,得到更快的速度,InnoDB注重事務處理,適合高併發操做。
圖1 BTREE存儲結構
從圖中就能夠看出,B+Tree的內部結點不存儲數據,只存儲指針,而葉子結點則只存儲數據,不存儲指針。而且在其每一個葉子節點上增長了一個指向數據的指針。
MyISAM引擎的索引結構爲B+Tree,其中B+Tree的數據域存儲的內容爲實際數據的地址,也就是說它的索引和實際的數據是分開的,只不過是用索引指向了實際的數據,這種索引就是所謂的非彙集索引。
實現方式
修改data表的搜索引擎爲Myisam,其它數據表不作更改。
優缺點
Myisam優勢數據文件和索引文件能夠放置在不一樣的目錄,平均分佈I/O。缺點是不合適高併發操做,對事務處理(修改和刪除操做)的支持較差。
InnoDB優勢是提供了具備提交、回滾和崩潰恢復能力的事務安全,適合高併發操做的事務處理。缺點是處理效率相對Myisam較差而且會佔用更多的磁盤空間以保留數據和索引。
因爲data表主要涉及的是查詢和插入操做,提升速度是第一需求,因此能夠將data表的搜索引擎改成Myisam。
2.4 綜合比較
實現方式
分表後的數據實際存儲在子表中,總表只是一個外殼,merge分表編碼較少,更改了表的結構。
分區後的數據存儲的文件分紅了許多小塊,不更改表的結構。
提升性能
分表側重點是數據分表存儲,聯表查詢,注重提升MySQL的併發能力。
分區側重於突破磁盤的讀寫能力,從而達到提升MySQL性能的目的。
實現的難易度
採用merge分表與range分區兩種方式難易度差很少,若是是用其餘分表方式就比分區更爲複雜。
range分區實現是比較簡單的,創建分區的表和日常的表沒有什麼區。
兩種方式基本上對開發端代碼都是透明的。
故宮系統中的data表併發訪問量不大,因此經過分表提升訪問速度和併發效果不太顯著,並且還可能破壞原有表的結構。而分區能夠提升磁盤的讀寫能力,配合Myisam搜索引擎能夠很大幅度提高查詢速度。
因此咱們採用data表分區存儲+Myisam搜索引擎+創建索引的方式來優化數據庫的查詢
實施步驟
一、將data表的搜索引擎由InnoDB更改成MyISAM
ALTER TABLE `data` ENGINE=MyISAM;
二、創建以collectime、originAddr字段的BTREE索引
ALTER TABLE `data`
ADD INDEX `collectTime` (`collectTime`) USING BTREE ,
ADD INDEX `nodeId` (`originAddr`) USING BTREE ;
三、分區以前將collectTime由char類型改成datetime/date類型,才能進行分區操做(注:char類型不支持分區操做)。
ALTER TABLE `data`
MODIFY COLUMN `collectTime` datetime NOT NULL AFTER `ID`;
四、採用range分區能夠保證分區均勻
注:這裏因爲最先的數據從12年開始,咱們採用了半年分一個區,預分區到2021年1月份,實際分區結合具體狀況而定。
ALTER TABLE `data`
partition by range(to_days(collectTime))
(
partition P0 values less than (to_days('2012-01-01')),
partition P1 values less than (to_days('2012-07-01')),
partition P2 values less than (to_days('2013-01-01')),
partition P3 values less than (to_days('2013-07-01')),
partition P4 values less than (to_days('2014-01-01')),
partition P5 values less than (to_days('2014-07-01')),
partition P6 values less than (to_days('2015-01-01')),
partition P7 values less than (to_days('2015-07-01')),
partition P8 values less than (to_days('2016-01-01')),
partition P9 values less than (to_days('2016-07-01')),
partition P10 values less than (to_days('2017-01-01')),
partition P11 values less than (to_days('2017-07-01')),
partition P12 values less than (to_days('2018-01-01')),
partition P12 values less than (to_days('2018-07-01')),
partition P12 values less than (to_days('2019-01-01')),
partition P12 values less than (to_days('2019-07-01')),
partition P12 values less than (to_days('2020-01-01')),
partition P12 values less than (to_days('2020-07-01')),
partition P12 values less than (to_days('2021-01-01')),
)
五、分區狀況查詢
SELECT * FROM
INFORMATION_SCHEMA.partitions
WHERE
TABLE_SCHEMA = schema()
AND TABLE_NAME='data';
圖2 查看分區信息
相關操做簡介
一、因爲range分區函數沒法識別char型字段,因此要在分區以前將collectTime由char類型改成datetime類型,才能進行range分區操做。
二、採用range分區時,要用to_days(collectime)的分區方式,採用這種方式,在查詢的時候只會在相應的分區查找,而若是不加to_days(),在查詢的時候,會對全表進行掃描。
三、分區優化後,查詢速度提高主要體如今非跨區查詢的時候,當查詢條件均屬於一個區域時,數據庫能夠快速定位到所查分區,而不會掃描全表。
四、每次插入數據的時候,數據庫會斷定對應的collectTime屬於哪一個分區,從而存儲到對應的分區中,不會影響其它分區。
1.通過數十次的多條件跨區與不跨區查詢測試,相比沒有作優化的data表,優化後查詢速度提高90%以上。
二、不跨區域查詢響應速度<=0.5s,跨區查詢在第一次比較慢,但以後在翻頁查詢的時候,相應速度<=1s。
三、查詢的時候因爲計算機性能差別,因此一樣的查詢在不一樣的機器上查詢速度會有所不用,咱們採用的測試環境爲i7 4500U酷睿 2核 4線程處理器 8G RAM 普通硬盤。
表1 data表查詢對比測試結果
編號 |
測試條件 |
collectTime字段 |
優化前 查詢時間 |
優化後 查詢時間 |
1 |
NodeID,data1,data2,collectTime |
2017/1/19—2017/4/19 |
2.59s |
0.74s |
2 |
NodeID,data1,data2,collectTime |
2017/1/19—2017/7/19 |
16.64s |
0.96s |
3 |
NodeID,data1,data2,collectTime |
2016/8/19—2017/2/19 |
34.3s |
2.49s |
4 |
NodeID,data1,data2,collectTime |
2016/1/19—2017/1/19 |
46.52s |
2.50s |
5 |
NodeID,data1,data2,collectTime |
2015/2/19—2017/3/19 |
78.12s |
3.73s |
6 |
NodeID,data1,data2,collectTime |
2015/3/19—2017/4/19 |
250.33s |
4.42s |
7 |
NodeID,data1,data2,collectTime |
2015/1/19—2017/4/19 |
226.10s |
4.39s |
8 |
NodeID,data1,data2,collectTime |
2014/4/19—2017/4/19 |
410.22s |
5.50s |
9 |
NodeID,data1,data2,collectTime |
2014/2/19—2017/4/19 |
437.50s |
5.50s |
10 |
NodeID,data1,data2,collectTime |
2014/1/19—2017/4/19 |
558.05s |
5.70s |
11 |
NodeID,data1,data2,collectTime |
2012/4/19—2017/4/19 |
--(響應時間過長) |
8.70s |