做为传统的关系型数据库,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 |