MySQL 水平拆分(读书笔记整理)
转:http://blog.csdn.net/mchdba/article/details/46278687
1,水平拆分的介绍
一般来说,简单的水平切分主要是将某个访问极其平凡的表再按照某个字段的某种规则来分散到多个表之中,每个表中包含一部分数据。
简单来说,我们可以将数据的水平切分理解为是按照数据行的切分,就是将表中的某些
行切分到一个数据库,而另外的某些行又切分到其他的数据库中。当然,为了能够比较容易
的判定各行数据被切分到哪个数据库中了,切分总是都需要按照某种特定的规则来进行的。
如根据某个数字类型字段基于特定数目取模,某个时间类型字段的范围,或者是某个字符类
型字段的hash 值。如果整个系统中大部分核心表都可以通过某个字段来进行关联,那这个
字段自然是一个进行水平分区的上上之选了,当然,非常特殊无法使用就只能另选其他了。
2,水平拆分的优缺点
水平拆分的优点:
◆表关联基本能够在数据库端全部完成;
◆不会存在某些超大型数据量和高负载的表遇到瓶颈的问题;
◆应用程序端整体架构改动相对较少;
◆事务处理相对简单;
◆只要切分规则能够定义好,基本上较难遇到扩展性限制;
水平切分的缺点:
◆切分规则相对更为复杂,很难抽象出一个能够满足整个数据库的切分规则;
◆后期数据的维护难度有所增加,人为手工定位数据更困难;
◆应用系统各模块耦合度较高,可能会对后面数据的迁移拆分造成一定的困难。
3,拆分的规则
3.1 根据取模水平拆分
一般来说,像现在互联网非常火爆的互联网公司,特别是电商和游戏业务,基本上大部分数据都能够通
过会员用户信息关联上,可能很多核心表都非常适合通过会员ID 来进行数据的水平切分。
而像论坛社区讨论系统,就更容易切分了,非常容易按照论坛编号来进行数据的水平切分。
切分之后基本上不会出现各个库之间的交互。
所以,对于我们的示例数据库来说,大部分的表都可以根据用户ID 来进行水平的切分。
不同用户相关的数据进行切分之后存放在不同的数据库中。如将所有用户ID 通过5取模
然后分别存放于两个不同的数据库中。每个和用户ID 关联上的表都可以这样切分。这样,
基本上每个用户相关的数据,都在同一个数据库中,即使是需要关联,也可以非常简单的关
联上。
3.2 根据区域来水平切分
比如全国划分为10大片区,江浙沪算一哥,齐鲁算一个,两广算一个,两湖算一个,中原算一个,西南算一个,内蒙一个,东北一个,西北一个,华北一个,东南一个。
在业务量比较大的华北、东南、江浙沪、两广片区的服务器可以分配较多的服务器资源,比如cpu、io、网络等等可以用比较好的高端配置。
在业务量正常的西北、齐鲁、两湖、东北的服务器可以分配中高端的服务器资源。
在业务量比较少的,西南、内蒙、中原的服务器可以稍微一般服务器即可。
当然这些资源划分不能对外明示,我们在做内部规划的时候考虑好就可以了,免得被人诟病说有所偏颇不重视之类的。
PS:这种划分不是定性的,根据业务可以随时将业务好的片区的资源升级。
如下图所示:
4,水平拆分与应用的整合视图
5,水平拆分后续的问题
在实施数据切分方案之前,有些可能存在的问题我们还是需要做一些分析的。一般来说,
我们可能遇到的问题主要会有以下几点:
◆引入分布式事务的问题;
◆跨节点Join 的问题;
◆跨节点合并排序分页问题;
5.1 引入分布式事务的问题
一旦数据进行切分被分别存放在多个MySQL Server 中之后,不管我们的切分规则设计
的多么的完美(实际上并不存在完美的切分规则),都可能造成之前的某些事务所涉及到的
数据已经不在同一个MySQL Server 中了。
在这样的场景下,如果我们的应用程序仍然按照老的解决方案,那么势必需要引入分布
式事务来解决。而在MySQL 各个版本中,只有从MySQL 5.0 开始以后的各个版本才开始对
分布式事务提供支持,而且目前仅有Innodb 提供分布式事务支持。不仅如此,即使我们刚
好使用了支持分布式事务的MySQL 版本,同时也是使用的Innodb 存储引擎,分布式事务
本身对于系统资源的消耗就是很大的,性能本身也并不是太高。而且引入分布式事务本身在
异常处理方面就会带来较多比较难控制的因素。
怎么办?其实我们可以可以通过一个变通的方法来解决这种问题,首先需要考虑的一件
事情就是:是否数据库是唯一一个能够解决事务的地方呢?其实并不是这样的,我们完全可
以结合数据库以及应用程序两者来共同解决。各个数据库解决自己身上的事务,然后通过应
用程序来控制多个数据库上面的事务。
也就是说,只要我们愿意,完全可以将一个跨多个数据库的分布式事务分拆成多个仅处
于单个数据库上面的小事务,并通过应用程序来总控各个小事务。当然,这样作的要求就是
我们的俄应用程序必须要有足够的健壮性,当然也会给应用程序带来一些技术难度。
5.2 跨节点Join 的问题
上面介绍了可能引入分布式事务的问题,现在我们再看看需要跨节点Join 的问题。数
据切分之后,可能会造成有些老的Join 语句无法继续使用,因为Join 使用的数据源可能
被切分到多个MySQL Server 中了。
怎么办?这个问题从MySQL 数据库角度来看,如果非得在数据库端来直接解决的话,
恐怕只能通过MySQL 一种特殊的存储引擎Federated 来解决了。Federated 存储引擎是
MySQL 解决类似于Oracle 的DB Link 之类问题的解决方案。和OracleDB Link 的主要
区别在于Federated 会保存一份远端表结构的定义信息在本地。咋一看,Federated 确实
是解决跨节点Join 非常好的解决方案。但是我们还应该清楚一点,那就似乎如果远端的表
结构发生了变更,本地的表定义信息是不会跟着发生相应变化的。如果在更新远端表结构的
时候并没有更新本地的Federated 表定义信息,就很可能造成Query 运行出错,无法得到
正确的结果。
对待这类问题,我还是推荐通过应用程序来进行处理,先在驱动表所在的MySQL Server
中取出相应的驱动结果集,然后根据驱动结果集再到被驱动表所在的MySQL Server 中取出
相应的数据。可能很多读者朋友会认为这样做对性能会产生一定的影响,是的,确实是会对
性能有一定的负面影响,但是除了此法,基本上没有太多其他更好的解决办法了。而且,由
于数据库通过较好的扩展之后,每台MySQL Server 的负载就可以得到较好的控制,单纯针
对单条Query 来说,其响应时间可能比不切分之前要提高一些,所以性能方面所带来的负
面影响也并不是太大。更何况,类似于这种需要跨节点Join 的需求也并不是太多,相对于
总体性能而言,可能也只是很小一部分而已。所以为了整体性能的考虑,偶尔牺牲那么一点
点,其实是值得的,毕竟系统优化本身就是存在很多取舍和平衡的过程。
5.3 跨节点合并排序分页问题
一旦进行了数据的水平切分之后,可能就并不仅仅只有跨节点Join 无法正常运行,有
些排序分页的Query 语句的数据源可能也会被切分到多个节点,这样造成的直接后果就是
这些排序分页Query 无法继续正常运行。其实这和跨节点Join 是一个道理,数据源存在
于多个节点上,要通过一个Query 来解决,就和跨节点Join 是一样的操作。同样
Federated 也可以部分解决,当然存在的风险也一样。
还是同样的问题,怎么办?我同样仍然继续建议通过应用程序来解决。
如何解决?解决的思路大体上和跨节点Join 的解决类似,但是有一点和跨节点Join
不太一样,Join 很多时候都有一个驱动与被驱动的关系,所以Join 本身涉及到的多个表
之间的数据读取一般都会存在一个顺序关系。但是排序分页就不太一样了,排序分页的数据
源基本上可以说是一个表(或者一个结果集),本身并不存在一个顺序关系,所以在从多个
数据源取数据的过程是完全可以并行的。这样,排序分页数据的取数效率我们可以做的比跨
库Join 更高,所以带来的性能损失相对的要更小,在有些情况下可能比在原来未进行数据
切分的数据库中效率更高了。当然,不论是跨节点Join 还是跨节点排序分页,都会使我们
的应用服务器消耗更多的资源,尤其是内存资源,因为我们在读取访问以及合并结果集的这
个过程需要比原来处理更多的数据。
分析到这里,可能很多朋友会发现,上面所有的这些问题,我给出的建议基本上都是通过应用程序来解决。大家可能心里开始犯嘀咕了,是不是因为我是DBA,所以就很多事情都扔给应用架构师和开发人员了?
其实完全不是这样,首先应用程序由于其特殊性,可以非常容易做到很好的扩展性,但
是数据库就不一样,必须借助很多其他的方式才能做到扩展,而且在这个扩展过程中,很难
避免带来有些原来在集中式数据库中可以解决但被切分开成一个数据库集群之后就成为一
个难题的情况。要想让系统整体得到最大限度的扩展,我们只能让应用程序做更多的事情,
来解决数据库集群无法较好解决的问题。
5.4 小结:
通过数据切分技术将一个大的MySQL Server 切分成多个小的MySQL Server,既解决
了写入性能瓶颈问题,同时也再一次提升了整个数据库集群的扩展性。不论是通过垂直切分,
还是水平切分,都能够让系统遇到瓶颈的可能性更小。尤其是当我们使用垂直和水平相结合
的切分方法之后,理论上将不会再遇到扩展瓶颈了。
6,案例演示
6.1 创建数据库3个实例
创建多实例参加:http://blog.csdn.net/mchdba/article/details/45798139
6.2 创建库和表以及用户
创建库表
create database `hwdb` /*!40100 default characterset utf8 */;
create table uc_user(user_id bigint primarykey, uc_name varchar(200), created_time datetime) engine=innodb charset utf8;
- 1
- 2
创建用户
grant insert,update,delete,select on hwdb.*to tim@'192.168.%' identified by 'timgood2013';
- 1
执行过程:
mysql> create table uc_user(user_idbigint primary key, uc_name varchar(200), created_time datetime) engine=innodbcharset utf8;
Query OK, 0 rows affected (0.53 sec)
mysql>
mysql> grant insert,update,delete,selecton hwdb.* to tim@'192.168.%' identified by 'timgood2013';
Query OK, 0 rows affected (0.03 sec)
mysql>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
6.3 创建java代码示例
package mysql;
import java.math.BigInteger;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Calendar;
public classMySQLTest {
public static void main(String[] args) {
MySQLTestmt=newMySQLTest();
//
BigIntegerbi = newBigInteger("2015053010401005");
Stringport=mt.getDBPort(bi.longValue());
Connection conn=mt.getConn(port);
mt.insert(conn,bi, "tim--"+bi.longValue());
}
// 获取要访问的db端口
public String getDBPort(long user_id){
Stringport="3307";
long v_cast=user_id%3;
if (v_cast==1 ){
port="3308";
}else if(v_cast==2){
port="3309";
}else {
port="3307";
}
return port;
}
// 获取数据库的连接,如果扩展的话,可以单独做一个接口提供给程序员来调用它
public ConnectiongetConn(String port ) {
Connectionconn = null;
try {
Class.forName("com.mysql.jdbc.Driver");
}catch(ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Stringurl = "jdbc:mysql://192.168.52.130:"+port+"/hwdb";
try {
conn= DriverManager.getConnection(url, "tim", "timgood2013");
}catch(SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("the current db is :"+url);
return conn;
}
// 获取日期字符串
public StringgetTimeByCalendar(){
Calendar cal = Calendar.getInstance();
int year = cal.get(Calendar.YEAR);//获取年份
int month=cal.get(Calendar.MONTH);//获取月份
int day=cal.get(Calendar.DATE);//获取日
int hour=cal.get(Calendar.HOUR);//小时
int minute=cal.get(Calendar.MINUTE);//分
int second=cal.get(Calendar.SECOND);//秒
String strdate=year+"-"+month+"-"+day+" "+hour+":"+minute+":"+second;
return strdate;
}
// 开始录入数据
public int insert(Connectioncnn,BigInteger user_id,String name){
Stringsql="insert intohwdb.uc_user(user_id,uc_name,created_time)values(?,?,?)";
int i=0;
long uid =user_id.longValue();
Connectionconn=cnn;
try{
PreparedStatement preStmt=conn.prepareStatement(sql);
preStmt.setLong(1, uid);
preStmt.setString(2,name);
preStmt.setString(3,getTimeByCalendar());
i=preStmt.executeUpdate();
}
catch (SQLException e)
{
e.printStackTrace();
}
return i;//返回影响的行数,1为执行成功
}
}
6.4 测试代码
User_id按照注册年月日时分秒+9999,这样的思路是,一秒满足9999个并发,也不会,至于如何统一规划这全局的9999个,可以设置一个静态的全局变量,而且这个全局变量会及时保存到某个DB中,这样基本保证了不重复,比如user_id:2015053010401005、2015053010401006、2015053010401007,测试代码如下:
MySQLTestmt=newMySQLTest();
//
BigIntegerbi = newBigInteger("2015053010401005");
Stringport=mt.getDBPort(bi.longValue());
Connection conn=mt.getConn(port);
1)通过%3来获取DB连接;
余0 --> db1(3307端口)
余1 --> db2(3308端口)
余2 --> db3(3309端口)
2)查看结果
运行结束后,可以去3个实例相应的查看录入的数据,如下所示:
Id为2015053010401005应该录入到db1(3307端口)
[root@data02 ~]# mysql--socket=/usr/local/mysql3307/mysql.sock -e "select * fromhwdb.uc_user;";
+------------------+-----------------------+---------------------+
| user_id | uc_name | created_time |
+------------------+-----------------------+---------------------+
| 2015053010401005 | tim--2015053010401005 |2015-04-30 09:27:48 |
+------------------+-----------------------+---------------------+
[root@data02 ~]#
Id为2015053010401006应该录入到db2(3308端口)
[root@data02 ~]# mysql--socket=/usr/local/mysql3308/mysql.sock -e "select * fromhwdb.uc_user;";
+------------------+-----------------------+---------------------+
| user_id | uc_name | created_time |
+------------------+-----------------------+---------------------+
| 2015053010401006 | tim--2015053010401006 |2015-04-30 09:27:57 |
+------------------+-----------------------+---------------------+
[root@data02 ~]#
Id为2015053010401007应该录入到db3(3309端口)
[root@data02 ~]# mysql--socket=/usr/local/mysql3309/mysql.sock -e "select * fromhwdb.uc_user;";
+------------------+-----------------------+---------------------+
| user_id | uc_name | created_time |
+------------------+-----------------------+---------------------+
| 2015053010401007 | tim--2015053010401007| 2015-04-30 09:28:01 |
+------------------+-----------------------+---------------------+
[root@data02 ~]#
6.5总结
基本和预想的一样的,数据通过模id取余数的方法,水平拆分到不同的库里面,这里只是简单演示了下,实际生产的复杂程度远比这个要高的多,所以大家遇到的问题会更多,但是水平拆分的理念都是类似的,这条路是光明的,大家可以放心走下去。
参考文档:MySQL性能调优与架构设计一书
MySQL 水平拆分(读书笔记整理)的更多相关文章
- MySQL 水平拆分与垂直拆分详解
前言:说到优化mysql,总会有这么个回答:水平拆分,垂直拆分,那么我们就来说说什么是水平拆分,垂直拆分. 一.垂直拆分 说明:一个数据库由很多表的构成,每个表对应着不同的业务,垂直切分是指按照业务将 ...
- mysql水平拆分与垂直拆分的详细介绍(转载http://www.cnblogs.com/nixi8/p/4524082.html)
垂直 垂直拆分是指数据表列的拆分,把一张列比较多的表拆分为多张表 通常我们按以下原则进行垂直拆分: 把不常用的字段单独放在一张表; 把text,blob等大字段拆分出来放在附表中; 经常组合查询的 ...
- MYSQL水平拆分与垂直拆分
目前很多互联网系统都存在单表数据量过大的问题,这就降低了查询速度,影响了客户体验.为了提高查询速度,我们可以优化sql语句,优化表结构和索引,不过对那些百万级千万级的数据库表,即便是优化过后,查询速度 ...
- MySQL技术内幕读书笔记(四)——表
目录 表 索引组织表 InnoDB逻辑存储结构 INNODB行记录格式 INNODB数据页结构 约束 视图 分区表 表 表就是关于特定实体的数据集合,是关系型数据库模型的核心. 索引组织表 在 ...
- MySQL技术内幕读书笔记(五)——索引与算法
索引与算法 INNODB存储引擎索引概述 INNODB存储引擎支持以下几种常见的索引: B+树索引 全文索引 哈希索引 InnoDB存储引擎支持的哈希索引是自适应的.会根据表的情况自动添加 ...
- MySQL恢复备份读书笔记
1. 任何执行时间长于 wait_timeout或interactive_timeout选项值得备份,都会导致会话被关闭,这也会隐含执行UNLOCK TABLES命令.2. 对于使用FLUSH TAB ...
- 我读<代码整洁之道>--读书笔记整理
第一章 整洁代码 "我可以列出我留意到的整洁代码的所有特点,但其中有一条是根本性的,整洁的代码总是看起来像是某位特别在意他的人写的.几乎没有改进的余地,代码作者设么都想到了,如果你企图改进它 ...
- MySQL技术内幕读书笔记(八)——事务
事务的实现 事务隔离性由锁来实现.原子性.一致性.持久性通过数据库的redo log和undo log来完成.redo log称为重做日志,用来保证事务的原子性和持久性.undo log用来保证事 ...
- MySQL技术内幕读书笔记(七)——锁
锁 锁是数据库系统区分与文件系统的一个关键特性.为了保证数据一致性,必须有锁的介入.数据库系统使用锁是为了支持对共享资源进行并发访问,提供数据的完整性和一致性. lock与latch 使用命令 ...
随机推荐
- Spark源码分析 之 Driver和Excutor是怎么跑起来的?(2.2.0版本)
今天抽空回顾了一下Spark相关的源码,本来想要了解一下Block的管理机制,但是看着看着就回到了SparkContext的创建与使用.正好之前没有正式的整理过这部分的内容,这次就顺带着回顾一下. S ...
- Java学习笔记11(面向对象四:多态)
多态: 举例:描述一个事物的多种形态,如Student类继承了Person类,一个Student对象既是Student,又是Person 多态体现为:父类引用变量可以指向子类对象 多态的前提:必须有子 ...
- php的二维数组排序
//建立一个$arr的二维数组 $arr=array( array('name'=>'e','age'=>5), array('name'=>'b','age'=>2), ar ...
- NoSQL:Linux操作memcached
一 NoSQL简介 NoSQL(NoSQL = Not Only SQL ),意即"不仅仅是SQL",泛指非关系型的数据库,随着互联网web2.0网站的兴起,传统的关系数据库在应付 ...
- 转载自 BotVS 「 珍藏版 」如何搭建一个完整的交易框架
[img]http://dn-filebox.qbox.me/8c218c119046b2a25df2d9c7b00c1e0fa6899bdd.png[/img]NO:01 交易策略 ≠ 交易系统. ...
- K:平衡二叉树(AVL)
相关介绍: 二叉查找树的查找效率与二叉树的形状有关,对于按给定序列建立的二叉排序树,若其左.右子树均匀分布,则查找过程类似于有序表的二分查找,时间复杂度变为O(log2n).当若给定序列原来有序,则 ...
- 要不要用gzip优化前端项目
这两天在做项目优化,注意到webpack有一个compression-webpack-plugin插件,可以打包成gzip格式部署到服务器,了解到了GZIP,其实GZIP有很多点,这里我们只讨论前端范 ...
- 不用第三方解码码取得图片宽高 附完整C++算法实现代码
在特定的应用场景下,有时候我们只是想获取图片的宽高, 但不想通过解码图片才取得这个信息. 预先知道图片的宽高信息,进而提速图片加载,预处理等相关操作以提升体验. 在stackoverflow有一篇相关 ...
- [bzoj1731] [Usaco2005 dec]Layout 排队布局
差分约束系统...因为题目要求的是1和n的最大距离所以这题就跑最长路.. 对于互相反感的牛(i与j互相反感,彼此距离至少为len,i<j),就有dis[j]-dis[i]>=len.就加一 ...
- Codeforces Round #416(Div. 2)-811A.。。。 811B.。。。 811C.dp。。。不会
CodeForces - 811A A. Vladik and Courtesy time limit per test 2 seconds memory limit per test 256 meg ...