mysql分区分表对比测试过程

一,故事背景。


某一天,几位大哥找小Q说:小Q,最近我们要做数据分区和分表的测试,这个任务就交给你了。小Q内心是紧张的:mysql学习还没开始,分区分表的测试居然交给了我。不过,想着反正也没学过,趁着机会学学看。小Q就答应下来了。开始项目分区分表测试。

二,测试阶段。


明确了测试任务后,小Q就开始屁颠屁颠地准备测试阶段

  • 1,第一阶段。
    • 1)从DAO层的sql语句开始。测试分区和不分区之间的性能区别。比较两者的数据。
  • 2,第二阶段。
    • 1)由于第一阶段的测试并不怎么尽人意,没有看到网上所说的分区性能会更加好。为了弄明白为什么会这样,也就只能深入到mysql的层级,把sql语句一个个地执行。
  • 3,第三阶段。
    第二阶段的分区也并没有看到分区后的性能会更加好,而且还会出现分区之后有一些sql是分区比不分区的耗时会更加长,而这些sql是只在分区后的某一个区进行查询。这也是不怎么符合原理的地方啊。为了弄清这些情况。只能更加深入地了解。
    • 1) 弄清楚第二阶段的sql为什么会出现那种情况。为什么分区的数据库在一个区中的查询反而会比不分区更加慢。
    • 2)重新建立2100万的数据,然后这些数据分为3个区,重新测试分区与不分区的性能差别。
    • 3)对新建的2100W万的数据,分为3个表。测试分表和原始表以及分区的性能差别。
      在这三个阶段之后将会决定之后的因为是分区还是分表。

三,第一阶段的测试。


第一阶段是对DAO的测试,这个很好办啊。小Q之前有看过Junit的单元测试。写一写测试用例,把所有的DAO的测试一遍。把测试后的数据统计出来就可以啦。但是,事实并不像自己想的那样。

  • 1,System.currentTimeMillis()和System.nanoTime()的区别。
    • 开始统计每个DAO的用时,想当然的使用了System.currentTimeMillis()这个函数。后来在网上查了一下资料。发现原来这个函数是有问题。因为这个API函数的结果是依赖于系统的调用,在linux下和windows下是有区别的: Mac / Linux 有几乎 1 毫秒的分辨率,而 Windows 具有 50 毫秒的分辨率。而且这个函数是会有毫秒级的误差。因为要比较分区和不分区的区别。那需要很高的精度。所以就只能换用另一个API函数了。
    • 统计DAO的执行时间,就用了System.nanoTime()。这个函数也是有误差的,误差的范围是纳秒。和毫秒的转换单位是10的6次方。ns的误差的精度,这个也是可以接受的。
    • 后来才知道还有一个sql.timer()的函数可以很好地统计执行时间。不过在分表的时候,sql执行的时间没有显示出来。
  • 2,初始化数据库数据。
    • 第一阶段的2000万数据需要自己生成,从别的数据库中导入了800万的数据,然后自己在写个程序,把剩下的1200万数据插入。然后再生成后的2000万数据备份出来,再按时间进行分区。这时候,又显示了小Q的经验不足。他居然把这些数据库的2000万数据导出,然后再导入,再进行分表。其中还有好几次的硬盘不足,不断地在删文件,想想也心塞。最后好说歹说才把数据准备好,也把数据分区了。导出,备份,分区的这些操作差不多花了一天半的时间。
    • 时间就是金钱,这么可以这样地浪费时间呢。后来,小Q找到了一个效率还不错的方法。
      • 1)先把原始数据弄好。
      • 2)导出原始数据的表结构。然后倒入为新的表,然后执行分区的操作。网上说可以建立分区,然后可以加快插入的速度,事后再添加索引。小Q怕麻烦,然后就直接分区和索引一起做好。
      • 3)执行sql语句:insert into new_table from origin_table;然后就喝杯茶等一下。
        结果这方法只需要执行43min就把2000万的数据给搞定了。果然是选择并努力重要啊。
  • 3,测试结果。
    • 1)DAO层的测试结果并不准确。原因如下:mysql数据库会把查询后的结果缓存起来。如果多次测试就会出现数据有偏差的情况。如果测试一次,就不能代表平均的情况。所以需要进行第二阶段的测试。
    • 2)在DAO层测试,需要在DAO层的sql语句中增加sql_no_cache。这个代表着数据不会放入缓存中。

四,第二阶段的测试。


第二阶段的测试主要是针对于mysql层级的。把DAO层中的sql语句复制出来,然后在命令行中执行。

  • 1,测试步骤。
    • 1)链接mysql数据库,先输入set profiling = 1;这个语句可以理解为是一个开关,打开一个记录所有执行过的sql语句的开关。
    • 2)多次执行某一个sql语句。如果数据没有波动(有个极大的峰值),一般我都会执行5遍。如果数据有波动。我会执行10遍,然后把最大的峰值给记录下来,在把平均值记录下来。查看多次执行后的耗时语句是show profiles;。这个会把之前执行过的sql语句都记录下来。主要包括执行序号,执行时间,sql语句。
    • 3)如果有某一个极大峰值的sql语句,小Q会去找一下是什么原因。就会使用show profile for query sql_no,sql_no是执行过的sql语句。这条命令会告诉你这个sql语句执行的过程中每个阶段的耗时。然后把其中耗时最大的阶段记录下来。还有一个命令是show profile;这个默认是查看上一条执行过的sql语句的阶段。
  • 2,测试结果。
    • 1)在测试中发现大多数的sql语句执行过程中耗时比较长的过程是Sending data和statistics。
      Sending data:
      a)Mysql内部各个存储之间的复制数据的过程:如硬盘的寻道(在数据文件中找)
      b)把数据发给客户端的耗时
      其实是mysql使用索引完成查询结束后。mysql得到了一堆的行id,如果有的列不在索引中,mysql需要重新到“数据行”上将需要返回的数据读取出来返回给客户端。
      statistics:
      服务器计算统计去规划一个查询。如果一个线程长时间处于这个状态,这个服务器的磁盘可能在执行其他工作。
    • 2) 在对每个sql进行explain partitions时,突然发现有些sql索引的使用没有按照之前建立的索引那样进行下去。而是使用别的索引。这就很尴尬了。针对于这个去查了一下资料,发现mysql有自己统计方式来选择索引进行查询。关于如何指定索引或者避免某个索引:
      • 指定索引:select * from table force KEY(idx) where …
      • 避免索引:select * from table ignore KEY(idx) where …
    • 3)在进行结果分析时发现:
      • a)在进行全区扫描时,分区相对不分区是较慢。
      • b)在进行某个区扫描时,分区会比不分区的快。分区后扫描的数据量少了。时间短了。
      • c)在没有添加时间参数的查询,分区会比不分区慢。需要每个区都进行扫描。而且,每个分区都会原来的表索引是一样的。需要分开查询。
      • d)在跨区的查询时,分区相对不分区较慢。
    • 4)影响结果的可能因素:
      • a)扫描的结果集。分区后只在一个区里面进行扫描,所需时间会比较短。
      • b)分区前后使用的索引。
      • c)使用索引后返回的结果集。如使用索引后还需要排序等,排序的结果集。

五,第三阶段的测试。


第二阶段没有测出自己想要的效果啊。也在《高性能mysql》P265中看到对分区的评价是分区表不是什么“银弹”。大家如果对这句话有疑问,请自行翻翻书,或者等小Q的下一篇的博客出来吧。
既然第二阶段的结果不能满足,那第三阶段的测试就不可避免了。第三阶段的测试就是要进行分表的测试了。

  • 1,测试步骤。
    • 1)重新插入全新的2100万数据,3个区,然后和不分区的测试比较。
    • 2)时间分区: 3个月(3个区)每个月数据量在700万。会有15个模拟班级。每个班级的插入数据为140万数据。从mysql层进行测试。
    • 3)测试分表的数据。
      分为3个表,按班级id进行分表,从dao层进行测试。班级id分表:3张表,对15个班级进行分表。每张表大概700万数据
  • 2,测试结果:
    • 1)发现在新的一轮时间分区的测试中,测试结果会比第二阶段的结果稍微好一点。但是并未能满足需求。
    • 2)分表测试中会有比较大提升。
      • a)如果带有班级id进行查询,那么查询的速度会比不分表的快。
      • b)如果没有带班级id的查询,那么需要所有的表都进行扫描。查询速度会比不分表的慢。
      • c)分表后,在DAO层要进行选表的操作,根据班级id选择一个相应的物理表。所以这部分会有时间的损耗。所以刚开始测试时是每执行一个测试方法都重新执行一遍的,会影响到结果。

六,最终方案选择。


  • 1,经历了多轮的测试后,最后决定了选择分表的策略。但是在进行分表前需要考虑分表的注意事项。虽然在测试的过程中耗时很长,并且也遇到了不少的坑,但是小Q还是感觉收获了很多。

你可能感兴趣的:(mysql)