mysql的扩展性之数据拆分

前言

前面我们介绍过mysql的复制特性,它在一定程度上可以提高数据的访问性能,但是当你的数据量很大的时候,我们的mysql服务器就显得力不从心。这时候,我们可以引入数据拆分的解决方案。我在这里根据数据拆分的简易程度分为分表、垂直分区、水平分区三种,并依次介绍。

分表

分表就是根据数据的逻辑关系,将一个表分为多个。例如可以把帖子表posts,分为post0,post1,post2。分表过后它们仍然处于同一个数据库当中,至于分为几个表为好,可以根据实际情况自己来定。

如果事先能估计到某些数据表数据量会很大,就可以选择在数据库设计初期,就做好分表。当然了,也可以在程序运行过程中,根据数据量的大小在手动分区。

那么表如何来分呢?没有一定答案,只能根据应用的类型来确定分表方案。比如你的客户是按不同城市来划分的,这时你可以按城市来分:custom_0371、custom_0373。你也可以把每个表的数据记录定为1万条,那么blog_00存储前1万条数据,第10001条数据就存储在blog_01,以此类推。再比如日志表,你可以按时间来划分,logs_201001、logs_201002。

这里我们来举个具体点的例子。假设有一个帖子表posts,它的数据结构如下:

create table posts (
id int(11) not null auto_increment,
title varchar(50) not null,
createtime int(11) not null default 0,
userid int(11) not null,
primary key (id),
key userid(userid)
);

现在这个里面的数据量过大,我们需要对它进行拆分,根据目前的数据量增长的趋势,我们计划把它拆分为10个小表,分别是posts_01、posts_02、…、posts_09,这几个表的数据结构一点也不用改变。这个表里面有个userid字段,我们向这个表插入数据,读取数据都需要这个字段,在这里我们就可以选取userid作为分表的依据,求userid % 10的值。

比如现在userid为10的用户要发表一篇帖子,求userid % 10 的值为0,那么这条记录就插入posts_00这张表中。要读取这篇帖子的话,以前可能是需要传递帖子id就行了,现在分表以后就需要调整了,还需要传递userid这个字段,知道userid为10的话,才能去posts_00表中获取数据。

使用merge存储引擎实现分表

使用show engines 查询到的MRG_MYISAM就是指这个存储引擎。

比如有两个myisam类型的表user1、user2,他们的数据结构、索引、字段顺序都完全一样,user1存储前10000条数据,user2从10001开始存储。我们可以建立一个merge类型的表alluser,并且数据结构和user1、user2也完全相同,并且指定alluser由user1和userid组成,那么我们就可以直接访问alluser这个表了。

垂直分区

如果上面的分表还只是涉及一个服务器的一个数据库的话,垂直分区一般都会涉及到两台甚至多台服务器了。

一般我们的应用程序都是都会按照模块来划分,模块之间的耦合读一般很低。基于这种情况,我们可以把blog模块涉及到的表放到A服务器,把论坛模块涉及到的表放到B服务器上,把整个系统的核心数据放到C服务器上,这就是垂直分区,也可以称之为纵向切分。

显然,经过纵向切分以后,单台服务器的负载会下降,进而提高了整个系统的响应能力。但是垂直分区也不是那么容易的事,一般各个模块直接虽然耦合度较低,但也是有联系的,这势必导致要修改程序。并且也不能过度的拆分,过度的拆分也会导致系统过于复杂而难以维护。

当然垂直分区也不能解决所有的问题,比如单表数据量过大的问题。我们还可以结合上面提到的分表和下面即将提到的水平分区来解决这个问题。

水平分区

其实水平分区和第一节提到的分表比较相似,都是对一个表进行逻辑上拆分。只不过分表还是在一台服务器上操作,而我在这里提到的水平分区是将一个表的数据分布到多台服务器上,可以称之为分表的升级版吧。

还是那上面的帖子表posts来举例。这里把posts分布到十台服务器上,分别是0、1、2…。posts表在这十台服务器上还是叫posts,表结构还是一样。分区依据还是userid,还是对10取余。

这时候我们需要计算的不是目标表名了,而是目标服务器。假如还是用useri为10举例,那就是,这个用户发的帖子需要存储到0这台服务器上。

数据拆分遇到的问题

数据分区之后,就带来了如何访问这些数据的问题。上面我只是简单的写了一些,其实真实情况要复杂。

order排序问题,数据水平拆分后就分布于多个表甚至多个服务器上,这给排序带来了很大的问题,并且目前也没有什么好的解决方案。所以,在项目初期都应该考虑到这样的问题。

由于不能扩服务器做join查询,所以以前程序里面的join操作可能有问题了,解决方案就是拆开join sql,分多次查询。可以先把主表的数据查出来,然后根据某个key,再把其他数据查询出来。有人可能说,这样多麻烦啊,并且性能也很差劲吧?是的,会麻烦一些,其实就算不分表,我们也应该坚决摒弃那种多于两个表的join语句;说到性能,我们可以用缓存来解决。就我所知,java领域中的hibernate也是这么干的。

其中,transaction是最难办的了,这样的问题也没有什么完美的解决方案,一般都会采取拆分事务的办法。不过在web项目中,一般都不会引入事务,有些情况下可以用程序去解决,或者干脆无视。

整合方案

假如我们已经确定了数据拆分的方案,那么我们如何将它与我们的应用程序整合呢。这的确是一个比较重要的问题,也是比较难办的问题。

(1) 封装数据访问层。

可以自定义一些配置文件,然后在发送query到数据库之前,让数据访问层根据配置文件生成特定的sql语句或者连接到不同的数据库。

这种方式应该说很是灵活,修改起来也比较容易。但是难度也是很大的,现实情况下数据的拆分会很复杂,并且是几种方案一起使用,如果访问层代码写的不好,很容易造成难以维护,甚至和其他模块的代码耦合到一块。

一个好的数据访问层应该是透明化的,也就是说写好配置文件后,我们不在需要关注数据应该存储到那台服务器。

(2) 使用开源的中间代理层

有时候感觉自己封装数据访问层太麻烦的话,可以选用一个开源的代理层。目前有mysql proxy和amoeba可以供我们使用。

代理层其实就是在客户端和mysql服务器之间建立一个连接池,客户端的请求发送到代理层,代理层经过分析后,再把sql发送到相应的mysql服务器。

mysql proxy是mysql官方提供的,它之所以灵活是因为它只提供了基础功能,其他功能比如负载均衡、读写分离,可以写lua脚本来实现。

amoeba是国内一个开发者写的,项目开始于08年,已经有不少中小公司试着在实际环境中使用了。我最近使用了一下,使用上还算简单,但是它的sql解析功能似乎还不算完善。

 

from http://www.usewo.com/?p=54 

猜你喜欢

转载自tiankonglala.iteye.com/blog/1537428