課程內(nèi)容
- mysql分區(qū)
- 水平切分
- 垂直切分
1. mysql分區(qū)
1.1 什么是分區(qū)?
mysql數(shù)據(jù)庫(kù)中的數(shù)據(jù)是以文件的形勢(shì)存在磁盤上的,默認(rèn)放在/mysql/data下面(可以通過(guò)my.cnf中的datadir來(lái)查看),一張表主要對(duì)應(yīng)著三個(gè)文件(8.0對(duì)應(yīng)兩個(gè),往期版本三個(gè))一個(gè)是frm存放表結(jié)構(gòu)的,一個(gè)是myd存放表數(shù)據(jù)的,一個(gè)是myi存表索引的(innodb同理)。
如果一張表的數(shù)據(jù)量太大的話,那么myd,myi就會(huì)變的很大,查找數(shù)據(jù)就會(huì)變的很慢,這個(gè)時(shí)候我們可以利用mysql的分區(qū)功能,在物理上將這一張表對(duì)應(yīng)的三個(gè)文件,分割成許多個(gè)小塊,這樣呢,我們查找一條數(shù)據(jù)時(shí),就不用全部查找了,只要知道這條數(shù)據(jù)在哪一塊,然后在那一塊找就行了。如果表的數(shù)據(jù)太大,可能一個(gè)磁盤放不下,這個(gè)時(shí)候,我們可以把數(shù)據(jù)分配到不同的磁盤里面去。
表分區(qū),是指根據(jù)一定規(guī)則,將數(shù)據(jù)庫(kù)中的一張表分解成多個(gè)更小的,容易管理的部分。從邏輯上看,只有一張表,但是底層卻是由多個(gè)物理分區(qū)組成。
表分區(qū)的優(yōu)點(diǎn)
(1)、與單個(gè)磁盤或文件系統(tǒng)分區(qū)相比,可以存儲(chǔ)更多的數(shù)據(jù)。
(2)、對(duì)于那些已經(jīng)失去保存意義的數(shù)據(jù),通??梢酝ㄟ^(guò)刪除與那些數(shù)據(jù)有關(guān)的分區(qū),很容易地刪除那些數(shù)據(jù)。相反地,在某些情況下,添加新數(shù)據(jù)的過(guò)程又可以通過(guò)為那些新數(shù)據(jù)專門增加一個(gè)新的分區(qū),來(lái)很方便地實(shí)現(xiàn)。
(3)、一些查詢可以得到極大的優(yōu)化,這主要是借助于滿足一個(gè)給定WHERE語(yǔ)句的數(shù)據(jù)可以只保存在一個(gè)或多個(gè)分區(qū)內(nèi),這樣在查找時(shí)就不用查找其他剩余的分區(qū)。因?yàn)榉謪^(qū)可以在創(chuàng)建了分區(qū)表后進(jìn)行修改,所以在第一次配置分區(qū)方案時(shí)還不曾這么做時(shí),可以重新組織數(shù)據(jù),來(lái)提高那些常用查詢的效率。
(4)、涉及到例如SUM()和COUNT()這樣聚合函數(shù)的查詢,可以很容易地進(jìn)行并行處理。這種查詢的一個(gè)簡(jiǎn)單例子如 “SELECT salesperson_id, COUNT (orders) as order_total FROM sales GROUP BY salesperson_id;”。通過(guò)“并行”,這意味著該查詢可以在每個(gè)分區(qū)上同時(shí)進(jìn)行,最終結(jié)果只需通過(guò)總計(jì)所有分區(qū)得到的結(jié)果。
(5)、通過(guò)跨多個(gè)磁盤來(lái)分散數(shù)據(jù)查詢,來(lái)獲得更大的查詢吞吐量。
表分區(qū)的限制
(1)、一個(gè)表最多只能有1024個(gè)分區(qū)。
(2)、 MySQL5.1中,分區(qū)表達(dá)式必須是整數(shù),或者返回整數(shù)的表達(dá)式。在MySQL5.5中提供了非整數(shù)表達(dá)式分區(qū)的支持。
(3)、如果分區(qū)字段中有主鍵或者唯一索引的列,那么多有主鍵列和唯一索引列都必須包含進(jìn)來(lái)。即:分區(qū)字段要么不包含主鍵或者索引列,要么包含全部主鍵和索引列。
(4)、分區(qū)表中無(wú)法使用外鍵約束。
(5)、MySQL的分區(qū)適用于一個(gè)表的所有數(shù)據(jù)和索引,不能只對(duì)表數(shù)據(jù)分區(qū)而不對(duì)索引分區(qū),也不能只對(duì)索引分區(qū)而不對(duì)表分區(qū),也不能只對(duì)表的一部分?jǐn)?shù)據(jù)分區(qū)。
1.2 分區(qū)類型
(1)、RANGE分區(qū):基于屬于一個(gè)給定連續(xù)區(qū)間的列值,把多行分配給分區(qū)。
(2)、LIST分區(qū):類似于按RANGE分區(qū),區(qū)別在于LIST分區(qū)是基于列值匹配一個(gè)離散值集合中的某個(gè)值來(lái)進(jìn)行選擇。
(3)、HASH分區(qū):基于用戶定義的表達(dá)式的返回值來(lái)進(jìn)行選擇的分區(qū),該表達(dá)式使用將要插入到表中的這些行的列值進(jìn)行計(jì)算。這個(gè)函數(shù)可以包含MySQL 中有效的、產(chǎn)生非負(fù)整數(shù)值的任何表達(dá)式。
(4)、KEY分區(qū):類似于按HASH分區(qū),區(qū)別在于KEY分區(qū)只支持計(jì)算一列或多列,且MySQL服務(wù)器提供其自身的哈希函數(shù)。必須有一列或多列包含整數(shù)值。
說(shuō)明:在MySQL5.1版本中,RANGE,LIST,HASH分區(qū)要求分區(qū)鍵必須是INT類型,或者通過(guò)表達(dá)式返回INT類型。但KEY分區(qū)的時(shí)候,可以使用其他類型的列(BLOB,TEXT類型除外)作為分區(qū)鍵。
可以通過(guò)以下命令來(lái)查看當(dāng)前數(shù)據(jù)庫(kù)是否啟用了分區(qū)功能:
SHOW GLOBAL VARIABLES LIKE '%partition%';
#有時(shí)候可能會(huì)有這么一種誤區(qū),只要啟用了分區(qū),數(shù)據(jù)庫(kù)就會(huì)運(yùn)行的更快。這個(gè)結(jié)論結(jié)論是存在很多問(wèn)題的,就經(jīng)驗(yàn)來(lái)看,分區(qū)可能會(huì)給某些SQL語(yǔ)句性能帶來(lái)提高,但是分區(qū)主要用于數(shù)據(jù)庫(kù)高可用性的管理。在OLTP應(yīng)用中,對(duì)于分區(qū)的使用應(yīng)該非常小心,總之,如果只是一味地使用分區(qū),而不理解分區(qū)是如何工作的,也不清楚你的應(yīng)用如何使用分區(qū),那么分區(qū)極有可能會(huì)對(duì)性能產(chǎn)生負(fù)面的影響。
1.3 range類型分區(qū)
range分區(qū)使用values less than 操作符來(lái)進(jìn)行定義, 把連續(xù)且不相互重疊的字段分配給分區(qū),命令如下。
create table emp(
`no` varchar(20) not null,
`name` varchar(20),
deptno int,
birthdate date,
salary int
)
partition by range(salary) (
partition p1 values less than(1000),
partition p2 values less than(5000),
partition p3 values less than(10000)
);
insert into emp values('001', 'shineyork', 10, '2019-10-10', 5000);
insert into emp values('002', 'keke', 20, '2019-10-10', 1500);
insert into emp values('003', 'a', 10, '2019-10-10', 10500);
insert into emp values('004', 'n', 20, '2019-10-10', 1000);
insert into emp values('004', 'c', 20, '2019-10-10', 6000);
如上的方式就是把數(shù)據(jù)根據(jù)salary的value進(jìn)行劃分,區(qū)分到不同的表區(qū)中;而這其中partition by range的語(yǔ)法類似于“switch..case”的語(yǔ)法,如果salary小余5000就會(huì)在p1中。。。
而如上就是分區(qū)之后的數(shù)據(jù)表的結(jié)構(gòu),其中emp#P#p2.ibd是分區(qū)之后的數(shù)據(jù),而emp.frm就是表的結(jié)構(gòu)
mysql> insert into emp values('003', 'a', 10, '2019-10-10', 10500);
ERROR 1526 (HY000): Table has no partition for value 10500
而上面的insert中因?yàn)?0500不在這個(gè)范圍類所以會(huì)產(chǎn)生問(wèn)題,解決這個(gè)問(wèn)題的辦法就是在其后加入“partition p4 values less than maxvalue” 語(yǔ)法
create table emp(
`no` varchar(20) not null,
`name` varchar(20),
deptno int,
birthdate date,
salary int
)
partition by range(salary) (
partition p1 values less than(1000),
partition p2 values less than(5000),
partition p3 values less than(10000),
partition p4 values less than maxvalue
)
==如果我們要工資在5000-10000的數(shù)據(jù) 的時(shí)候需要注意(假設(shè)工資的最小單位是元)==
delete from emp where salary>=’1000′ and salary<= ‘4999’;
-- 不建議寫成以下方式,因?yàn)閮?yōu)化器會(huì)選擇搜索兩個(gè)分區(qū)(p1,p2),這是我們不希望看到的,因此對(duì)于啟用分區(qū),應(yīng)該根據(jù)分區(qū)的特性來(lái)編寫最優(yōu)的SQL
delete from emp where salary>=’1000′ and salary<= ‘5000’;
查詢分區(qū)之后某一個(gè)區(qū)中的數(shù)據(jù)
select * from emp partition (p3);
在range中也可以使用MySQL的系統(tǒng)函數(shù),比如根據(jù)年齡進(jìn)行區(qū)分
create table emp(
`no` varchar(20) not null,
`name` varchar(20),
deptno int,
birthdate date,
salary int
)
partition by range(year(birthdate)) (
partition p1 values less than(1990),
partition p2 values less than(2000),
partition p3 values less than(2010),
partition p4 values less than maxvalue
)
如果我們要?jiǎng)h除1990年出生的數(shù)據(jù) , 只需要?jiǎng)h除1990年所在的分區(qū)即可。
delete from emp where birthdate=’1990′
在進(jìn)行分區(qū)時(shí),如果出現(xiàn)“This partition function is not allowed”的錯(cuò)誤提示,則你可能使用了非支持函數(shù)。MySQL 5.6支持的partition函數(shù):http://dev.mysql.com/doc/refman/5.6/en/partitioning-limitations-functions.html
另外,需要注意一點(diǎn)的時(shí),對(duì)于RANGE分區(qū)的查詢,優(yōu)化器只能對(duì)YEAR(),TO_YEAR(),TO_SECONDS(),UNIX_TIMESTAMP()這類函數(shù)進(jìn)行優(yōu)化選擇,如果你使用了其他的函數(shù)或方法編寫了符合分區(qū)特性的SQL語(yǔ)句,就不一定能夠使用查詢優(yōu)化,所以再編寫SQL語(yǔ)句時(shí)盡量測(cè)試。
1.4 list類型
LIST分區(qū)和RANGE分區(qū)類似,區(qū)別在于LIST分區(qū)是基于列值匹配一個(gè)離散值集合中的某個(gè)值來(lái)進(jìn)行選擇,而非連續(xù)的。
LIST分區(qū)通過(guò)使用partition by list(expr)來(lái)實(shí)現(xiàn),其中“expr” 是某列值或一個(gè)基于某個(gè)列值、并返回一個(gè)整數(shù)值的表達(dá)式,然后通過(guò)values in(value_list)的方式來(lái)定義每個(gè)分區(qū),其中value_list是一個(gè)通過(guò)逗號(hào)分隔的整數(shù)列表。
create table emp(
`no` varchar(20) not null,
`name` varchar(20),
deptno int,
birthdate date,
salary int
)
partition by list(deptno) (
partition p1 values in (10,20,30),
partition p2 values in (1,2,3),
partition p4 values in (4,40)
);
insert into emp values('001', 'shineyork', 1, '2019-10-10', 5000);
insert into emp values('002', 'keke', 20, '2019-10-10', 1500);
insert into emp values('003', 'a', 10, '2019-10-10', 10500);
insert into emp values('004', 'n', 40, '2019-10-10', 1000);
insert into emp values('005', 'c', 6, '2019-10-10', 6000);
mysql> insert into emp values('005', 'c', 6, '2019-10-10', 6000);
ERROR 1526 (HY000): Table has no partition for value 6
這是因?yàn)?code>deptno列值6不能在用于定義分區(qū)p1, p2, p4的值列表中找到。要重點(diǎn)注意的是,LIST分區(qū)沒(méi)有類似如values less than maxvalue這樣的包含其他值在內(nèi)的定義。將要匹配的任何值都必須在值列表中找到。
LIST分區(qū)除了能和RANGE分區(qū)結(jié)合起來(lái)生成一個(gè)復(fù)合的子分區(qū),與HASH和KEY分區(qū)結(jié)合起來(lái)生成復(fù)合的子分區(qū)也是可能的。
1.5 hash類型
HASH分區(qū)是基于用戶定義的表達(dá)式的返回值來(lái)進(jìn)行選擇的分區(qū),該表達(dá)式使用將要插入到表中的這些行的列值進(jìn)行計(jì)算。這個(gè)函數(shù)可以包含MySQL中有效的、產(chǎn)生非負(fù)整數(shù)值的任何表達(dá)式。 HASH分區(qū)主要用來(lái)確保數(shù)據(jù)在預(yù)先確定數(shù)目的分區(qū)中平均分布。 在RANGE和LIST分區(qū)中,必須明確指定一個(gè)給定的列值或列值集合應(yīng)該保存在哪個(gè)分區(qū)中;而在HASH分區(qū)中,MySOL自動(dòng)完成這些工作,用戶所要做的只是基于將要被哈希的列值指定一個(gè)列值或表達(dá)式,以及指定被分區(qū)的表將要被分割成的分區(qū)數(shù)量。
要使用HASH分區(qū)來(lái)分割一個(gè)表,要在CREATE TABLE 語(yǔ)句上添加一個(gè)partition by hash(expr)子句,其中expr是一個(gè)返回一個(gè)整數(shù)的表達(dá)式。它可以僅僅是字段類型為MySQL 整型的一列的名字。此外,你很可能需要在后面再添加一個(gè)partitions num子句,其中num是一個(gè)非負(fù)的整數(shù),它表示表將要被分割成分區(qū)的數(shù)量,如果沒(méi)有包括一個(gè)partitions子句,那么分區(qū)的數(shù)量將默認(rèn)為1。
create table emp(
`no` varchar(20) not null,
`name` varchar(20),
deptno int,
birthdate date,
salary int
)
partition by hash(year(birthdate)) partitions 4;
1.6 linear hash類型
MySQL還支持線性哈希功能,它與常規(guī)哈希的區(qū)別在于,線性哈希功能使用的一個(gè)線性的2的冪(powers-of-two)運(yùn)算法則,而常規(guī)哈希使用的是求哈希函數(shù)值的模數(shù)。
線性哈希分區(qū)和常規(guī)哈希分區(qū)在語(yǔ)法上的唯一區(qū)別在于,在partition by 子句中添加linear關(guān)鍵字。
create table emp(
`no` varchar(20) not null,
`name` varchar(20),
deptno int,
birthdate date,
salary int
)
partition by linear hash(year(birthdate)) partitions 4;
按照線性哈希分區(qū)的優(yōu)點(diǎn)在于增加、刪除、合并和拆分分區(qū)將變得更加快捷,有利于處理含有極其大量(1000G)數(shù)據(jù)的表。它的缺點(diǎn)在于使用,與常規(guī)HASH分區(qū)得到的數(shù)據(jù)分布相比,各個(gè)分區(qū)間數(shù)據(jù)的分布不大可能均衡。
1.7 key類型
KEY分區(qū)和HASH分區(qū)相似,不同之處在于HASH分區(qū)使用用戶定義的函數(shù)進(jìn)行分區(qū),支持字符串HASH分區(qū),KEY分區(qū)使用MySQL數(shù)據(jù)庫(kù)提供的函數(shù)進(jìn)行分區(qū),這些函數(shù)基于與PASSWORD()一樣的運(yùn)算法則。
create table emp(
`no` varchar(20) not null,
`name` varchar(20),
deptno int,
birthdate date,
salary int
)
partition by key(year(birthdate)) partitions 4;
在KEY分區(qū)中使用關(guān)鍵字LINEAR和在HASH分區(qū)中使用具有同樣的作用,分區(qū)的編號(hào)是通過(guò)2的冪(powers-of-two)算法得到,而不是通過(guò)模數(shù)算法。
1.8 columns類型
在前面說(shuō)了RANGE、LIST、HASH和KEY這四種分區(qū)中,分區(qū)的條件是:數(shù)據(jù)必須為整形(interger),如果不是整形,那應(yīng)該需要通過(guò)函數(shù)將其轉(zhuǎn)化為整形,如YEAR(),TO_DAYS(),MONTH()等函數(shù)。MySQL5.5版本開(kāi)始支持COLUMNS分區(qū),可視為RANGE分區(qū)和LIST分區(qū)的一種進(jìn)化。COLUMNS分區(qū)可以直接使用非整形的數(shù)據(jù)進(jìn)行分區(qū),分區(qū)根據(jù)類型直接比較而得,不需要轉(zhuǎn)化為整形。此外,RANGE COLUMNS分區(qū)可以對(duì)多個(gè)列的值進(jìn)行分區(qū)。
COLUMNS分區(qū)支持以下的數(shù)據(jù)類型:
- 所有的整形類型,如INT、SMALLINT、TINYINT和BIGINT。而FLOAT和DECIMAL則不予支持。
- 日期類型,如DATE和DATETIME。其余的日期類型不予支持。
- 字符串類型,如CHAR、VARCHAR、BINARY和VARBINARY。而B(niǎo)LOB和TEXT類型不予支持。
對(duì)于日期類型的分區(qū),我們不再需要YEAR()和TO_DATS()函數(shù)了,而直接可以使用COLUMNS,如:
create table emp(
`key` VARCHAR (50),
`value` VARCHAR (50),
`create_time` datetime
)
partition by range columns (create_time)(
partition p0 values less than ('2017-01-01 00:00:00'),
partition p1 values less than ('2017-03-01 00:00:00')
);
同樣可以使用字符串分區(qū)
create table emp(
`key` VARCHAR (15),
` value` VARCHAR (50),
`create_time` datetime,
`city` VARCHAR (15)
)
partition by list columns (city)(
partition p0 values IN (
'shanghai',
'beijing',
'shenzhen'
),
partition p1 values IN ('hubei', 'henan', 'hunan')
);
對(duì)比range分區(qū)和list分區(qū),columns分區(qū)的亮點(diǎn)除了支持?jǐn)?shù)據(jù)類型增加之外,另外一大亮點(diǎn)是columns分區(qū)還支持多列分區(qū)。如:
create table emp(
` key` VARCHAR (15),
`value` VARCHAR (50),
`create_time` datetime,
`test` VARCHAR (1)
)
partition by range columns (create_time, test)(
partition p0 values less than (
'2017-01-01 00:00:00',
'yes'
),
partition p1 values less than ('2017-03-01 00:00:00', 'no'),
partition p2 values less than (maxvalue, maxvalue)
);
MySQL 5.5開(kāi)始支持COLUMNS分區(qū),對(duì)于之前的RANGE和LIST分區(qū),用戶可以用RANGE COLUMNS和LIST COLUMNS分區(qū)進(jìn)行很好的代替了。
1.9 mysql子分區(qū)
子分區(qū)(subparttitioning)是在分區(qū)的基礎(chǔ)上再進(jìn)行分區(qū),有時(shí)也稱這種分區(qū)為復(fù)合分區(qū)。MySQL數(shù)據(jù)庫(kù)允許在RANGE和LIST的分區(qū)上再進(jìn)行HASH或KEY的子分區(qū),如:
create table emp (a INT, b date) ENGINE = INNODB partition by range (YEAR(b)) subpartition BY HASH (to_days(b)) subpartitions 2 (
partition p0 values less than (1990),
partition p1 values less than (2000),
partition p2 values less than maxvalue
);
mysql > system ls - ln / DATA / mysql / 3306 / DATA / db total 592 - rw - r --
--
- 1 27 27 67 Feb 27 12 : 03 db.opt - rw - r --
--
- 1 27 27 8578 Feb 27 15 : 54 emp.frm - rw - r --
--
- 1 27 27 98304 Feb 27 15 : 54 emp #P#p0#SP#p0sp0.ibd
- rw - r --
--
- 1 27 27 98304 Feb 27 15 : 54 emp #P#p0#SP#p0sp1.ibd
- rw - r --
--
- 1 27 27 98304 Feb 27 15 : 54 emp #P#p1#SP#p1sp0.ibd
- rw - r --
--
- 1 27 27 98304 Feb 27 15 : 54 emp #P#p1#SP#p1sp1.ibd
- rw - r --
--
- 1 27 27 98304 Feb 27 15 : 54 emp #P#p2#SP#p2sp0.ibd
- rw - r --
--
- 1 27 27 98304 Feb 27 15 : 54 emp #P#p2#SP#p2sp1.ibd
表emp先根據(jù)b列進(jìn)行了RANGE分區(qū),然后又進(jìn)行了一次HASH分區(qū),所以分區(qū)的數(shù)量應(yīng)該為(3×2=)6個(gè),這通過(guò)查看物理磁盤上的文件也可以得到證實(shí)。我們也可以通過(guò)使用subpartition語(yǔ)法來(lái)顯示地指出各個(gè)子分區(qū)的名字,例如對(duì)上述的ts表同樣可以這樣:
CREATE TABLE emp (a INT, b date) ENGINE = INNODB PARTITION BY RANGE (YEAR(b)) subpartition BY HASH (to_days(b))(
partition p0 values less than (1990)(
subpartition s0,
subpartition s1
),
partition p1 values less than (2000)(
subpartition s2,
subpartition s3
),
partition p2 values less than maxvalue (
subpartition s4,
subpartition s5
)
);
子分區(qū)的建立需要注意以下幾個(gè)問(wèn)題:
1、每個(gè)子分區(qū)的數(shù)量必須相同。
2、要在一個(gè)分區(qū)表的任何分區(qū)上使用subpartition來(lái)明確定義任何子分區(qū),就必須定義所有的子分區(qū)。
3、每個(gè)subpartition子句必須包括子分區(qū)的一個(gè)名字。
4、子分區(qū)的名字必須是唯一的。
子分區(qū)可以用于特別大的表,在多個(gè)磁盤分別分配數(shù)據(jù)和索引,用到不錯(cuò),語(yǔ)句這里就不介紹了,可以MySQL技術(shù)內(nèi)幕-Innodb存儲(chǔ)引擎書(shū)籍。
2.0 分區(qū)中的NULL值
MySQL數(shù)據(jù)庫(kù)允許對(duì)NULL值做分區(qū),但是處理的方法與其他數(shù)據(jù)庫(kù)可能完全不同。MySQL數(shù)據(jù)庫(kù)的分區(qū)總是視NULL值小于任何的一個(gè)非NULL值,這和MySQL數(shù)據(jù)庫(kù)中處理NULL值的ORDER BY操作是一樣的。因此對(duì)于不同的分區(qū)類型,MySQL數(shù)據(jù)庫(kù)對(duì)于NULL值的處理也是各不相同。
1)對(duì)于RANGE分區(qū),如果向分區(qū)列插入了NULL值,則MySQL數(shù)據(jù)庫(kù)會(huì)將該值放入最左邊的分區(qū)(p0)。
CREATE TABLE t_range (a INT, b INT) ENGINE = INNODB
partition BY RANGE (b)(
partition p0 values less than (10),
partition p1 values less than (20),
partition p2 values less than maxvalue
);
另外需要注意的是,如果刪除p0這個(gè)分區(qū),刪除的將是小于10的記錄,并且還有NULL值的記錄,這點(diǎn)非常重要。
2)對(duì)于LIST分區(qū),如果向分區(qū)列插入了NULL值,則必須顯示地指出哪個(gè)分區(qū)放入NULL值,否則會(huì)報(bào)錯(cuò)。
CREATE TABLE t_list (a INT, b INT) ENGINE = INNODB PARTITION BY list (b)(
partition p0 values in (1, 3, 5, 7, 9, NULL),
partition p1 values in (2, 4, 6, 8, 10)
);
3)對(duì)于HASH和KEY分區(qū),對(duì)于NULL值的處理方法和RANGE分區(qū)、LIST分區(qū)不一樣。任何分區(qū)函數(shù)都會(huì)將含有NULL值的記錄返回為0。
CREATE TABLE t_hash (a INT, b INT) ENGINE = INNODB PARTITION BY HASH (b) PARTITIONS 2;
2.分區(qū)和性能
分區(qū)真的會(huì)加快數(shù)據(jù)庫(kù)的查詢嗎?實(shí)際上可能根本感覺(jué)不到查詢速度的提升,甚至?xí)l(fā)現(xiàn)查詢速度急劇下降,因此在合理使用分區(qū)之前,必須了解分區(qū)的使用環(huán)境。
數(shù)據(jù)庫(kù)的應(yīng)用分為兩類:一類是OLTP(在線事務(wù)處理),如Blog、電子商務(wù)、網(wǎng)絡(luò)游戲等;另一類是OLAP(在線分析處理),如數(shù)據(jù)倉(cāng)庫(kù)、數(shù)據(jù)集市。對(duì)于OLAP的應(yīng)用,分區(qū)的確是可以很好地提高查詢的性能,因?yàn)镺LAP應(yīng)用大多數(shù)查詢需要頻繁地掃描一張很大的表。假設(shè)有一張1億行的表,其中有一個(gè)時(shí)間戳屬性列。用戶的查詢需要從這張表中獲取一年的數(shù)據(jù)。如果按時(shí)間戳進(jìn)行分區(qū),則只需要掃描相應(yīng)的分區(qū)即可。這就是前面介紹的分區(qū)修剪技術(shù)。
對(duì)于OLTP的應(yīng)用,分區(qū)應(yīng)該非常小心。在這種應(yīng)用下,通常不可能會(huì)獲取一張大表10%的數(shù)據(jù),大部分都是通過(guò)索引返回幾條記錄即可。而根據(jù)B+樹(shù)索引的原理可知,對(duì)于一張大表,一般的B+樹(shù)需要2~3次的磁盤IO。因此B+樹(shù)可以很好地完成操作,不需要分區(qū)的幫助,并且設(shè)計(jì)不好的分區(qū)會(huì)帶來(lái)嚴(yán)重的性能問(wèn)題。
如很多開(kāi)發(fā)團(tuán)隊(duì)會(huì)認(rèn)為含有1000w行的表是一張非常巨大的表,所以他們往往會(huì)選擇采用分區(qū),如對(duì)主鍵做10個(gè)HASH的分區(qū),這樣每個(gè)分區(qū)就只有100w的數(shù)據(jù)了,因此查詢應(yīng)該變得更快了。如select * from table where pk=@pk。但是有沒(méi)有考慮過(guò)這樣一種情況:100w和1000w行的數(shù)據(jù)本身構(gòu)成的B+樹(shù)的層次都是一樣的,可能都是2~3層。那么上述走主鍵分區(qū)的索引并不會(huì)帶來(lái)性能的提高。好的,如果1000w的B+樹(shù)高度是3,100w的B+樹(shù)高度是2,那么上述按主鍵分區(qū)的索引可以避免1次IO,從而提高查詢的效率。這沒(méi)問(wèn)題,但是這張表只有主鍵索引,沒(méi)有任何其他的列需要查詢的。如果還有類似如下的SQL:select * from table where key=@key,這時(shí)對(duì)于key的查詢需要掃描所有的10個(gè)分區(qū),即使每個(gè)分區(qū)的查詢開(kāi)銷為2次IO,則一共需要20次IO。而對(duì)于原來(lái)單表的設(shè)計(jì),對(duì)于KEY的查詢只需要2~3次IO。
由以上結(jié)論可以看出,對(duì)于在OLTP場(chǎng)景中使用分區(qū)一定要特別小心了。
2.1 分區(qū)表在線DDL的支持(官網(wǎng)手冊(cè))
==☆☆☆分區(qū)表在線DDL的支持☆☆☆==:https://dev.mysql.com/doc/refman/5.7/en/online-ddl-partitioning.html
3. 分庫(kù)分表
作為一個(gè)數(shù)據(jù)庫(kù),作為數(shù)據(jù)庫(kù)中的一張表,隨著用戶的增多隨著時(shí)間的推移,總有一天,數(shù)據(jù)量會(huì)大到一個(gè)難以處理的地步。這時(shí)僅僅一張表的數(shù)據(jù)就已經(jīng)超過(guò)了千萬(wàn),無(wú)論是查詢還是修改,對(duì)于它的操作都會(huì)很耗時(shí),這時(shí)就需要進(jìn)行數(shù)據(jù)庫(kù)切分的操作了。對(duì)于一個(gè)大型的互聯(lián)網(wǎng)應(yīng)用,海量數(shù)據(jù)的存儲(chǔ)和訪問(wèn)成為了系統(tǒng)設(shè)計(jì)的瓶頸問(wèn)題,對(duì)于系統(tǒng)的穩(wěn)定性和擴(kuò)展性造成了極大的問(wèn)題。通過(guò)數(shù)據(jù)切分來(lái)提高網(wǎng)站性能,橫向擴(kuò)展數(shù)據(jù)層已經(jīng)成為架構(gòu)研發(fā)人員首選的方式。
- 水平切分?jǐn)?shù)據(jù)庫(kù):可以降低單臺(tái)機(jī)器的負(fù)載,同時(shí)最大限度的降低了宕機(jī)造成的損失;
- 負(fù)載均衡策略:可以降低單臺(tái)機(jī)器的訪問(wèn)負(fù)載,降低宕機(jī)的可能性;
- 集群方案:解決了數(shù)據(jù)庫(kù)宕機(jī)帶來(lái)的單點(diǎn)數(shù)據(jù)庫(kù)不能訪問(wèn)的問(wèn)題;
- 讀寫分離策略:最大限度的提高了應(yīng)用中讀取數(shù)據(jù)的速度和并發(fā)量;
3.1 mysql數(shù)據(jù)庫(kù)容量查詢
-- 1、統(tǒng)計(jì)每張表的數(shù)據(jù)量
SELECT
*
FROM
(
SELECT
TABLE_NAME,
concat(
round(
sum(DATA_LENGTH / 1024 / 1024),
7
)
) AS size
FROM
information_schema. TABLES
WHERE
table_schema = 'baobaot'
GROUP BY
table_name
) t;
-- 2、統(tǒng)計(jì)每張表的數(shù)據(jù)容量(數(shù)據(jù)量+索引)
select
table_name,
(
sum(DATA_LENGTH) + sum(INDEX_LENGTH)
) / 1024 / 1024 as size
from
information_schema. tables
where
table_schema = 'baobaot'
GROUP BY
table_name;
-- 3、查看數(shù)據(jù)庫(kù)數(shù)據(jù)量
SELECT concat(round(sum(DATA_LENGTH / 1024 / 1024 / 1024), 2), 'G')
FROM information_schema. tables
WHERE table_schema= 'baobaot' ;
問(wèn)題描述
1、單個(gè)表數(shù)據(jù)量越大,讀寫鎖,插入操作重新建立索引效率越低。
2、單個(gè)庫(kù)數(shù)據(jù)量太大(一個(gè)數(shù)據(jù)庫(kù)數(shù)據(jù)量到1T-2T就是極限)
3、單個(gè)數(shù)據(jù)庫(kù)服務(wù)器壓力過(guò)大
4、讀寫速度遇到瓶頸(并發(fā)量幾百)
何為切分?
簡(jiǎn)單來(lái)說(shuō),就是指通過(guò)某種特定的條件,將我們存放在同一個(gè)數(shù)據(jù)庫(kù)中的數(shù)據(jù)分散存放到多個(gè)數(shù)據(jù)庫(kù)(主機(jī))上面,以達(dá)到分散單臺(tái)設(shè)備負(fù)載的效果。 數(shù)據(jù)的切分(Sharding)根據(jù)其切分規(guī)則的類型,可以分為兩種切分模式。一種是按照不同的表(或者Schema)來(lái)切分到不同的數(shù)據(jù)庫(kù)(主機(jī))之上,這種切可以稱之為數(shù) 據(jù)的垂直(縱向)切分;另外一種則是根據(jù)表中的數(shù)據(jù)的邏輯關(guān)系,將同一個(gè)表中的數(shù)據(jù)按照某種條件拆分到多臺(tái)數(shù)據(jù)庫(kù)(主機(jī))上面,這種切分稱之為數(shù)據(jù)的水平(橫向)切分。
垂直切分的最大特點(diǎn)就是規(guī)則簡(jiǎn)單,實(shí)施也更為方便,尤其適合各業(yè)務(wù)之間的耦合度非常低,相互影響很小,業(yè)務(wù)邏輯非常清晰的系統(tǒng)。在這種系統(tǒng)中,可以很容易做到將不同業(yè)務(wù)模塊所使用的表分拆到不同的數(shù)據(jù)庫(kù)中。根據(jù)不同的表來(lái)進(jìn)行拆分,對(duì)應(yīng)用程序的影響也更小,拆分規(guī)則也會(huì)比較簡(jiǎn)單清晰。
水平切分于垂直切分相比,相對(duì)來(lái)說(shuō)稍微復(fù)雜一些。因?yàn)橐獙⑼粋€(gè)表中的不同數(shù)據(jù)拆分到不同的數(shù)據(jù)庫(kù)中,對(duì)于應(yīng)用程序來(lái)說(shuō),拆分規(guī)則本身就較根據(jù)表名來(lái)拆分更為復(fù)雜,后期的數(shù)據(jù)維護(hù)也會(huì)更為復(fù)雜一些。
3.2 水平切分
相對(duì)于垂直拆分,水平拆分不是將表做分類,而是按照某個(gè)字段的某種規(guī)則來(lái)分散到多個(gè)庫(kù)之中,每個(gè)表中包含一部分?jǐn)?shù)據(jù)。簡(jiǎn)單來(lái)說(shuō),我們可以將數(shù)據(jù)的水平切分理解為是按照數(shù)據(jù)行的切分,就是將表中的某些行切分到一個(gè)數(shù)據(jù)庫(kù),而另外的某些行又切分到其他的數(shù)據(jù)庫(kù)中。
拆分?jǐn)?shù)據(jù)就需要定義分片規(guī)則。關(guān)系型數(shù)據(jù)庫(kù)是行列的二維模型,拆分的第一原則是找到拆分維度。比如:從會(huì)員的角度來(lái)分析,商戶訂單交易類系統(tǒng)中查詢會(huì)員某天某月某個(gè)訂單,那么就需要按照會(huì)員結(jié)合日期來(lái)拆分,不同的數(shù)據(jù)按照會(huì)員 ID 做分組,這樣所有的數(shù)據(jù)查詢 join 都會(huì)在單庫(kù)內(nèi)解決;如果從商戶的角度來(lái)講,要查詢某個(gè)商家某天所有的訂單數(shù),就需要按照商戶 ID 做拆分;但是如果系統(tǒng)既想按會(huì)員拆分,又想按商家數(shù)據(jù),則會(huì)有一定的困難。如何找到合適的分片規(guī)則需要綜合考慮衡量。
幾種典型的分片規(guī)則包括:
- 按照用戶 ID 求模,將數(shù)據(jù)分散到不同的數(shù)據(jù)庫(kù),具有相同數(shù)據(jù)用戶的數(shù)據(jù)都被分散到一個(gè)庫(kù)中;
- 按照日期,將不同月甚至日的數(shù)據(jù)分散到不同的庫(kù)中;
- 按照某個(gè)特定的字段求摸,或者根據(jù)特定范圍段分散到不同的庫(kù)中。
既然數(shù)據(jù)做了拆分有優(yōu)點(diǎn)也就優(yōu)缺點(diǎn)。
優(yōu)點(diǎn):
- 拆分規(guī)則抽象好,join 操作基本可以數(shù)據(jù)庫(kù)做;
- 不存在單庫(kù)大數(shù)據(jù),高并發(fā)的性能瓶頸;
- 應(yīng)用端改造較少; 提高了系統(tǒng)的穩(wěn)定性跟負(fù)載能力。
缺點(diǎn):
- 拆分規(guī)則難以抽象;
- 分片事務(wù)一致性難以解決;
- 數(shù)據(jù)多次擴(kuò)展難度跟維護(hù)量極大;
- 跨庫(kù) join 性能較差。
3.3 垂直切分
一個(gè)數(shù)據(jù)庫(kù)由很多表的構(gòu)成,每個(gè)表對(duì)應(yīng)著不同的業(yè)務(wù),垂直切分是指按照業(yè)務(wù)將表進(jìn)行分類,分布到不同的數(shù)據(jù)庫(kù)上面,這樣也就將數(shù)據(jù)或者說(shuō)壓力分擔(dān)到不同的庫(kù)上面。
系統(tǒng)被切分成了,用戶,訂單交易,支付幾個(gè)模塊。 一個(gè)架構(gòu)設(shè)計(jì)較好的應(yīng)用系統(tǒng),其總體功能肯定是由很多個(gè)功能模塊所組成的,而每一個(gè)功能模塊所需要的數(shù)據(jù)對(duì)應(yīng)到數(shù)據(jù)庫(kù)中就是一個(gè)或者多個(gè)表。而在架構(gòu)設(shè)計(jì)中,各個(gè)功能模塊相互之間的交互點(diǎn)越統(tǒng)一越少,系統(tǒng)的耦合度就越低,系統(tǒng)各個(gè)模塊的維護(hù)性以及擴(kuò)展 性也就越好。這樣的系統(tǒng),實(shí)現(xiàn)數(shù)據(jù)的垂直切分也就越容易。但是往往系統(tǒng)之有些表難以做到完全的獨(dú)立,存在這擴(kuò)庫(kù) join 的情況,對(duì)于這類的表,就需要去做平衡,是數(shù)據(jù)庫(kù)讓步業(yè)務(wù),共用一個(gè)數(shù)據(jù)源,還是分成多個(gè)庫(kù),業(yè)務(wù)之間通過(guò)接口來(lái)做調(diào)用。在系統(tǒng)初期,數(shù)據(jù)量比較少,或者資源有限的情況下,會(huì)選擇共用數(shù) 據(jù)源,但是當(dāng)數(shù)據(jù)發(fā)展到了一定的規(guī)模,負(fù)載很大的情況,就需要必須去做分割。
一般來(lái)講業(yè)務(wù)存在著復(fù)雜 join 的場(chǎng)景是難以切分的,往往業(yè)務(wù)獨(dú)立的易于切分。如何切分,切分到何種程度是考驗(yàn)技術(shù)架構(gòu)的一個(gè)難題。
優(yōu)點(diǎn):
- 拆分后業(yè)務(wù)清晰,拆分規(guī)則明確;
- 系統(tǒng)之間整合或擴(kuò)展容易;
- 數(shù)據(jù)維護(hù)簡(jiǎn)單。
缺點(diǎn):
- 部分業(yè)務(wù)表無(wú)法 join,只能通過(guò)接口方式解決,提高了系統(tǒng)復(fù)雜度;
- 受每種業(yè)務(wù)不同的限制存在單庫(kù)性能瓶頸,不易數(shù)據(jù)擴(kuò)展跟性能提高;
- 事務(wù)處理復(fù)雜。
由于垂直切分是按照業(yè)務(wù)的分類將表分散到不同的庫(kù),所以有些業(yè)務(wù)表會(huì)過(guò)于龐大,存在單庫(kù)讀寫與存儲(chǔ)瓶頸,所以就需要水平拆分來(lái)做解決。
4.分庫(kù)分表的策略
4.1 水平切分
概念:以字段為依據(jù),按照一定策略(hash、range等),將一個(gè)表中的數(shù)據(jù)拆分到多個(gè)表中。 規(guī)則:hash,range,取模,list
結(jié)果:
每個(gè)表的結(jié)構(gòu)都一樣;
每個(gè)表的數(shù)據(jù)都不一樣,沒(méi)有交集;
所有表的并集是全量數(shù)據(jù);
場(chǎng)景:系統(tǒng)絕對(duì)并發(fā)量并沒(méi)有上來(lái),只是單表的數(shù)據(jù)量太多,影響了SQL效率,加重了CPU負(fù)擔(dān),以至于成為瓶頸。推薦:一次SQL查詢優(yōu)化原理分析
分析:表的數(shù)據(jù)量少了,單次SQL執(zhí)行效率高,自然減輕了CPU的負(fù)擔(dān)。
4.1.1 根據(jù)時(shí)間日期進(jìn)行拆分(范圍)
概念:根據(jù)年,月進(jìn)行切分,訂單表可以根據(jù)項(xiàng)目的業(yè)務(wù),一月一表或者是一年一表。用戶與商家查詢訂單時(shí)也可以先查詢最近一月的訂單

問(wèn)題:數(shù)據(jù)的分布不均勻,如果當(dāng)月用戶訂單特別多,那么那張表的性能就很低,反之,有一月的訂單數(shù)據(jù)很低。
4.1.2 根據(jù)用戶id進(jìn)行拆分
根據(jù)用戶id的話一般以取模的方式分表
- 訂單id的設(shè)計(jì)為(時(shí)間戳 + 用戶標(biāo)識(shí) + 隨機(jī)數(shù))
- 首先需要注意的是適當(dāng)?shù)倪M(jìn)行垂直分庫(kù);分為訂單基礎(chǔ)庫(kù)和訂單流程庫(kù) 同時(shí)為了解決商家和用戶的查詢問(wèn)題會(huì)建立以用戶和商家作為維度切分的訂單表-》對(duì)于商家來(lái)說(shuō)只存訂單主表就好了
- 在創(chuàng)建訂單之后就會(huì)根據(jù)user_id的后幾位指定數(shù)值作為標(biāo)識(shí)計(jì)算確定指定的庫(kù)和表 比如指定 user_id的后5為作為用戶標(biāo)識(shí) 關(guān)于分庫(kù)與分表的計(jì)算方式為
庫(kù) = (用戶標(biāo)識(shí) % ( 分表數(shù) * 分庫(kù)數(shù)))
表 = ((用戶標(biāo)識(shí) % ( 分表數(shù) * 分庫(kù)數(shù))) / 分表數(shù))(取整)
簡(jiǎn)單點(diǎn):
庫(kù) = (用戶標(biāo)識(shí) % 分庫(kù)數(shù))
表 = (用戶標(biāo)識(shí) / 分表數(shù))(取整) % 分庫(kù)數(shù)
比如通過(guò) / 得到的值為 25.6 那么對(duì)應(yīng)的表就是25

4.1.3 根據(jù)訂單id進(jìn)行拆分
根據(jù)訂單ID進(jìn)行取模查詢:但是查詢的時(shí)候如果用戶要查詢所有的訂單肯定是有問(wèn)題的,再比如商家查詢....肯定少不了全表的掃描;

這對(duì)于這個(gè)問(wèn)題可以采用異構(gòu)索引表的方式來(lái)解決;

所謂的異構(gòu)索引表實(shí)際就是一張實(shí)體表,而其次關(guān)于商家我們也可以進(jìn)行這樣的思路建立一個(gè)索引表處理
4.2 垂直切分
概念:以字段為依據(jù),按照字段的活躍性,將表中字段拆到不同的表(主表和擴(kuò)展表)中。
結(jié)果:
每個(gè)表的結(jié)構(gòu)都不一樣;
每個(gè)表的數(shù)據(jù)也不一樣,一般來(lái)說(shuō),每個(gè)表的字段至少有一列交集,一般是主鍵,用于關(guān)聯(lián)數(shù)據(jù);
所有表的并集是全量數(shù)據(jù);
場(chǎng)景:系統(tǒng)絕對(duì)并發(fā)量并沒(méi)有上來(lái),表的記錄并不多,但是字段多,并且熱點(diǎn)數(shù)據(jù)和非熱點(diǎn)數(shù)據(jù)在一起,單行數(shù)據(jù)所需的存儲(chǔ)空間較大。以至于數(shù)據(jù)庫(kù)緩存的數(shù)據(jù)行減少,查詢時(shí)會(huì)去讀磁盤數(shù)據(jù)產(chǎn)生大量的隨機(jī) 讀IO,產(chǎn)生IO瓶頸。
分析:可以用列表頁(yè)和詳情頁(yè)來(lái)幫助理解。垂直分表的拆分原則是將熱點(diǎn)數(shù)據(jù)(可能會(huì)冗余經(jīng)常一起查詢的數(shù)據(jù))放在一起作為主表,非熱點(diǎn)數(shù)據(jù)放在一起作為擴(kuò)展表。這樣更多的熱點(diǎn)數(shù)據(jù)就能被緩存下來(lái),進(jìn)而減少 了隨機(jī)讀IO。拆了之后,要想獲得全部數(shù)據(jù)就需要關(guān)聯(lián)兩個(gè)表來(lái)取數(shù)據(jù)。
但記住,千萬(wàn)別用join,因?yàn)閖oin不僅會(huì)增加CPU負(fù)擔(dān)并且會(huì)講兩個(gè)表耦合在一起(必須在一個(gè)數(shù)據(jù)庫(kù)實(shí)例上)。關(guān)聯(lián)數(shù)據(jù),應(yīng)該在業(yè)務(wù)Service層做文章,分別獲取主表和擴(kuò)展表數(shù)據(jù)然后用關(guān)聯(lián)字段關(guān)聯(lián)得到全部數(shù)據(jù)
4.2.1 垂直切分-商品表
用戶在瀏覽商品列表時(shí),只有對(duì)某商品感興趣時(shí)才會(huì)查看該商品的詳細(xì)描述。因此,商品信息中商品描述字段訪問(wèn)頻次較低,且該字段存儲(chǔ)占用空間較大,訪問(wèn)單個(gè)數(shù)據(jù)IO時(shí)間較長(zhǎng);商品信息中商品名稱、商品圖片、商品價(jià)格等其他字段數(shù)據(jù)訪問(wèn)頻次較高。由于這兩種數(shù)據(jù)的特性不一樣,因此他考慮將商品信息表拆分如下:將訪問(wèn)頻次低的商品描述信息單獨(dú)存放在一張表中,訪問(wèn)頻次較高的商品基本信息單獨(dú)放在一張表中。
商品表 (products)
| 字段 | 類型 | 描述 |
|---|---|---|
| id | int | 主鍵id |
| product_core | char(16) | 商品編碼 |
| title | varchar(30) | 商品名稱 |
| bar_code | varchar(50) | 國(guó)條碼 |
| one_category_id | int | 第一父類分類id |
| two_category_id | int | 第二父類分類id |
| three_category_id | int | 第三父類分類id |
| status | tinyint | 是否上下架 |
| audit_status | tinyint | 審核狀態(tài) |
| shop_id | int | 店鋪id |
| rating | double(8,2) | 商品平均分 |
| sold_count | int | 銷量 |
| revirew_count | int | 評(píng)價(jià)數(shù)量 |
| price | decimal(10,2) | sku最低價(jià)格 |
| image | varchar(200) | 商品主圖路徑 |
| created_at | datetime | 上架時(shí)間 |
| updated_at | datetime | 修改時(shí)間 |
| deleted_at | datetime | 下架時(shí)間 |
商品屬性表 (products_sku)
| 字段 | 類型 | 描述 |
|---|---|---|
| id | int | 主鍵id |
| title | varchar(30) | 商品sku名稱 |
| price | decimal(10,2) | sku屬性商品價(jià)格 |
| stock | int | 庫(kù)存 |
| product_id | int | 商品id |
| status | tinyint | 狀態(tài) |
| parameter | json | 參數(shù) |
| color_type | ENUM | 顏色類型 |
| created_at | datetime | 創(chuàng)建時(shí)間 |
| updated_at | datetime | 修改時(shí)間 |
| deleted_at | datetime | 刪除時(shí)間 |
商品詳情 (products_description) 與 商品屬性詳情表 (products_sku_description)
| 字段 | 類型 | 描述 |
|---|---|---|
| id | int | 主鍵id |
| product_id | int | 商品id |
| description | text | 商品詳情 |
| 字段 | 類型 | 描述 |
|---|---|---|
| id | int | 主鍵id |
| product_sku_id | int | 商品id |
| description | text | 商品詳情 |
4.2.2 水平切分-訂單表
思考:訂單表的查詢方式有哪些?
- 根據(jù)用戶id查詢 -> 涉及分頁(yè),查詢?nèi)坑唵?/li>
- 根據(jù)商家 -> 根據(jù)日期排序查詢
下面是訂單的結(jié)構(gòu):
訂單主表 (orders)
| 字段 | 類型 | 描述 |
|---|---|---|
| id | int | 訂單ID |
| customer_id | int | 下單人ID |
| shipping_user | varchar(10) | 收貨人姓名 |
| address | json | 收貨地址 |
| payment_method | tinyint | 支付方式:1現(xiàn)金,2余額,3網(wǎng)銀,4支付寶,5微信 |
| order_money | decimal(10,2) | 訂單金額 |
| district_money | decimal(10,2) | 優(yōu)惠金額 |
| shipping_money | decimal(10,2) | 運(yùn)費(fèi)金額 |
| payment_money | decimal(10,2) | 支付金額 |
| shipping_comp_name | varchar(10) | 快遞公司名稱 |
| shipping_sn | varchar(50) | 快遞單號(hào) |
| create_time | datetime | 下單時(shí)間 |
| shipping_time | datetime | 發(fā)貨時(shí)間 |
| pay_time | datetime | 支付時(shí)間 |
| order_status | tinyint | 訂單狀態(tài) |
| order_point | int | 訂單積分 |
| invoice_time | varchar(100) | 發(fā)票抬頭 |
| modified_time | datetime | 最后修改時(shí)間 |
| receive_time | datetime | 收貨時(shí)間 |
訂單詳情表 (order_item)
| 字段 | 類型 | 描述 |
|---|---|---|
| id | int | 訂單詳情表ID |
| order_id | varchar | 訂單表ID |
| product_id | int | 訂單商品ID |
| product_name | varchar(50) | 商品名稱 |
| product_cnt | int | 購(gòu)買商品數(shù)量 |
| product_price | decimal(10,2) | 購(gòu)買商品單價(jià) |
| average_cost | decimal(10,2) | 平均成本價(jià)格 |
| weight | float | 商品重量 |
| fee_money | decimal(10,2) | 優(yōu)惠分?jǐn)偨痤~ |
| w_id | int | 倉(cāng)庫(kù)ID |
| modified_time | datetime | 最后修改時(shí)間 |
首先關(guān)于訂單表如何選擇拆分?
- 根據(jù)訂單id分,
#如果是按照訂單ID取模的方式,比如按64取模,則可以保證主訂單數(shù)據(jù)以及相關(guān)的子訂單、訂單詳情數(shù)據(jù)平均落人到后端的64個(gè)數(shù)據(jù)庫(kù)中,原則上很好地滿足了數(shù)據(jù)盡可能平均拆分的原則。
- 根據(jù)user_id分
#通過(guò)采用買家用戶ID哈希取模的方式,比如是按64取模,技術(shù)上則也能保證訂單數(shù)據(jù)拆分到后端的64個(gè)數(shù)據(jù)庫(kù)中,但這里就會(huì)出現(xiàn)一個(gè)業(yè)務(wù)場(chǎng)景中帶來(lái)的一個(gè)問(wèn)題,就是如果有些賣家是交易量非常大的(這樣 的群體不在少數(shù)),那這些賣家產(chǎn)生的訂單數(shù)據(jù)量(特別是訂單詳情表的數(shù)據(jù)量)會(huì)比其他賣家要多出不少,也就是會(huì)出現(xiàn)數(shù)據(jù)不平均的現(xiàn)象,最終導(dǎo)致這些賣家的訂單數(shù)據(jù)所在的數(shù)據(jù)庫(kù)會(huì)相對(duì)其他數(shù)據(jù)庫(kù)提早進(jìn)人到數(shù)據(jù)歸檔(為了避免在線交易數(shù)據(jù)庫(kù)的數(shù)據(jù)的增大帶來(lái)數(shù)據(jù)庫(kù)性能問(wèn)題,淘寶將3個(gè)月內(nèi)的訂單數(shù)據(jù)保存進(jìn)在線交易數(shù)據(jù)庫(kù)中,超過(guò)3個(gè)月的訂單會(huì)歸檔到后端專門的歸檔數(shù)據(jù)庫(kù))。
4.3 根據(jù)業(yè)務(wù)進(jìn)行分庫(kù)
4.1 垂直分庫(kù)
概念:以表為依據(jù),按照業(yè)務(wù)歸屬不同,將不同的表拆分到不同的庫(kù)中。
結(jié)果:
每個(gè)庫(kù)的結(jié)構(gòu)都不一樣;
每個(gè)庫(kù)的數(shù)據(jù)也不一樣,沒(méi)有交集;
所有庫(kù)的并集是全量數(shù)據(jù);
場(chǎng)景:系統(tǒng)絕對(duì)并發(fā)量上來(lái)了,并且可以抽象出單獨(dú)的業(yè)務(wù)模塊。
分析:到這一步,基本上就可以服務(wù)化了。例如,隨著業(yè)務(wù)的發(fā)展一些公用的配置表、字典表等越來(lái)越多,這時(shí)可以將這些表拆到單獨(dú)的庫(kù)中,甚至可以服務(wù)化。再有,隨著業(yè)務(wù)的發(fā)展孵化出了一套業(yè)務(wù)模式,這時(shí)可 以將相關(guān)的表拆到單獨(dú)的庫(kù)中,甚至可以服務(wù)化。
4.2 水平分庫(kù)
概念:以字段為依據(jù),按照一定策略(hash、range等),將一個(gè)庫(kù)中的數(shù)據(jù)拆分到多個(gè)庫(kù)中。
結(jié)果:
每個(gè)庫(kù)的結(jié)構(gòu)都一樣;
每個(gè)庫(kù)的數(shù)據(jù)都不一樣,沒(méi)有交集;
所有庫(kù)的并集是全量數(shù)據(jù);
場(chǎng)景:系統(tǒng)絕對(duì)并發(fā)量上來(lái)了,分表難以根本上解決問(wèn)題,并且還沒(méi)有明顯的業(yè)務(wù)歸屬來(lái)垂直分庫(kù)。
分析:庫(kù)多了,io和cpu的壓力自然可以成倍緩解。
4.4 分表的原則:
- 數(shù)據(jù)盡可能平均拆分
- 盡可能的減少事務(wù)邊界所謂的事務(wù)邊界指單個(gè)SQL語(yǔ)句在后端數(shù)據(jù)庫(kù)上同時(shí)執(zhí)行的數(shù)量。因?yàn)槭聞?wù)邊界越大,系統(tǒng)的鎖沖突概率就越高,系統(tǒng)越難以擴(kuò)展,整體性能越低。(邊界是指一次性需要查詢的數(shù)據(jù)庫(kù)有最大值)
- 異構(gòu)索引表盡量降低全表掃描頻率 這個(gè)是針對(duì)假如數(shù)據(jù)是按訂單id進(jìn)行分拆,當(dāng)用戶想查看自己的訂單時(shí),針對(duì)這種場(chǎng)景,常采用異構(gòu)索引表來(lái)解決,即采用異步機(jī)制將原表內(nèi)的每一次創(chuàng)建或更新,都換另一個(gè)維度保存一份完整的數(shù)據(jù)表或索引表。本質(zhì)上這是互聯(lián)網(wǎng)公司很多時(shí)候采用的一個(gè)解決思路:拿空間換時(shí)間。