您的位置:首页 > 大数据

mysql数据库大数据量的查询优化和分页测试

2016-07-01 17:08 721 查看
http://blog.sina.com.cn/s/blog_438308750100im0b.html

有什么问题:yubaojian0616@163.com 于堡舰

 

我原来的公司是一家网络游戏公司,其中网站交易与游戏数据库结合通过ws实现的,但是交易记录存放在网站上,级别是千万级别的数据库是mysql数据库.

 

  可能有人会问mysql是否支持千万级数据库,还有既然已经到了这个数据量公司肯定不差,为什么要用mysql而不用oracle这里我做一下解答

  1. mysql绝对支持千万级数据库是可以肯定的,

  2. 为什么选择择mysql呢?

 1> 第一也是最主要的一条是mysql他能做到。

 2> 在第一点前提下以下的就不是太重要了,mysql相对操作简单,测试容易,配置优化也相对容易很多

 3> 我们这里的数据仅仅是为了记录交易保证交易是被记录的,对于查询的还是相对少只有管理后台操作中需要对数据库进行查询

 4> 数据结构简单,而且每条记录都非常小,因为查询速度不管和记录条数有关和数据文件大小也有直接关系.

 5> 我们采用的是大小表的解决办法,每天大概需要插入数据库好几百万条,这里可能还是有人怀疑,其实没问题,如果批量插入我测试的在普通的pc机子上带该一个线程并发我插入的是6千万条记录大概需要“JDBC插入6000W条数据用时:9999297ms”,小表保存最近插入的内容,把几天前的保存到大表中,这里我说的就是大表大概6-7千万条数据;

 

  带着这些疑问和求知欲望咱们来做一个测试,因为在那个时候我也不是dba不知道人家是怎么搞的能够做成这么大的数据量,我们平时叶总探讨一些相关的内容

  1.mysql的数据查询,大小字段要分开,这个还是有必要的,除非一点就是你查询的都是索引内容而不是表内容,比如只查询id等等

  2.查询速度和索引有很大关系也就是索引的大小直接影响你的查询效果,但是查询条件一定要建立索引,这点上注意的是索引字段不能太多,太多索引文件就会很大那样搜索只能变慢,

  3.查询指定的记录最好通过Id进行in查询来获得真实的数据.其实不是最好而是必须,也就是你应该先查询出复合的ID列表,通过in查询来获得数据

  我们来做一个测试ipdatas表:

  CREATE TABLE `ipdatas` (

   `id` INT(11) NOT NULL AUTO_INCREMENT,

   `uid` INT(8) NOT NULL DEFAULT '0',

   `ipaddress` VARCHAR(50) NOT NULL,

   `source` VARCHAR(255) DEFAULT NULL,

   `track` VARCHAR(255) DEFAULT NULL,

   `entrance` VARCHAR(255) DEFAULT NULL,

   `createdtime` DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00',

   `createddate` DATE NOT NULL DEFAULT '0000-00-00',

   PRIMARY KEY (`id`),

   KEY `uid` (`uid`)

  ) ENGINE=MYISAM AUTO_INCREMENT=67086110 DEFAULT CHARSET=utf8;

  这是我们做的广告联盟的推广ip数据记录表,由于我也不是mysql的DBA所以这里咱们仅仅是测试

  因为原来里面有大概7015291条数据

  这里我们通过jdbc的batch插入6000万条数据到此表当中“JDBC插入6000W条数据用时:9999297ms”;

  大概用了两个多小时,这里面我用的是batch大小大概在1w多每次提交,还有一点是每次提交的数据都很小,而且这里用的myisam数据表,因为我需要知道mysql数据库的大小以及索引数据的大小结果是

  ipdatas.MYD 3.99 GB (4,288,979,008 字节)

  ipdatas.MYI 1.28 GB (1,377,600,512 字节)

  这里面我要说的是如果真的是大数据如果时间需要索引还是最好改成数字字段,索引的大小和查询速度都比时间字段可观。

  步入正题:

  1.全表搜索

 返回结构是67015297条数据

   SELECT COUNT(id) FROM ipdatas;

   SELECT COUNT(uid) FROM ipdatas;

   SELECT COUNT(*) FROM ipdatas;

   首先这两个全表数据查询速度很快,mysql中包含数据字典应该保留了数据库中的最大条数

 查询索引条件

   SELECT COUNT(*) FROM ipdatas WHERE uid=1;   返回结果时间:2分31秒594

   SELECT COUNT(id) FROM ipdatas WHERE uid=1;  返回结果时间:1分29秒609

   SELECT COUNT(uid) FROM ipdatas WHERE uid=1; 返回结果时间:2分41秒813

   第二次查询都比较快因为mysql中是有缓存区的所以增大缓存区的大小可以解决很多查询的优化,真可谓缓存无处不在啊在程序开发中也是层层都是缓存

 查询数据

   第一条开始查询

   SELECT * FROM ipdatas ORDER BY id DESC LIMIT 1,10 ; 31毫秒

   SELECT * FROM ipdatas LIMIT 1,10 ; 15ms

  

   第10000条开始查询

   SELECT * FROM ipdatas ORDER BY id ASC LIMIT 10000,10 ; 266毫秒

   SELECT * FROM ipdatas LIMIT 10000,10 ; 16毫秒

   第500万条开始查询

   SELECT * FROM ipdatas LIMIT 5000000,10 ;11.312秒

   SELECT * FROM ipdatas ORDER BY id ASC LIMIT 5000000,10 ; 221.985秒

   这两条返回结果完全一样,也就是mysql默认机制就是id正序然而时间却大相径庭

   第5000万条开始查询

   SELECT * FROM ipdatas LIMIT 60000000,10 ;66.563秒 (对比下面的测试)

   SELECT * FROM ipdatas ORDER BY id ASC LIMIT 50000000,10; 1060.000秒

   SELECT * FROM ipdatas ORDER BY id DESC LIMIT 17015307,10; 434.937秒

   第三条和第二条结果一样只是排序的方式不同但是用时却相差不少,看来这点还是不如很多的商业数据库,像oracle和sqlserver等都是中间不成两边还是没问题,看来mysql是开始行越向后越慢,这里看来可以不排序的就不要排序了性能差距巨大,相差了20多倍

 查询数据返回ID列表

   第一条开始查

   select id from ipdatas order by id asc limit 1,10; 31ms

   SELECT id FROM ipdatas LIMIT 1,10 ; 0ms

  

   第10000条开始

   SELECT id FROM ipdatas ORDER BY id ASC LIMIT 10000,10; 68ms

   select id from ipdatas limit 10000,10;0ms

   第500万条开始查询

   SELECT id FROM ipdatas LIMIT 5000000,10; 1.750s

   SELECT id FROM ipdatas ORDER BY id ASC LIMIT 5000000,10;14.328s

   第6000万条记录开始查询

   SELECT id FROM ipdatas LIMIT 60000000,10; 116.406s

   SELECT id FROM ipdatas ORDER BY id ASC LIMIT 60000000,10; 136.391s

   select id from ipdatas limit 10000002,10; 29.032s

   select id from ipdatas limit 20000002,10; 24.594s

   select id from ipdatas limit 30000002,10; 24.812s 

   select id from ipdatas limit 40000002,10; 28.750s  84.719s

   select id from ipdatas limit 50000002,10; 30.797s  108.042s

   select id from ipdatas limit 60000002,10; 133.012s  122.328s

   select * from ipdatas limit 10000002,10; 27.328s

   select * from ipdatas limit 20000002,10; 15.188s

   select * from ipdatas limit 30000002,10; 45.218s

   select * from ipdatas limit 40000002,10; 49.250s   50.531s

   select * from ipdatas limit 50000002,10; 73.297s   56.781s

   select * from ipdatas limit 60000002,10; 67.891s   75.141s

   select id from ipdatas order by id asc limit 10000002,10; 29.438s

   select id from ipdatas order by id asc limit 20000002,10; 24.719s

   select id from ipdatas order by id asc limit 30000002,10; 25.969s

   select id from ipdatas order by id asc limit 40000002,10; 29.860d

   select id from ipdatas order by id asc limit 50000002,10; 32.844s

   select id from ipdatas order by id asc limit 60000002,10; 34.047s

   至于SELECT * ipdatas order by id asc 就不测试了 大概都在十几分钟左右

   可见通过SELECT id 不带排序的情况下差距不太大,加了排序差距巨大

   下面看看这条语句

   SELECT * FROM ipdatas WHERE id IN (10000,100000,500000,1000000,5000000,10000000,2000000,30000000,40000000,50000000,60000000,67015297);

   耗时0.094ms

   可见in在id上面的查询可以忽略不计毕竟是6000多万条记录,所以为什么很多lucene或solr搜索都返回id进行数据库重新获得数据就是因为这个,当然lucene/solr+mysql是一个不错的解决办法这个非常适合前端搜索技术,比如前端的分页搜索通过这个可以得到非常好的性能.还可以支持很好的分组搜索结果集,然后通过id获得数据记录的真实数据来显示效果真的不错,别说是千万级别就是上亿也没有问题,真是吐血推荐啊.

 

