mysql分表詳解


經常聽到有人說“數據表太大了,需要分表”,“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" ;
\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 ;
}

生成的數據如圖:

仙士可博客

 

數據庫總條數預覽:

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

取出所有用戶,根據 用戶賬號進行取模,例如:

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM