您的位置:首页 > 数据库 > MySQL

Mysql分库分表方案

2016-01-25 14:49 351 查看
Mysql分区:【还是在一个表里[b]】,分表则在多张表了[/b]

说白就是sql语句,1.可以建静态的分区,2.也可以建动态的,3.还可以后来补充分区【下面的注释部分其实是执行的,尽管是灰色】

 CREATE TABLE `us_active_user_111111111111111` (

  `visitdate` date NOT NULL,

  `us_id` int(11) NOT NULL,

  PRIMARY KEY (`us_id`,`visitdate`)

) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4

/*!50100 PARTITION BY RANGE (EXTRACT(YEAR_MONTH FROM `visitdate`))

SUBPARTITION BY HASH (to_days(`visitdate`))

SUBPARTITIONS 31

(PARTITION p201610 VALUES LESS THAN (201611) ENGINE = MyISAM,

 PARTITION p201611 VALUES LESS THAN (201612) ENGINE = MyISAM,

 PARTITION p201612 VALUES LESS THAN (201701) ENGINE = MyISAM,

 PARTITION p201701 VALUES LESS THAN (201702) ENGINE = MyISAM,

 PARTITION p201702 VALUES LESS THAN (201703) ENGINE = MyISAM,

 PARTITION p201703 VALUES LESS THAN (201704) ENGINE = MyISAM,

 PARTITION p201704 VALUES LESS THAN (201705) ENGINE = MyISAM,

 PARTITION p201705 VALUES LESS THAN (201706) ENGINE = MyISAM,

 PARTITION p201706 VALUES LESS THAN (201707) ENGINE = MyISAM,

 PARTITION p201707 VALUES LESS THAN (201708) ENGINE = MyISAM,

 PARTITION p201708 VALUES LESS THAN (201709) ENGINE = MyISAM,

 PARTITION p201709 VALUES LESS THAN (201710) ENGINE = MyISAM,

 PARTITION p201710 VALUES LESS THAN (201711) ENGINE = MyISAM,

 PARTITION p201711 VALUES LESS THAN (201712) ENGINE = MyISAM,

 PARTITION p201712 VALUES LESS THAN (201801) ENGINE = MyISAM,

 PARTITION p201801 VALUES LESS THAN (201802) ENGINE = MyISAM,

 PARTITION p201802 VALUES LESS THAN (201803) ENGINE = MyISAM,

 PARTITION p201803 VALUES LESS THAN (201804) ENGINE = MyISAM) */;

分区的几种方式:【也可以自己定义】


list分区

LIST分区中每个分区的定义和选择是基于某列的值从属于一个值列表集中的一个值,而RANGE分 区是从属于一个连续区间值的集合。

//这种方式失败  

mysql> CREATE TABLE IF NOT EXISTS `list_part` (  

 ->   `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户ID',  

 ->   `province_id` int(2) NOT NULL DEFAULT 0 COMMENT '省',  

 ->   `name` varchar(50) NOT NULL DEFAULT '' COMMENT '名称',  

 ->   `sex` int(1) NOT NULL DEFAULT '0' COMMENT '0为男,1为女',  

 ->   PRIMARY KEY (`id`)  

 -> ) ENGINE=INNODB  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1  

 -> PARTITION BY LIST (province_id) (  

 ->     PARTITION p0 VALUES IN (1,2,3,4,5,6,7,8),  

 ->     PARTITION p1 VALUES IN (9,10,11,12,16,21),  

 ->     PARTITION p2 VALUES IN (13,14,15,19),  

 ->     PARTITION p3 VALUES IN (17,18,20,22,23,24)  

 -> );  

ERROR 1503 (HY000): A PRIMARY KEY must include all columns in the table's partitioning function 

 

//这种方式成功 

mysql> CREATE TABLE IF NOT EXISTS `list_part` ( 

 ->   `id` int(11) NOT NULL  COMMENT '用户ID', 

 ->   `province_id` int(2) NOT NULL DEFAULT 0 COMMENT '省', 

 ->   `name` varchar(50) NOT NULL DEFAULT '' COMMENT '名称', 

 ->   `sex` int(1) NOT NULL DEFAULT '0' COMMENT '0为男,1为女'  

 -> ) ENGINE=INNODB  DEFAULT CHARSET=utf8  

 -> PARTITION BY LIST (province_id) (  

 ->     PARTITION p0 VALUES IN (1,2,3,4,5,6,7,8),  

 ->     PARTITION p1 VALUES IN (9,10,11,12,16,21),  

 ->     PARTITION p2 VALUES IN (13,14,15,19),  

 ->     PARTITION p3 VALUES IN (17,18,20,22,23,24)  

 -> );  

Query OK, 0 rows affected (0.33 sec)  

上面的这个创建list分区时,如果有主銉的话,分区时主键必须在其中,不然就会报错

如果我不用主键,分区就创建成功了,一般情况下,一个张表肯定会有一个主键,这算是一个分区的局限性吧。

如果对数据进行测试,请参考range分区的测试来操作


hash分区

HASH分区主要用来确保数据在预先确定数目的分区中平均分布,你所要做的只是基于将要被哈希的列值指定一个列值或表达式,以 及指定被分区的表将要被分割成的分区数量。

mysql> CREATE TABLE IF NOT EXISTS `hash_part` (  

 ->   `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '评论ID',  

 ->   `comment` varchar(1000) NOT NULL DEFAULT '' COMMENT '评论',  

 ->   `ip` varchar(25) NOT NULL DEFAULT '' COMMENT '来源IP',  

 ->   PRIMARY KEY (`id`)  

 -> ) ENGINE=INNODB  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1  

 -> PARTITION BY HASH(id)  

 -> PARTITIONS 3;  

Query OK, 0 rows affected (0.06 sec)  


key分区

按照KEY进行分区类似于按照HASH分区,除了HASH分区使用的用 户定义的表达式,而KEY分区的 哈希函数是由MySQL 服务器提供。

mysql> CREATE TABLE IF NOT EXISTS `key_part` (  

 ->   `news_id` int(11) NOT NULL  COMMENT '新闻ID',  

 ->   `content` varchar(1000) NOT NULL DEFAULT '' COMMENT '新闻内容',  

 ->   `u_id` varchar(25) NOT NULL DEFAULT '' COMMENT '来源IP',  

 ->   `create_time` DATE NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '时间'  

 -> ) ENGINE=INNODB  DEFAULT CHARSET=utf8  


 -> PARTITION BY LINEAR HASH(YEAR(create_time))  

 -> PARTITIONS 3;  

Query OK, 0 rows affected (0.07 sec)  

分区管理

新增分区

删除分区
--当删除了一个分区,也同时删除了该分区中所有的数据。
ALTER TABLE sale_data DROP PARTITION p201010;

分区的合并
下面的SQL,将p201001 - p201009 合并为3个分区p2010Q1 - p2010Q3


分区优点
1,分区可以分在多个磁盘,存储更大一点
2,根据查找条件,也就是where后面的条件,查找只查找相应的分区不用全部查找了
3,进行大数据搜索时可以进行并行处理。
4,跨多个磁盘来分散数据查询,来获得更大的查询吞吐量

Mysql分库分表方案

http://wentao365.iteye.com/blog/1740874

1.为什么要分表:

当一张表的数据达到几千万时,你查询一次所花的时间会变多,如果有联合查询的话,我想有可能会死在那儿了。分表的目的就在于此,减小数据库的负担,缩短查询时间。

mysql中有一种机制是表锁定和行锁定,是为了保证数据的完整性。

表锁定表示你们都不能对这张表进行操作,必须等我对表操作完才行。

行锁定也一样,别的sql必须等我对这条数据操作完了,才能对这条数据进行操作。

2. mysql proxy:amoeba

做mysql集群,利用amoeba。

从上层的java程序来讲,不需要知道主服务器和从服务器的来源,即主从数据库服务器对于上层来讲是透明的。可以通过amoeba来配置。

 3.大数据量并且访问频繁的表,将其分为若干个表

比如对于某网站平台的数据库表-公司表,数据量很大,这种能预估出来的大数据量表,我们就事先分出个N个表,这个N是多少,根据实际情况而定。

     某网站现在的数据量至多是5000万条,可以设计每张表容纳的数据量是500万条,也就是拆分成10张表,

那么如何判断某张表的数据是否容量已满呢?可以在程序段对于要新增数据的表,

在插入前先做统计表记录数量的操作,当<500万条数据,就直接插入,

当已经到达阀值,可以在程序段新创建数据库表(或者已经事先创建好),再执行插入操作。

 4. 利用merge存储引擎来实现分表

如果要把已有的大数据量表分开比较痛苦,最痛苦的事就是改代码,因为程序里面的sql语句已经写好了。用merge存储引擎来实现分表, 这种方法比较适合.

举例子:



 

 ------------------- ----------华丽的分割线--------------------------------------

 数据库架构

1、简单的MySQL主从复制:

MySQL的主从复制解决了数据库的读写分离,并很好的提升了读的性能,其图如下:



 

其主从复制的过程如下图所示:



 

但是,主从复制也带来其他一系列性能瓶颈问题:

1. 写入无法扩展

2. 写入无法缓存

3. 复制延时

4. 锁表率上升

5. 表变大,缓存率下降

那问题产生总得解决的,这就产生下面的优化方案,一起来看看。

2、MySQL垂直分区

   如果把业务切割得足够独立,那把不同业务的数据放到不同的数据库服务器将是一个不错的方案,而且

万一其中一个业务崩溃了也不会影响其他业务的正常进行,

并且也起到了负载分流的作用,大大提升了数据库的吞吐能力。

经过垂直分区后的数据库架构图如下:

 


然而,尽管业务之间已经足够独立了,但是有些业务之间或多或少总会有点联系,如用户,基本上都会和每个业务相关联,况且这种分区方式,也不能解决单张表数据量暴涨的问题,因此为何不试试水平分割呢?

3、MySQL水平分片(Sharding)

这是一个非常好的思路,将用户按一定规则(按id哈希)分组,并把该组用户的数据存储到一个数据库分片中,即一个sharding,这样随着用户数量的增加,只要简单地配置一台服务器即可,原理图如下:

 


如何来确定某个用户所在的shard呢,可以

建一张用户和shard对应的数据表,

每次请求先从这张表找用户的shard id,再从对应shard中查询相关数据,如下图所示:



不太严格的讲,对于海量数据的数据库,

如果是因为表多而数据多,这时候适合使用垂直切分,即把关系紧密(比如同一模块)的表切分出来放在一个server上。

如果表并不多,但每张表的数据非常多,这时候适合水平切分,即把表的数据按某种规则(比如按ID散列)切分到多个数据库(server)上。

当然,现实中更多是这两种情况混杂在一起,这时候需要根据实际情况做出选择,也可能会综合使用垂直与水平切分,从而将原有数据库切分成类似矩阵一样可以无限扩充的数据库(server)阵列。下面分别详细地介绍一下垂直切分和水平切分.



垂直切分的最大特点就是规则简单,实施也更为方便,


尤其适合各业务之间的耦合度非常低,相互影响很小,业务逻辑非常清晰的系统。

在这种系统中,可以很容易做到将不同业务模块所使用的表分拆到不同的数据库中。

根据不同的表来进行拆分,对应用程序的影响也更小,拆分规则也会比较简单清晰。(这也就是所谓的”share nothing”)。


 



不太严格的讲,对于海量数据的数据库,如果是因为表多而数据多,这时候适合使用垂直切分,即把关系紧密(比如同一模块)的表切分出来放在一个server上。如果表并不多,但每张表的数据非常多,这时候适合水平切分,即把表的数据按某种规则(比如按ID散列)切分到多个数据库(server)上。当然,现实中更多是这两种情况混杂在一起,这时候需要根据实际情况做出选择,也可能会综合使用垂直与水平切分,从而将原有数据库切分成类似矩阵一样可以无限扩充的数据库(server)阵列。下面分别详细地介绍一下垂直切分和水平切分.



水平切分于垂直切分相比,相对来说稍微复杂一些。因为要
将同一个表中的不同数据拆分到不同的数据库中,对于应用程序来说,拆分规则本身就较根据表名来拆分更为复杂,
后期的数据维护也会更为复杂一些



数系统会将垂直切分和水平切分联合使用,先对系统做垂直切分,再针对每一小搓表的情况选择性地做水平切分。从而将整个数据库切分成一个分布式矩阵。






二、切分策略

      如前面所提到的,切分是按先垂直切分再水平切分的步骤进行的。

     垂直切分的结果正好为水平切分做好了铺垫。

直切分的思路就是分析表间的聚合关系,把关系紧密的表放在一起。多数情况下可能是同一个模块,或者是同一“聚集”。这里的“聚集”正是领域驱动设计里所说的聚集。在垂直切分出的表聚集内,找出“根元素”(这里的“根元素”就是领域驱动设计里的“聚合根”),按“根元素”进行水平切分,也就是从“根元素”开始,把所有和它直接与间接关联的数据放入一个shard里。这样出现跨shard关联的可能性就非常的小。应用程序就不必打断既有的表间关联。比如:对于社交网站,几乎所有数据最终都会关联到某个用户上,基于用户进行切分就是最好的选择。再比如论坛系统,用户和论坛两个模块应该在垂直切分时被分在了两个shard里,对于论坛模块来说,Forum显然是聚合根,因此按Forum进行水平切分,把Forum里所有的帖子和回帖都随Forum放在一个shard里是很自然的。

      对于共享数据数据,如果是只读的字典表,每个shard里维护一份应该是一个不错的选择,这样不必打断关联关系。如果是一般数据间的跨节点的关联,就必须打断。

      需要特别说明的是:当同时进行垂直和水平切分时,切分策略会发生一些微妙的变化。比如:在只考虑垂直切分的时候,被划分到一起的表之间可以保持任意的关联关系,因此你可以按“功能模块”划分表格,但是一旦引入水平切分之后,表间关联关系就会受到很大的制约,通常只能允许一个主表(以该表ID进行散列的表)和其多个次表之间保留关联关系,也就是说:当同时进行垂直和水平切分时,在垂直方向上的切分将不再以“功能模块”进行划分,而是需要更加细粒度的垂直切分,而这个粒度与领域驱动设计中的“聚合”概念不谋而合,甚至可以说是完全一致,每个shard的主表正是一个聚合中的聚合根!这样切分下来你会发现数据库分被切分地过于分散了(shard的数量会比较多,但是shard里的表却不多),为了避免管理过多的数据源,充分利用每一个数据库服务器的资源,可以考虑将业务上相近,并且[b][b]具有相近数据增长速率(主表数据量在同一数量级上)的[/b]两个或多个shard放到同一个数据源里,每个shard依然是独立的,它们有各自的主表,并使用各自主表ID进行散列,不同的只是它们的散列取模(即节点数量)必需是一致的。([/b]

本文着重介绍sharding的基本思想和理论上的切分策略,关于更加细致的实施策略和参考事例请参考我的另一篇博文:数据库分库分表(sharding)系列(一) 拆分实施策略和示例演示 


1.事务问题:

解决事务问题目前有两种可行的方案:分布式事务和通过应用程序与数据库共同控制实现事务下面对两套方案进行一个简单的对比。

方案一:使用分布式事务

    优点:交由数据库管理,简单有效

    缺点:性能代价高,特别是shard越来越多时

方案二:由应用程序和数据库共同控制

     原理:将一个跨多个数据库的分布式事务分拆成多个仅处

           于单个数据库上面的小事务,并通过应用程序来总控

           各个小事务。

     优点:性能上有优势

     缺点:需要应用程序在事务控制上做灵活设计。如果使用  

           了spring的事务管理,改动起来会面临一定的困难。

2.跨节点Join的问题

      只要是进行切分,跨节点Join的问题是不可避免的。但是良好的设计和切分却可以减少此类情况的发生。解决这一问题的普遍做法是分两次查询实现。在第一次查询的结果集中找出关联数据的id,根据这些id发起第二次请求得到关联数据。

3.跨节点的count,order by,group by以及聚合函数问题

      这些是一类问题,因为它们都需要基于全部数据集合进行计算。多数的代理都不会自动处理合并工作。解决方案:与解决跨节点join问题的类似,分别在各个节点上得到结果后在应用程序端进行合并。和join不同的是每个结点的查询可以并行执行,因此很多时候它的速度要比单一大表快很多。但如果结果集很大,对应用程序内存的消耗是一个问题。

参考资料:

《MySQL性能调优与架构设计》

比如日志文章(article)表有如下字段:



面对这样的一个表,我们怎样切分呢?怎样将这样的数据分布到不同的数据库中的表中去呢?我们可以这样做,将user_id为1~10000的所有的文章信息放入DB1中的article表中,将user_id为10001~20000的所有文章信息放入DB2中的 article表中,以此类推,一直到DBn。这样一来,文章数据就很自然的被分到了各个数据库中,达到了数据切分的目的。

接下来要解决的问题就是怎样找到具体的数据库呢?其实问题也是简单明显的,既然分库的时候我们用到了区分字段user_id,那么很自然,数据库路由的过程当然还是少不了user_id的。就是我们知道了这个blog的user_id,就利用这个user_id,利用分库时候的规则,反过来定位具体的数据库。比如user_id是234,利用刚才的规则,就应该定位到DB1,假如user_id是12343,利用该才的规则,就应该定位到DB2。以此类推,利用分库的规则,反向的路由到具体的DB,这个过程我们称之为“DB路由”。

平常我们会自觉的按照范式来设计我们的数据库,考虑到数据切分的DB设计,将违背这个通常的规矩和约束。为了切分,我们不得不在数据库的表中出现冗余字段,用作区分字段或者叫做分库的标记字段。比如上面的article的例子中的user_id这样的字段(当然,刚才的例子并没有很好的体现出user_id的冗余性,因为user_id这个字段即使就是不分库,也是要出现的,算是我们捡了便宜吧)。当然冗余字段的出现并不只是在分库的场景下才出现的,在很多大型应用中,冗余也是必须的,这个涉及到高效DB的设计,本文不再赘述。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息