上面的内容还没有进行有条件的查询仅仅是一些关于orderby和limit的测试,请关注我的下一篇文件对于条件查询的1亿数据检索测试

本文为本人最近利用几个小时才分析总结出的原创文章,希望大家转载,但是要注明出处

http://blog.sina.com.cn/s/blog_438308750100im0e.html

有什么问题可以互相讨论:yubaojian0616@163.com 于堡舰

 

  上一篇文章我们测试一些order by查询和分页查询的一些基准性能,现在我们来分析一下条件索引查询的结果集的测试

 

现在我们继续进行一个测试相同的表结构插入1亿条数据这次用到的是Innodb表引擎,表名有些变化,这里为甚要新建一个表的很重要元素是原来的那张表是每个uid=1来做的索引,这次uid是1...10不等的数每种1千万条记录

CREATE TABLE `ipdata` (

   `id` int(11) NOT NULL AUTO_INCREMENT,

   `uid` int(8) NOT NULL DEFAULT '0',

   `ipaddress` varchar(50) NOT NULL,

   `source` varchar(255) DEFAULT NULL,

   `track` varchar(255) DEFAULT NULL,

   `entrance` varchar(255) DEFAULT NULL,

   `createdtime` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',

   `createddate` date NOT NULL DEFAULT '0000-00-00',

   PRIMARY KEY (`id`),

   KEY `uid` (`uid`)

} ENGINE=InnoDB AUTO_INCREMENT=100004857 DEFAULT CHARSET=utf8

