在日常开发或维护中经常会遇到大表的情况,所谓的大表是指存储了百万级乃至千万级条记录的表。这样的表过于庞大,导致数据库在查询和插入的时候耗时太长,性能低下,如果涉及联合查询的情况,性能会更加糟糕。分表和表分区的目的就是减少数据库的负担,提高数据库的效率,通常点来讲就是提高表的增删改查效率。
分表是将一个大表按照一定的规则分解成多张的实体表
,我们可以称为子表
。对于myisam
引擎,每个表都对应三个文件,MYD数据文件,.MYI索引文件,.frm表结构文件。根据分表技术对海量数据的优化方式目前有2种方法。
把一个数据量很大的表,可以把不同的列划分到不同的子表中,子表的结构各不相同。垂直分割的理由一般是根据数据的活跃度进行分离,不同活跃的数据,处理方式是不同的。
【案例】
所以,在进行数据库结构设计的时候,就应该考虑分表,首先是纵向分表的处理。
(1)数据库引擎的选择
这样纵向分表后:首先存储引擎的使用不同,冷数据使用MyIsam 可以有更好的查询效果;活跃数据,可以使用Innodb ,可以有更好的更新速度。
(2)读写优化
其次,对冷数据进行更多的从库配置,因为更多的操作是查询,这样来加快查询速度。对热数据,可以相对有更多的主库的横向分表处理。
(3)热数据加缓存
其实,对于一些特殊的活跃数据,也可以考虑使用memcache、redis之类的缓存,等累计到一定量再去更新数据库。或者mongodb 一类的nosql 数据库,这里只是举例,就先不说这个。
根据一列或者多列的值把数据行放到多个独立的子表里,字表的结构相同。水平分表方式可以通过多个低配置主机整合起来,实现高性能。分表理由:保证单表的容量不会太大,从而来保证单表的查询等处理能力。
下面要介绍的“分表”主要指水平分表。
假如我有一张用户表user,有50W条数据,现在要拆成二张表user1和user2,每张表25W条数据。
(1)新建子表
mysql> CREATE TABLE IF NOT EXISTS `user1` (
-> `id` int(11) NOT NULL AUTO_INCREMENT,
-> `name` varchar(50) DEFAULT NULL,
-> `sex` int(1) NOT NULL DEFAULT '0',
-> PRIMARY KEY (`id`)
-> ) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
Query OK, 0 rows affected (0.05 sec)
mysql> CREATE TABLE IF NOT EXISTS `user2` (
-> `id` int(11) NOT NULL AUTO_INCREMENT,
-> `name` varchar(50) DEFAULT NULL,
-> `sex` int(1) NOT NULL DEFAULT '0',
-> PRIMARY KEY (`id`)
-> ) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
Query OK, 0 rows affected (0.01 sec)
(2)往子表里插入数据
INSERT INTO user1(user1.id,user1.name,user1.sex)SELECT (user.id,user.name,user.sex)FROM user where user.id <= 250000
INSERT INTO user2(user2.id,user2.name,user2.sex)SELECT (user.id,user.name,user.sex)FROM user where user.id > 250000
(3)建立merge表
mysql> CREATE TABLE IF NOT EXISTS `alluser` (
-> `id` int(11) NOT NULL AUTO_INCREMENT,
-> `name` varchar(50) DEFAULT NULL,
-> `sex` int(1) NOT NULL DEFAULT '0',
-> INDEX(id)
-> ) TYPE=MERGE UNION=(user1,user2) INSERT_METHOD=LAST AUTO_INCREMENT=1 ;
Query OK, 0 rows affected, 1 warning (0.00 sec)
(4)备份user表,然后删除它
(5)把这个alluser表的表名改成user
(1)一个 merge 表不能在整个表上维持 unique 约束。当你执行一个 insert,数据进入第一个或者最后一个 myisam 表(取决于 insert_method 选项的值)。mysql 确保唯一键值在那个 myisam 表里保持唯一,但不是跨集合里所有的表。
(2)当你创建一个 merge 表之时,没有检查去确保底层表的存在以及有相同的机构。当 merge 表被使用之时,mysql 检查每个被映射的表的记录长度是否相等,但这并不十分可靠。如果你从不相似的 myisam 表创建一个 merge 表,你非常有可能撞见奇怪的问题。
上面介绍的分表其实是对表结构进行了更改,对大表进行拆分。实际上我们在设计数据库表结构和业务逻辑的时候,就应该计算业务需求,提前做好分表,避免大表的出现。下面是基于业务层面的一些常用分表策略。下面以新闻发布系统
举例。下面分离出的子表可以位于一个数据实例上,如果位于不同的实例上那么就存在分库
了,也就多了表与库的映射这一步。
(1)按时间结构
类似:
article_201701
article_201702
article_201703
在这个系统中,主键是13位带毫秒的时间戳
(2)归档式
类似:
article_old
article_new
一张是旧文章表,一张是新文章表,新文章表放2个月的信息,每天定期把2个月中的最早一天的文章归入旧表中。
(3)按版块结构
news_category
news_article
sports_category
sports_article
(4)按哈希结构
md5取前两位哈希可以达到1296张表,如果觉得不够,那就再加一位,总数可达46656张表。
分表是逻辑层面的分割,分区是物理层面、存储层面的分割。准确来说,分区是将数据分段划分在多个位置存放,可以是同一块磁盘也可以在不同的机器。分区后,表面上还是一张表。
mysql分表和分区有什么联系呢?
(1)都能提高mysql的性高,在高并发状态下都有一个良好的表现。
(2)分表和分区不矛盾,可以相互配合的,对于那些大访问量,并且表数据比较多的表,我们可以采取分表和分区结合的方式。
(3)merge这种分表方式,不能和分区配合。
(4)表分区相对于分表,操作方便,不需要创建子表。
把连续区间按范围划分:
create table user(
id int(11),
money int(11) unsigned not null,
date datetime
)
partition by range(YEAR(date))(
partition p2014 values less than (2015),
partition p2015 values less than (2016),
partition p2016 values less than (2017),
partition p2017 values less than maxvalue
);
把离散值分成集合,按集合划分,适合有固定取值列的表
create table user(
a int(11),
b int(11)
)
partition by list(b)(
partition p0 values in (1,3,5,7,9),
partition p1 values in (2,4,6,8,0)
);
随机分配,分区数固定:
create table user(
a int(11),
b datetime
)
partition by hash(YEAR(b))
partitions 4;
类似Hash,区别是只支持1列或多列,且mysql提供自身的Hash函数
create table user(
a int(11),
b datetime
)
partition by key(b)
partitions 4;
(1)新增分区
ALTER TABLE sale_data
ADD PARTITION (PARTITION p201710 VALUES LESS THAN (201711));
(2)删除分区
当删除了一个分区,也同时删除了该分区中所有的数据。
ALTER TABLE sale_data DROP PARTITION p201710;
(3)分区的合并
下面的SQL,将p201701 - p201709 合并为3个分区p2017Q1 - p2017Q3:
ALTER TABLE sale_data
REORGANIZE PARTITION p201701,p201702,p201703,
p201704,p201705,p201706,
p201707,p201708,p201709 INTO
(
PARTITION p2017Q1 VALUES LESS THAN (201704),
PARTITION p2017Q2 VALUES LESS THAN (201707),
PARTITION p2017Q3 VALUES LESS THAN (201710)
);