天天看點

mysql分表詳解

到底為什麼要分表?

難道資料量大就要分表?

mysql資料量對索引的影響

本人mysql版本為5.7

新增資料測試

為了測試mysql索引查詢是否和資料量有關,本人做了以下的測試準備:

建立4個表article1,article2,article3,article4,article5 每個表分别插入20萬,50萬,100萬,200萬,1500萬的資料,資料都是随機生成

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,多協程插入:

<?php

include "./vendor/autoload.php";

\EasySwoole\EasySwoole\Core::getInstance()->initialize();

for ($i = 0; $i <= 2000; $i++) {//協程最多3000,建立1000個協程

    go(function () use ($i) {

        \App\Utility\Pool\MysqlPool::invoke(function (\App\Utility\Pool\MysqlPoolObject $mysqlObject) use ($i) {

            for ($y = 0; $y <= 1000; $y++) {//每個協程插入100條資料

                $data = [

                    'user_id'     => mt_rand(1, 2500),

                    'title'       => \EasySwoole\Utility\Random::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 插入完成\n";

        }, -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;

}      

生成的資料如圖:

資料庫總條數預覽:

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";      

查詢時間測試

查詢腳本

<?php

/**

 * Created by PhpStorm.

 * User: tioncico

 * Date: 19-5-11

 * Time: 下午7:20

 */

include "./vendor/autoload.php";

\EasySwoole\EasySwoole\Core::getInstance()->initialize();

go(function () {

    /**

     * @var $db \App\Utility\Pool\MysqlPoolObject

     */

    $db = \App\Utility\Pool\MysqlPool::defer();

    $startTime = microtimeFloat();

    //查詢1000次

    for ($i = 0; $i < 10000; $i++) {

        $str =\EasySwoole\Utility\Random::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 =\EasySwoole\Utility\Random::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 =\EasySwoole\Utility\Random::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 =\EasySwoole\Utility\Random::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 =\EasySwoole\Utility\Random::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

取出所有使用者,根據 使用者賬号進行取模,例如:

<?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:字段與其他字段更新時間不同,可以嘗試分表