我开启了Innodb的线程数为128,因为innodb是行级别锁定,并发处理能力很强我开启100线程每个线程大小为100万记录插入时间如下

JDBC插入100w条数据此线程用时:9300984ms

JDBC插入100w条数据此线程用时:9381203ms

JDBC插入100w条数据此线程用时:9412343ms

JDBC插入100w条数据此线程用时:9442046ms

JDBC插入100w条数据此线程用时:9449828ms

JDBC插入100w条数据此线程用时:9484703ms

JDBC插入100w条数据此线程用时:9528093ms

JDBC插入100w条数据此线程用时:9533359ms

JDBC插入100w条数据此线程用时:9534296ms

JDBC插入100w条数据此线程用时:9539718ms

JDBC插入100w条数据此线程用时:9541750ms

JDBC插入100w条数据此线程用时:9636406ms

JDBC插入100w条数据此线程用时:9695093ms

JDBC插入100w条数据此线程用时:9806890ms

JDBC插入100w条数据此线程用时:9895500ms

JDBC插入100w条数据此线程用时:9989750ms

JDBC插入100w条数据此线程用时:10012312ms

JDBC插入100w条数据此线程用时:10037250ms

JDBC插入100w条数据此线程用时:10092796ms

JDBC插入100w条数据此线程用时:11993187ms

