經常聽到有人說“數據表太大了,需要分表”,“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:字段與其他字段更新時間不同,可以嘗試分表