zoukankan      html  css  js  c++  java
  • mysql分表详解

    本人混迹qq群2年多了,经常听到有人说“数据表太大了,需要分表”,“xxxx了,要分表”的言论,那么,到底为什么要分表?

    难道数据量大就要分表?

    mysql数据量对索引的影响

    本人mysql版本为5.7

    新增数据测试

    为了测试mysql索引查询是否和数据量有关,本人做了以下的测试准备:

    新建4个表article1,article2,article3,article4,article5 每个表分别插入20万,50万,100万,200万,1500万的数据,数据都是随机生成

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
        create table test.article1(
      id          int auto_increment comment 'id'
        primary key,
      user_id     int          not null comment '用户id',
      title       varchar(64)  not null comment '标题',
      add_time    datetime     null comment '新增时间',
      update_time int          null comment '更新时间',
      description varchar(255) null comment '简介',
      status      tinyint(1)   null comment '状态 1正常 0隐藏'
    )
      charset = utf8;
     
    create index article_title_index
      on test.article1 (title);

    生成数据脚本,使用easyswoole,多协程插入:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    <?php
    include "./vendor/autoload.php";
    EasySwooleEasySwooleCore::getInstance()->initialize();
    for ($i = 0; $i <= 2000; $i++) {//协程最多3000,创建1000个协程
        go(function () use ($i) {
            AppUtilityPoolMysqlPool::invoke(function (AppUtilityPoolMysqlPoolObject $mysqlObjectuse ($i) {
                for ($y = 0; $y <= 1000; $y++) {//每个协程插入100条数据
                    $data = [
                        'user_id'     => mt_rand(1, 2500),
                        'title'       => EasySwooleUtilityRandom::character(32),//随机生成32位字母的标题
                        'add_time'    => date('Y-m-d H:i:s', mt_rand(strtotime('2018-01-01'), strtotime('2019-01-01'))),//随机生成日期
                        'update_time' => mt_rand(strtotime('2018-01-01'), strtotime('2019-01-01')),//随机生成日期
                        'description' => getChar(mt_rand(8, 64)),//随机生成8-64位汉字,
                        'status'      => mt_rand(0, 1),
                    ];
                    $mysqlObject->insert('article2'$data);
                }
                echo "协程$i 插入完成 ";
            }, -1);
        });
    }
     
    function getChar($num)  // $num为生成汉字的数量
    {
        $b '';
        for ($i = 0; $i $num$i++) {
            // 使用chr()函数拼接双字节汉字,前一个chr()为高位字节,后一个为低位字节
            $a chr(mt_rand(0xB0, 0xD0)) . chr(mt_rand(0xA1, 0xF0));
            // 转码
            $b .= iconv('GB2312''UTF-8'$a);
        }
        return $b;
    }

    生成的数据如图:

    仙士可博客

    数据库总条数预览:

    1
    select (select count(1) from article1) as "1" , (select count(1) from article2) as "2", (select count(1) from article3) as "3", (select count(1) from article4) as "4", (select count(1) from article5) as "5";

    仙士可博客

    查询时间测试

    查询脚本

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    <?php
    /**
     * Created by PhpStorm.
     * User: tioncico
     * Date: 19-5-11
     * Time: 下午7:20
     */
    include "./vendor/autoload.php";
    EasySwooleEasySwooleCore::getInstance()->initialize();
    go(function () {
        /**
         * @var $db AppUtilityPoolMysqlPoolObject
         */
        $db = AppUtilityPoolMysqlPool::defer();
        $startTime = microtimeFloat();
        //查询1000次
        for ($i = 0; $i < 10000; $i++) {
            $str =EasySwooleUtilityRandom::character(32);//随机生成字符串,用于查询
            $data $db->where('title',$str)->getOne('article1');
        }
     
        echo "1耗时" . (microtimeFloat() - $startTime) . '秒'.PHP_EOL;
        $startTime = microtimeFloat();
        //查询1000次
        for ($i = 0; $i < 10000; $i++) {
            $str =EasySwooleUtilityRandom::character(32);//随机生成字符串,用于查询
            $data $db->where('title',$str)->getOne('article2');
        }
     
        echo "2耗时" . (microtimeFloat() - $startTime) . '秒'.PHP_EOL;
        $startTime = microtimeFloat();
        //查询1000次
        for ($i = 0; $i < 10000; $i++) {
            $str =EasySwooleUtilityRandom::character(32);//随机生成字符串,用于查询
            $data $db->where('title',$str)->getOne('article3');
        }
     
        echo "3耗时" . (microtimeFloat() - $startTime) . '秒'.PHP_EOL;
        $startTime = microtimeFloat();
        //查询1000次
        for ($i = 0; $i < 10000; $i++) {
            $str =EasySwooleUtilityRandom::character(32);//随机生成字符串,用于查询
            $data $db->where('title',$str)->getOne('article4');
        }
     
        echo "4耗时" . (microtimeFloat() - $startTime) . '秒'.PHP_EOL;
        $startTime = microtimeFloat();
        //查询1000次
        for ($i = 0; $i < 10000; $i++) {
            $str =EasySwooleUtilityRandom::character(32);//随机生成字符串,用于查询
            $data $db->where('title',$str)->getOne('article5');
        }
     
        echo "5耗时" . (microtimeFloat() - $startTime) . '秒'.PHP_EOL;
    });
    function microtimeFloat()
    {
        list($usec$sec) = explode(" ", microtime());
        return ((float)$usec + (float)$sec);
    }

    该脚本是一个实例脚本,在后面的其他测试中依旧使用该脚本,修改下字段和逻辑

    title全索引查询一条时间情况:(为了准确,本人运行了多次)

    仙士可博客仙士可博客仙士可博客

    可以看出,数据量在200万以下时,查询时间几乎没有差别,只是在数据量1400万时,查询1万次的时间增加了1秒

    注:本人在之前测试,和之后测试时,查询article5时时间大概是2.1-2.5秒左右,可能mysql有其他知识点本人未掌握,所以没法详细解释

    title全索引查询不限制条数时间情况:(为了准确,本人运行了多次)

    仙士可博客仙士可博客

    可以看出,在200万数据之前 查询时间并没有太大的差距,1400万有一点点的差距

    title like 左前缀 索引查询不限制条数时间情况:(为了准确,本人运行了多次)

    仙士可博客仙士可博客仙士可博客

    根据这次测试,我们可以发现

    1:mysql的查询和数据量的大小关系并不大(微乎其微)

    2:mysql只要是命中索引,不管数据量有多大,都会非常快(快的一批,由于本人比较懒,并且本人之前也测试过单表1.5亿速度一样很快,就懒得继续新增2亿测试数据了,太累)

    什么情况需要分表

    从上面的章节可以发现,数据量的多少和查询速度其实关系不是很大,那么为什么要分表呢?原因有以下几种:

    1:   单表 不涉及索引的操作太多,无法直接命中索引的

    2:模糊查找范围过大,无法直接命中索引的,例如日志表查时间区间

    3:单表数据量过大,操作繁忙的

    4:数据量过大,有大部分数据很少访问的(冷热数据)
    5:装逼,需要用分表装逼的

    分表优缺点

    在上面,我们已经知道了为什么要分表,分表该怎么分呢?

    首先,我们需要先搞懂分表的意义

    数据分表有着以下好处:

    1:分散表压力,使其响应速度提高

    2:数据降维,提升查询速度

    3:分冷热数据,更好管理,备份

    4:支持分布式部署数据库,将压力分担到其他服务器中

    同时,缺点如下:

    1:分表之后较难管理多表

    2:join表时可能需要join多个

    3:查询模糊数据时需要全部的表一起查

    所以,数据量不大时候,不建议分表。

    水平分表

    根据数据的不同规则作为一个分表条件,区分数据以数据之间的分表叫做水平分表

    水平分表是比较常见的分表方法,也是解决数据量大时候的分表方法,在水平分表中,也根据场景的不同而分表方法不同

    取模分表

    假设有个用户表(1000w用户)需要分表,那么我们可以根据该用户表的唯一标识(id ,用户账号)进行取模分表

    重新新建n个表。例如5个, user1,user2,user3....uesr5

    取出所有用户,根据 用户账号进行取模,例如:

    1
    2
    3
    4
    5
    6
    <?php
    $userAccount ='tioncico';
    $num =  (crc32($userAccount)%5);
    $tableName 'user'.($num+1);
    echo "{$userAccount}应该存储到{$tableName}表";
    //tioncico应该存储到user3表

     不建议使用id分表,因为一般情况下,我们是使用账号,或者其他唯一标识 来进行区分某个人的,如果你表设计像qq号一样,那完全可以将id命名为其他的字段,用于区分,自增id同样需要

    取模分表法会使数据尽量的均衡分布,压力均衡,非常适合于需要通过特定标识字段查找数据的表(会员表)

    冷热数据分表

    冷热数据大多数体现在跟时间有关的 日志表,订单表上面

    在冷热数据分表时,我们应该遵循以下几种分表规则

    1:数据冷热分表,需要注意冷热数据的界限

    例如,商城订单表,每天增加100万的订单,一年就会增加到3.6亿的订单数,而大多数情况下,用户只会查询近1-3个月的数据,我们可以

    通过订单时间进行分表,只需要按照月份进行分表即可

    2:通过取模分表,需要注意取模字段,

    垂直分表

    区分一条数据的不同字段,叫做垂直分表

    垂直分表其实我们在设计数据库时,可能已经是用到了的,比如会员金额表,关联会员表的userId,这个时候,其实就可以叫做是垂直分表
    把会员金额的字段分到了其他的表中(会员金额表)

    垂直分表较为简单,有以下几种分法:

    1:字段意义和表其他字段意义不同,可以尝试分表

    2:字段占用空间太大,不常用或只在特定情况使用,可以尝试分表

    3:字段与其他字段更新时间不同,可以尝试分表

    以上是本人对分表的一些理解,如果有错误或者补充,欢迎各位大神指点一二,本人感激不尽

     

  • 相关阅读:
    JSP 和Servlet 有有什么关系?
    转发(forward)和重定向(redirect)的区别?
    get和post请求的区别?
    软件的三大类型-单机类型、BS类型、CS类型
    Redis集群搭建
    Tomcat网站上的core和deployer的区别
    spring 事务处理
    mybatis ${}与#{}的区别
    Quartz--Spring 定时任务
    @JsonSerialize @JsonIgnoreProperties @JsonIgnore @JsonFormat
  • 原文地址:https://www.cnblogs.com/myJuly/p/12987250.html
Copyright © 2011-2022 走看看