JDBC插入100w条数据此线程用时:12033203ms

JDBC插入100w条数据此线程用时:12068453ms

JDBC插入100w条数据此线程用时:12133625ms

JDBC插入100w条数据此线程用时:12212953ms

JDBC插入100w条数据此线程用时:12253421ms

JDBC插入100w条数据此线程用时:12284968ms

JDBC插入100w条数据此线程用时:12296421ms

JDBC插入100w条数据此线程用时:12366828ms

JDBC插入100w条数据此线程用时:12388093ms

JDBC插入100w条数据此线程用时:12389656ms

JDBC插入100w条数据此线程用时:12396625ms

JDBC插入100w条数据此线程用时:12417921ms

JDBC插入100w条数据此线程用时:12431000ms

JDBC插入100w条数据此线程用时:12432875ms

JDBC插入100w条数据此线程用时:12434703ms

JDBC插入100w条数据此线程用时:12455218ms

JDBC插入100w条数据此线程用时:12457109ms

JDBC插入100w条数据此线程用时:12484218ms

JDBC插入100w条数据此线程用时:12518375ms

JDBC插入100w条数据此线程用时:12519015ms

JDBC插入100w条数据此线程用时:12521109ms

JDBC插入100w条数据此线程用时:12521515ms

JDBC插入100w条数据此线程用时:12537343ms

JDBC插入100w条数据此线程用时:12539421ms

JDBC插入100w条数据此线程用时:12544250ms

JDBC插入100w条数据此线程用时:12559234ms

JDBC插入100w条数据此线程用时:12567484ms

JDBC插入100w条数据此线程用时:12574109ms

JDBC插入100w条数据此线程用时:12579156ms

JDBC插入100w条数据此线程用时:12638046ms

JDBC插入100w条数据此线程用时:12693047ms

JDBC插入100w条数据此线程用时:12722906ms

JDBC插入100w条数据此线程用时:12728781ms

JDBC插入100w条数据此线程用时:12732546ms

JDBC插入100w条数据此线程用时:12748265ms

JDBC插入100w条数据此线程用时:12757421ms

JDBC插入100w条数据此线程用时:12761375ms

JDBC插入100w条数据此线程用时:12765312ms

JDBC插入100w条数据此线程用时:12788359ms

JDBC插入100w条数据此线程用时:12802765ms

JDBC插入100w条数据此线程用时:12810484ms

JDBC插入100w条数据此线程用时:12811062ms

JDBC插入100w条数据此线程用时:12811796ms

JDBC插入100w条数据此线程用时:12812843ms

JDBC插入100w条数据此线程用时:12829671ms

JDBC插入100w条数据此线程用时:12830296ms

JDBC插入100w条数据此线程用时:12840000ms

JDBC插入100w条数据此线程用时:12840890ms

JDBC插入100w条数据此线程用时:12850312ms

JDBC插入100w条数据此线程用时:12856671ms

JDBC插入100w条数据此线程用时:12858609ms

JDBC插入100w条数据此线程用时:12860125ms

JDBC插入100w条数据此线程用时:12861750ms

JDBC插入100w条数据此线程用时:12864125ms

JDBC插入100w条数据此线程用时:12875609ms

JDBC插入100w条数据此线程用时:12875781ms

JDBC插入100w条数据此线程用时:12900859ms

JDBC插入100w条数据此线程用时:12906812ms

JDBC插入100w条数据此线程用时:12909656ms

JDBC插入100w条数据此线程用时:12913375ms

JDBC插入100w条数据此线程用时:12915609ms

JDBC插入100w条数据此线程用时:12917562ms

JDBC插入100w条数据此线程用时:12918000ms

JDBC插入100w条数据此线程用时:12919468ms

JDBC插入100w条数据此线程用时:12922093ms

JDBC插入100w条数据此线程用时:12922843ms

JDBC插入100w条数据此线程用时:12924375ms

JDBC插入100w条数据此线程用时:12925734ms

JDBC插入100w条数据此线程用时:12925781ms

JDBC插入100w条数据此线程用时:12931140ms

