当构建大型网站时,因为种种原因会对数据进行切分(对种种原因我们下次再解释)。根据切分的规则,切分一般包括两个方面:垂直切分和水平切分。而根据切分的层次可以分为:机器级切分即mysqld进程级切分,库级切分,表级切分。首先来说说什么是垂直切分:
垂直切分是按照不同的表(或者 Schema)来切分到不同的数据库(主机)之上,大部分情况下它都是机器级的切分。当业务间的耦合度很低,或者说数据属于不同的逻辑上的模块,一般采取这种切分。这里需要注意的是,在机器级的层次上去采取数据切分时必须保证不会存在切分块之间的事务。因为mysql的分布式事务的支持还不够。采用垂直切分的优点是显而易见的:数据库的拆分简单明了,拆分规则明确;应用程序模块清晰明确,整合容易;数据维护方便易行,容易定位。但是这不能解决访问极其频繁且数据量超大的表的性能问题,而且如前面所说在事务的处理,和处理不同切分块之间的表关联时有很多不方便。因此在垂直切分数据时往往它的粒度比较粗,一般时按业务的需求或者说和应用模块的切分相对应。
再来说说水平切分,这个在web应用中比较重要也比较难处理。
水平切分简单点说就是把原本是一个表中的数据拆分到多个表中或者多个数据库的多个表中,把一个表中的行拆分到多个表中,显然表的Schema是一样的。数据的水平切分一般都是在库级和表级进行切分。
水平切分的优点显而易见主要有解决了大表的性能的问题。同时事务的处理相对比较简单。可扩展性强。但是水平切分也会产生一些问题,下面详细说说。
1.不能使用外键约束与join。很明显外键是分布到多个表中的无法在数据端去保证外键约束。同理也无法做到完全意义上的join。互联网中的应用对数据一致性的要求没有企业级IT应用要求那么高(当然像百付宝这种应用除外)。所以大部分的时候我们可以在应用层去适当的维护数据在外键上的约束。至于怎么是适当就取决于应用对性能与一致性的权衡。Join其实在大规模应用中是应该尽量避免的。尤其是关键的查询。如果你发现应用中30%以上的query都要用到join那就应该好好考虑数据库schema的设计。对于需要join的时候,可以通过冗余来实现。一个表包含需要join的query中的所有select字段。而且对于使用join的query一般会有特定的过滤条件。同理这个表也可以根据过滤条件水平切分。举个例子来说。表User,用户表与表Post,帖子表。为了查询某个用户的所有帖子,一般采取join的方式。在分库分表的情况下,无法确定join的Post表是哪一个。所以可以建一个冗余表Post_for_user。其中包含所业务查询所需要的所有信息。同时可以按照与User表相同的分库分表方式去分表。当然冗余也有缺点。一个是表变多了,再就是每次往Post表中插入数据时必须在Post_for_user表中插入同样的数据。这样逻辑就复杂且插入性能受影响。当然可以采用异步的更新Post_for_user表的方式去解决,下次再讨论这个问题。
2.对于不是按分库分表的字段做where条件去query或者update时需要在所有表上执行这个sql语句。对于特定的query如果不能根据where条件确定在哪个表上执行sql,就必须在所有的表上执行sql.这个显然是不可取的.因此必须根据业务需求去增加冗余的map表.比如:在user表中需要根据login_name去查询用户信息,但是user表是按照user_id分库分表的.所以可以构造一个user_map表去形成login_name与user_id的对应关系.并且这个表的分库分表原则是根据login_name去hash的.这样可以解决这个问题.但是可能有人要问如果有很多这种查询怎么办?其实从互联网应用来说,远没有那么复查的查询.所有的业务都是以user为中心的.好好的设计表是王道.当然这种方法还是有同1一样的冗余问题.
3.保持主键的唯一性比较复杂.当切分以后,主键的唯一性就不能靠数据库表本身去保证了。有两种方法去解决这个问题。1.在应用层去保证。可以实现一个服务,它的功能就是生成顺序增长的id。这样的话每次数据库的插入动作都需要先访问这个服务。这个服务很显然是个单点。至于怎么实现这个服务有很多种选择,比如用一个文件记当前的id,或者用一个特殊表的自增的字段做全局的id。2.在数据库端去保证。一般的方法是通过指定自增字段的起始点和步长去避免id的的重复,以达到保持主键唯一性的目的。比如分1000个表,可以指定步长为1000,起点分别是1~1000.或者按id区段分。步长为1,但是不同区段的起点相差很大。
总之,数据水平切分,涉及到的问题还是很多的。关键是根据业务本身的需求,采取一种合适的切分策略。不要为了切分去切分。
引用:http://www.zhouxblog.com/archives/37