为什么要分表和分区?
我们的数据库数据越来越大,随之而来的是单个表中数据太多。以至于查询书读变慢,而且由于表的锁机制导致应用操作也搜到严重影响,出现了数据库性能瓶颈。
mysql中有一种机制是表锁定和行锁定,是为了保证数据的完整性。表锁定表示你们都不能对这张表进行操作,必须等我对表操作完才行。行锁定也一样,别的sql必须等我对这条数据操作完了,才能对这条数据进行操作。当出现这种情况时,我们可以考虑分表或分区。
1、分表
什么是分表?
分表是将一个大表按照一定的规则分解成多张具有独立存储空间的实体表,每个表都对应三个文件,MYD数据文件,.MYI索引文件,.frm表结构文件。这些表可以分布在同一块磁盘上,也可以在不同的机器上。app读写的时候根据事先定义好的规则得到对应的表名,然后去操作它。
将单个数据库表进行拆分,拆分成多个数据表,然后用户访问的时候,根据一定的算法(如用hash的方式,也可以用求余(取模)的方式),让用户访问不同的表,这样数据分散到多个数据表中,减少了单个数据表的访问压力。提升了数据库访问性能。分表的目的就在于此,减小数据库的负担,缩短查询时间。
Mysql分表分为垂直切分和水平切分
垂直切分是指数据表列的拆分,把一张列比较多的表拆分为多张表
通常我们按以下原则进行垂直拆分:
把不常用的字段单独放在一张表;
把text,blob(binary large object,二进制大对象)等大字段拆分出来放在附表中;
经常组合查询的列放在一张表中;
垂直拆分更多时候就应该在数据表设计之初就执行的步骤,然后查询的时候用jion关键起来即可。
水平拆分是指数据表行的拆分,把一张的表的数据拆成多张表来存放。
水平拆分原则
通常情况下,我们使用hash、取模等方式来进行表的拆分
比如一张有400W的用户表users,为提高其查询效率我们把其分成4张表users1,users2,users3,users4
通过用ID取模的方法把数据分散到四张表内Id%4= [0,1,2,3]
然后查询,更新,删除也是通过取模的方法来查询
部分业务逻辑也可以通过地区,年份等字段来进行归档拆分;
进行拆分后的表,这时我们就要约束用户查询行为。比如我们是按年来进行拆分的,这个时候在页面设计上就约束用户必须要先选择年,然后才能进行查询。
分表的几种方式:
1)mysql集群
它并不是分表,但起到了和分表相同的作用。集群可分担数据库的操作次数,将任务分担到多台数据库上。集群可以读写分离,减少读写压力。从而提升数据库性能。
2)预先估计会出现大数据量并且访问频繁的表,将其分为若干个表
根据一定的算法(如用hash的方式,也可以用求余(取模)的方式)让用户访问不同的表。
例如论坛里面发表帖子的表,时间长了这张表肯定很大,几十万,几百万都有可能。 聊天室里面信息表,几十个人在一起一聊一个晚上,时间长了,这张表的数据肯定很大。像这样的情况很多。所以这种能预估出来的大数据量表,我们就事先分出个N个表,这个N是多少,根据实际情况而定。以聊天信息表为例:我们事先建100个这样的表,message_00,message_01,message_02..........message_98,message_99.然后根据用户的ID来判断这个用户的聊天信息放到哪张表里面,可以用hash的方式来获得,也可以用求余的方式来获得,方法很多。
或者可以设计每张表容纳的数据量是N条,那么如何判断某张表的数据是否容量已满呢?可以在程序段对于要新增数据的表,在插入前先做统计表记录数量的操作,当<N条数据,就直接插入,当已经到达阀值,可以在程序段新创建数据库表(或者已经事先创建好),再执行插入操作)。
3)利用merge存储引擎来实现分表
如果要把已有的大数据量表分开比较痛苦,最痛苦的事就是改代码,因为程序里面的sql语句已经写好了,用merge存储引擎来实现分表, 这种方法比较适合。
merge分表,分为主表和子表,主表类似于一个壳子,逻辑上封装了子表,实际上数据都是存储在子表中的。
我们可以通过主表插入和查询数据,如果清楚分表规律,也可以直接操作子表。
下面我们来实现一个简单的利用merge存储引擎来实现分表的演示:
创建一个完整表存储着所有的成员信息(表名为member)
mysql> drop database IF EXISTS test;
mysql> use test;
create table member(
id bigint auto_increment primary key,
name varchar(20),
sex tinyint not null default '0'
)engine=myisam default charset=utf8 auto_increment=1;
加入点数据:
mysql> insert into member(name,sex) values('tom1',1);
mysql> insert into member(name,sex) select name,sex from member;
第二条语句多执行几次就有了很多数据
mysql> select * from member;
+----+------+-----+
| id | name | sex |
+----+------+-----+
| 1 | tom1 | 1 |
| 2 | tom1 | 1 |
| 3 | tom1 | 1 |
| 4 | tom1 | 1 |
| 5 | tom1 | 1 |
| 6 | tom1 | 1 |
| 7 | tom1 | 1 |
| 8 | tom1 | 1 |
| 9 | tom1 | 1 |
| 10 | tom1 | 1 |
| 11 | tom1 | 1 |
| 12 | tom1 | 1 |
| 13 | tom1 | 1 |
| 14 | tom1 | 1 |
| 15 | tom1 | 1 |
| 16 | tom1 | 1 |
+----+------+-----+
下面我们进行分表,这里我们把member分两个表tb_member1,tb_member2
mysql> use test;
DROP table IF EXISTS tb_member1;
create table tb_member1(
id bigint primary key ,
name varchar(20),
sex tinyint not null default '0'
) ENGINE=MyISAM DEFAULT CHARSET=utf8 ;
DROP table IF EXISTS tb_member2;
create table tb_member2(
id bigint primary key,
name varchar(20),
sex tinyint not null default '0'
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
//创建tb_member2也可以用下面的语句 create table tb_member2 like tb_member1;
创建主表tb_member
DROP table IF EXISTS tb_member;
create table tb_member(
id bigint primary key ,
name varchar(20),
sex tinyint not null default '0'
) ENGINE=MERGE UNION=(tb_member1,tb_member2) INSERT_METHOD=LAST CHARSET=utf8 ;
注:INSERT_METHOD,此参数INSERT_METHOD = NO 表示该表不能做任何写入操作只作为查询使用,INSERT_METHOD = LAST表示插入到最后的一张表里面。INSERT_METHOD = first表示插入到第一张表里面。
查看一下tb_member表的结构:
mysql> desc tb_member;
mysql> desc tb_member;
+-------+-------------+------+-----+---------+-----------------------------------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-----------------------------------------+
| id | bigint(20) | NO | PRI | NULL | auto_increment |
| name | varchar(20) | YES | | NULL | |
| sex | tinyint(4) | NO | | 0 | |
+-------+-------------+------+-----+---------+------------------------------------------+
3 rows in set (0.00 sec)
注:查看子表与主表的字段定义要一致
接下来,我们把数据分到两个分表中去:
mysql> insert into tb_member1(id,name,sex) select id,name,sex from member where id%2=0;
mysql> insert into tb_member2(id,name,sex) select id,name,sex from member where id%2=1;
查看两个子表的数据:
mysql> select * from tb_member1;
+----+------+-----+
| id | name | sex |
+----+------+-----+
| 16 | tom1 | 1 |
| 14 | tom1 | 1 |
| 12 | tom1 | 1 |
| 10 | tom1 | 1 |
| 8 | tom1 | 1 |
| 6 | tom1 | 1 |
| 4 | tom1 | 1 |
| 2 | tom1 | 1 |
+----+------+-----+
8 rows in set (0.00 sec)
mysql> select * from tb_member2;
+----+------+-----+
| id | name | sex |
+----+------+-----+
| 3 | tom1 | 1 |
| 1 | tom1 | 1 |
| 5 | tom1 | 1 |
| 7 | tom1 | 1 |
| 9 | tom1 | 1 |
| 11 | tom1 | 1 |
| 13 | tom1 | 1 |
| 15 | tom1 | 1 |
+----+------+-----+
8 rows in set (0.00 sec)
查看一下主表的数据:
mysql> select * from tb_member;
+----+------+-----+
| id | name | sex |
+----+------+-----+
| 16 | tom1 | 1 |
| 14 | tom1 | 1 |
| 12 | tom1 | 1 |
| 10 | tom1 | 1 |
| 8 | tom1 | 1 |
| 6 | tom1 | 1 |
| 4 | tom1 | 1 |
| 2 | tom1 | 1 |
| 15 | tom1 | 1 |
| 13 | tom1 | 1 |
| 11 | tom1 | 1 |
| 9 | tom1 | 1 |
| 7 | tom1 | 1 |
| 5 | tom1 | 1 |
| 3 | tom1 | 1 |
| 1 | tom1 | 1 |
+----+------+-----+
16 rows in set (0.00 sec)
mysql> select * from tb_member where id=3;
+----+------+-----+
| id | name | sex |
+----+------+-----+
| 3 | tom1 | 1 |
+----+------+-----+
1 row in set (0.00 sec)
注意:总表只是一个外壳,存取数据发生在一个一个的子表里面。
注意:每个子表都有自已独立的相关表文件,而主表只是一个壳,并没有完整的相关表文件
[root@localhost ~]# ls -l /usr/local/mysql/data/test/tb_member*
-rw-r-----. 1 mysql mysql 8614 Sep 15 21:49 /usr/local/mysql/data/test/tb_member1.frm
-rw-r-----. 1 mysql mysql 320 Sep 16 00:02 /usr/local/mysql/data/test/tb_member1.MYD
-rw-r-----. 1 mysql mysql 2048 Sep 16 00:43 /usr/local/mysql/data/test/tb_member1.MYI
-rw-r-----. 1 mysql mysql 8614 Sep 15 21:50 /usr/local/mysql/data/test/tb_member2.frm
-rw-r-----. 1 mysql mysql 180 Sep 16 00:02 /usr/local/mysql/data/test/tb_member2.MYD
-rw-r-----. 1 mysql mysql 2048 Sep 16 00:43 /usr/local/mysql/data/test/tb_member2.MYI
-rw-r-----. 1 mysql mysql 8614 Sep 16 21:12 /usr/local/mysql/data/test/tb_member3.frm
-rw-r-----. 1 mysql mysql 0 Sep 16 21:12 /usr/local/mysql/data/test/tb_member3.MYD
-rw-r-----. 1 mysql mysql 1024 Sep 16 21:12 /usr/local/mysql/data/test/tb_member3.MYI
-rw-r-----. 1 mysql mysql 8614 Sep 16 21:14 /usr/local/mysql/data/test/tb_member.frm
-rw-r-----. 1 mysql mysql 53 Sep 16 21:14 /usr/local/mysql/data/test/tb_member.MRG
分表和分区(即分库的区别)
1、实现方式上
a) mysql的分表是真正的分表,一张表分成很多表后,每一个小表都是完整的一张表,都对应三个文件,一个.MYD数据文件,.MYI索引文件,.frm表结构文件。
b) 分区不一样,一张大表进行分区后,他还是一张表,不会变成二张表,但是他存放数据的区块变多了
2、数据处理上
a)分表后,数据都是存放在分表里,总表只是一个外壳,存取数据发生在一个一个的分表里面。
b)分区呢,不存在分表的概念,分区只不过把存放数据的文件分成了许多小块,分区后的表呢,还是一张表,数据处理还是由自己来完成。
3、提高性能上
a)分表后,单表的并发能力提高了,磁盘I/O性能也提高了。并发能力为什么提高了呢,因为查寻一次所花的时间变短了,如果出现高并发的话,总表可以根据不同的查询,将并发压力分到不同的小表里面。
b)mysql提出了分区的概念,主要是想突破磁盘I/O瓶颈,想提高磁盘的读写能力,来增加mysql性能。
在这一点上,分区和分表的测重点不同,分表重点是存取数据时,如何提高mysql并发能力上;而分区呢,如何突破磁盘的读写能力,从而达到提高mysql性能的目的。
4、实现的难易度上
a)分表的方法有很多,用merge来分表,是最简单的一种方式。这种方式跟分区难易度差不多,并且对程序代码来说可以做到透明的。如果是用其他分表方式就比分区麻烦了。
b)分区实现是比较简单的,建立分区表,根建平常的表没什么区别,并且对开代码端来说是透明的。
mysql分表和分区有什么联系?
1.都能提高mysql的性高,在高并发状态下都有一个良好的表现。
2.分表和分区不矛盾,可以相互配合的,对于那些大访问量,并且表数据比较多的表,我们可以采取分表和分区结合的方式,访问量不大,但是表数据很多的表,我们可以采取分区的方式等。
3.分表技术是比较麻烦的,需要手动去创建子表,app服务端读写时候需要计算子表名。采用merge好一些,但也要创建子表和配置子表间的union关系。
4.表分区相对于分表,操作方便,不需要创建子表。
记住mysql特性:有 表锁和行锁 ,使用 主键更新是行锁不会影响其他数据的更新提高并发性能,使用非主键更新则会造成表锁,影响并发性能。。