JDBC插入100w条数据此线程用时:12934562ms

JDBC插入100w条数据此线程用时:12934828ms

JDBC插入100w条数据此线程用时:12935281ms

JDBC插入100w条数据此线程用时:12936953ms

JDBC插入100w条数据此线程用时:12937218ms

JDBC插入100w条数据此线程用时:12937406ms

JDBC插入100w条数据此线程用时:12937765ms

JDBC插入100w条数据此线程用时:12939125ms

JDBC插入100w条数据此线程用时:12940281ms

JDBC插入100w条数据此线程用时:12941828ms

大概一共用了2个多小时内容为1亿条数据mysql的innodb中文件大小为 11.7 GB (12,660,506,624 字节);

首先来看看in查询

SELECT * FROM ipdata WHERE id IN(112358,201023,100020,100001,10000,100000,1000000,10000000,100000000); 141ms

SELECT * FROM ipdata WHERE id IN(12345,123456,1234567,12345678,987654,789654,1236985,852963,9745621,78965412); 141ms

看来in的查询还算理想,

然后我们进行分页必要查询不排序

SELECT id FROM ipdata WHERE uid=1 LIMIT 1,10; 31ms;

SELECT id FROM ipdata WHERE uid=1 LIMIT 10,10; 0ms;

SELECT id FROM ipdata WHERE uid=1 LIMIT 100,10; 0ms;

SELECT id FROM ipdata WHERE uid=1 LIMIT 1000,10; 0ms;

SELECT id FROM ipdata WHERE uid=1 LIMIT 10000,10; 47ms;

SELECT id FROM ipdata WHERE uid=1 LIMIT 100000,10; 235ms;

SELECT id FROM ipdata WHERE uid=1 LIMIT 1000000,10; 1.438s;

SELECT id FROM ipdata WHERE uid=1 LIMIT 5000000,10; 5.422s;

SELECT id FROM ipdata WHERE uid=1 LIMIT 10000000,10; 9.562s; 无返回结果

SELECT id FROM ipdata WHERE uid=1 LIMIT 9999990,10; 10.953s;
符合上一篇的结论mysql越向后越慢,但是整体来说是可以接受的,毕竟分页到最后一页虽然用到了10秒钟,但是后台人员不可能到最后去看,第二呢,10秒后台人员也算可以接受级别;

分页排序查询

SELECT id FROM ipdata WHERE uid=1 ORDER BY id ASC LIMIT 1,10; 0ms;

SELECT id FROM ipdata WHERE uid=1 ORDER BY id ASC LIMIT 10,10; 0ms;

SELECT id FROM ipdata WHERE uid=1 ORDER BY id ASC LIMIT 100,10; 0ms;

SELECT id FROM ipdata WHERE uid=1 ORDER BY id ASC LIMIT 1000,10; 0ms;

SELECT id FROM ipdata WHERE uid=1 ORDER BY id ASC LIMIT 10000,10; 47ms;

SELECT id FROM ipdata WHERE uid=1 ORDER BY id ASC LIMIT 100000,10; 266ms;

SELECT id FROM ipdata WHERE uid=1 ORDER BY id ASC LIMIT 1000000,10; 1.594s;

SELECT id FROM ipdata WHERE uid=1 ORDER BY id ASC LIMIT 5000000,10; 5.625s;

SELECT id FROM ipdata WHERE uid=1 ORDER BY id DESC LIMIT 5000000,10; 11.235s;

SELECT id FROM ipdata WHERE uid=1 ORDER BY id ASC LIMIT 10000000,10; 11.562s 无返回结果

SELECT id FROM ipdata WHERE uid=1 ORDER BY ID ASC LIMIT 9999990,10; 11.719s;

SELECT id FROM ipdata WHERE uid=1 ORDER BY ID DESC LIMIT 9999990,10; 18.719s;
结论是如果单查找id,order by的时间比较可观,但是可见正序和倒序时间不同.

 

返回全部结果查询"*"

SELECT * FROM ipdata WHERE uid=1 ORDER BY id ASC LIMIT 1,10; 109ms;

SELECT * FROM ipdata WHERE uid=1 ORDER BY id ASC LIMIT 10,10; 0ms;

SELECT * FROM ipdata WHERE uid=1 ORDER BY id ASC LIMIT 100,10; 16ms;

SELECT * FROM ipdata WHERE uid=1 ORDER BY id ASC LIMIT 1000,10; 63ms;

SELECT * FROM ipdata WHERE uid=1 ORDER BY id ASC LIMIT 10000,10; 356ms;

SELECT * FROM ipdata WHERE uid=1 ORDER BY id ASC LIMIT 100000,10; 2.969s;

SELECT * FROM ipdata WHERE uid=1 ORDER BY id ASC LIMIT 1000000,10; 30.766s;

select id,uid,ipaddress,source,track,entrance,createdtime,createddate from ipdata WHERE uid=1 ORDER BY id ASC LIMIT 1000000,10; 29.953s;

...下面的就不测试了,已经难以接受了
结论SELECT id 要比SELECT *快了不少至少在大的结果面前;

 

结果count测试

SELECT COUNT(*) FROM ipdata WHERE uid=1; 12.281s;

SELECT COUNT(*) FROM ipdata WHERE uid=2; 12.250s;

....

SELECT COUNT(*) FROM ipdata WHERE uid=10; 11.453s;

count级别大概是10多秒左右返回都是1000万;

Count(id)测试

SELECT COUNT(id) FROM ipdata WHERE uid=1; 10.281s;

SELECT COUNT(id) FROM ipdata WHERE uid=2; 10.531s;

....

SELECT COUNT(id) FROM ipdata WHERE uid=10; 12.531s;

Count(id)这里我不知道是机器原因可能测试不是十分准确,总之相差不大,不知道是否mysql默认通过唯一主键来count,如果*和id差不多都方便我还是推荐id,呵呵

   这样我们可以通过SELECT id 来得到id列表,然后通过in来得到相应的记录,可见是可行的;还有在这次测试中我们通过uid这个属性来过滤掉了90%的结果集,如果根据95%过滤理想化可能还有点欠缺,但是根据80%过滤原则就不同了,至少这个索引还是理想的,过滤掉的内容看来mysql就可以算到千万级别的用时了。其实这里面的时间不代表真实时间,毕竟机器也是我们办公室一台pc电脑,数据也比较小,这里我只是有时间来测试一下千万条乃至上亿条数据的处理能力,到服务器上应该要比这个快很多,毕竟磁盘io差距大,而且cpu也有差距,

 
总结
 1.mysql千万级别数据肯定是没问题的,毕竟现在的流向web2.0网站大部分是mysql的

 2.合理分表也是必须的,主要涉及横向分表与纵向分表,如把大小字段分开,或者每100万条记录在一张表中等等,像上面的这个表可以考虑通过uid的范围分表,或者通过只建立索引表,去掉相对大的字段来处理.

 3.count()时间比较长,但是本身是可以缓存在数据库中或者缓存在程序中的,因为我们当时使用在后台所以第一页比较慢但是后面比较理想

 4.SELECT id 相对SELECT * 差距还是比较大的,可以通过上面的方法来使用SELECT id + SELECT * ... IN 查询来提高性能

 5.必要的索引是必须的,还是要尽量返回5%-20%的结果级别其中小于5%最理想;

 6.mysql分页的前面几页速度很快,越向后性能越差,可以考虑只带上一页,下一页不带页面跳转的方法,呵呵这个比较垃圾但是也算是个方案,只要在前后多查一条就能解决了.比如100,10 你就差99,12呵呵,这样看看前后是否有结果.

 7.前台还是要通过其他手段来处理,比如lucene/Solr+mysql结合返回翻页结果集,或者上面的分表

 8. 1亿的数据还在我们这里大家可以充分考虑搜索条件 我帮大家测试哈哈。

 

接下来我将要测试一些关于1亿+的用户数据表的解决方案,及大数据的搜索方案通过lucene/solr+mysql
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: