分库分表的几种常见玩法及如何解决跨库查询等问题

在谈论数据库架构和数据库优化的时候,咱们常常会听到“分库分表”、“分片”、“Sharding”…这样的关键词。让人感到高兴的是,这些朋友所服务的公司业务量正在(或者即将面临)高速增加,技术方面也面临着一些挑战。让人感到担心的是,他们系统真的就须要“分库分表”了吗?“分库分表”有那么容易实践吗?为此,笔者整理了分库分表中可能遇到的一些问题,并结合以往经验介绍了对应的解决思路和建议。spring

垂直分表

垂直分表在平常开发和设计中比较常见,通俗的说法叫作“大表拆小表”,拆分是基于关系型数据库中的“列”(字段)进行的。一般状况,某个表中的字段比较多,能够新创建一张“扩展表”,将不常用或者长度较大的字段拆分出去放到“扩展表”中,以下图所示:sql

小结

 

在字段不少的状况下,拆分开确实更便于开发和维护(笔者曾见过某个遗留系统中,一个大表中包含100多列的)。某种意义上也能避免“跨页”的问题(MySQL、MSSQL底层都是经过“数据页”来存储的,“跨页”问题可能会形成额外的性能开销,这里不展开,感兴趣的朋友能够自行查阅相关资料进行研究)。数据库

拆分字段的操做建议在数据库设计阶段就作好。若是是在发展过程当中拆分,则须要改写之前的查询语句,会额外带来必定的成本和风险,建议谨慎。缓存

垂直分库

垂直分库在“微服务”盛行的今天已经很是普及了。基本的思路就是按照业务模块来划分出不一样的数据库,而不是像早期同样将全部的数据表都放到同一个数据库中。以下图:安全


小结服务器

系统层面的“服务化”拆分操做,可以解决业务系统层面的耦合和性能瓶颈,有利于系统的扩展维护。而数据库层面的拆分,道理也是相通的。与服务的“治理”和“降级”机制相似,咱们也能对不一样业务类型的数据进行“分级”管理、维护、监控、扩展等。架构

众所周知,数据库每每最容易成为应用系统的瓶颈,而数据库自己属于“有状态”的,相对于Web和应用服务器来说,是比较难实现“横向扩展”的。数据库的链接资源比较宝贵且单机处理能力也有限,在高并发场景下,垂直分库必定程度上可以突破IO、链接数及单机硬件资源的瓶颈,是大型分布式系统中优化数据库架构的重要手段。并发

而后,不少人并无从根本上搞清楚为何要拆分,也没有掌握拆分的原则和技巧,只是一味的模仿大厂的作法。致使拆分后遇到不少问题(例如:跨库join,分布式事务等)。数据库设计

水平分表

水平分表也称为横向分表,比较容易理解,就是将表中不一样的数据行按照必定规律分布到不一样的数据库表中(这些表保存在同一个数据库中),这样来下降单表数据量,优化查询性能。最多见的方式就是经过主键或者时间等字段进行Hash和取模后拆分。以下图所示:分布式

小结

水平分表,可以下降单表的数据量,必定程度上能够缓解查询性能瓶颈。但本质上这些表还保存在同一个库中,因此库级别仍是会有IO瓶颈。因此,通常不建议采用这种作法。

水平分库分表

水平分库分表与上面讲到的水平分表的思想相同,惟一不一样的就是将这些拆分出来的表保存在不一样的数据中。这也是不少大型互联网公司所选择的作法。以下图:

某种意义上来说,有些系统中使用的“冷热数据分离”(将一些使用较少的历史数据迁移到其余的数据库中。而在业务功能上,一般默认只提供热点数据的查询),也是相似的实践。在高并发和海量数据的场景下,分库分表可以有效缓解单机和单库的性能瓶颈和压力,突破IO、链接数、硬件资源的瓶颈。固然,投入的硬件成本也会更高。同时,这也会带来一些复杂的技术问题和挑战(例如:跨分片的复杂查询,跨分片事务等)

分库分表的难点

垂直分库带来的问题和解决思路:

跨库join的问题

在拆分以前,系统中不少列表和详情页所需的数据是能够经过sql join来完成的。而拆分后,数据库多是分布式在不一样实例和不一样的主机上,join将变得很是麻烦。并且基于架构规范,性能,安全性等方面考虑,通常是禁止跨库join的。那该怎么办呢?首先要考虑下垂直分库的设计问题,若是能够调整,那就优先调整。若是没法调整的状况,下面笔者将结合以往的实际经验,总结几种常见的解决思路,并分析其适用场景。

跨库Join的几种解决思路

全局表

所谓全局表,就是有可能系统中全部模块均可能会依赖到的一些表。比较相似咱们理解的“数据字典”。为了不跨库join查询,咱们能够将这类表在其余每一个数据库中均保存一份。同时,这类数据一般也不多发生修改(甚至几乎不会),因此也不用太担忧“一致性”问题。

字段冗余

这是一种典型的反范式设计,在互联网行业中比较常见,一般是为了性能来避免join查询。

举个电商业务中很简单的场景:

“订单表”中保存“卖家Id”的同时,将卖家的“Name”字段也冗余,这样查询订单详情的时候就不须要再去查询“卖家用户表”。

字段冗余能带来便利,是一种“空间换时间”的体现。但其适用场景也比较有限,比较适合依赖字段较少的状况。最复杂的仍是数据一致性问题,这点很难保证,能够借助数据库中的触发器或者在业务代码层面去保证。固然,也须要结合实际业务场景来看一致性的要求。就像上面例子,若是卖家修改了Name以后,是否须要在订单信息中同步更新呢?

数据同步

定时A库中的tab_a表和B库中tbl_b有关联,能够定时将指定的表作同步。固然,同步原本会对数据库带来必定的影响,须要性能影响和数据时效性中取得一个平衡。这样来避免复杂的跨库查询。笔者曾经在项目中是经过ETL工具来实施的。

系统层组装

在系统层面,经过调用不一样模块的组件或者服务,获取到数据并进行字段拼装。提及来很容易,但实践起来可真没有这么简单,尤为是数据库设计上存在问题但又没法轻易调整的时候。

具体状况一般会比较复杂。下面笔者结合以往实际经验,并经过伪代码方式来描述。

简单的列表查询的状况

伪代码很容易理解,先获取“个人提问列表”数据,而后再根据列表中的UserId去循环调用依赖的用户服务获取到用户的RealName,拼装结果并返回。

有经验的读者一眼就能看出上诉伪代码存在效率问题。循环调用服务,可能会有循环RPC,循环查询数据库…不推荐使用。再看看改进后的:

这种实现方式,看起来要优雅一点,其实就是把循环调用改为一次调用。固然,用户服务的数据库查询中极可能是In查询,效率方面比上一种方式更高。(坊间流传In查询会全表扫描,存在性能问题,传闻不可全信。其实查询优化器都是基本成本估算的,通过测试,在In语句中条件字段有索引的时候,条件较少的状况是会走索引的。这里不细展开说明,感兴趣的朋友请自行测试)。

小结

简单字段组装的状况下,咱们只须要先获取“主表”数据,而后再根据关联关系,调用其余模块的组件或服务来获取依赖的其余字段(如例中依赖的用户信息),最后将数据进行组装。

一般,咱们都会经过缓存来避免频繁RPC通讯和数据库查询的开销。

列表查询带条件过滤的状况

在上述例子中,都是简单的字段组装,而不存在条件过滤。看拆分前的SQL:

这种链接查询而且还带条件过滤的状况,想在代码层面组装数据实际上是很是复杂的(尤为是左表和右表都带条件过滤的状况会更复杂),不能像以前例子中那样简单的进行组装了。试想一下,若是像上面那样简单的进行组装,形成的结果就是返回的数据不完整,不许确。 

有以下几种解决思路:

  1. 查出全部的问答数据,而后调用用户服务进行拼装数据,再根据过滤字段state字段进行过滤,最后进行排序和分页并返回。

    这种方式可以保证数据的准确性和完整性,可是性能影响很是大,不建议使用。

  2. 查询出state字段符合/不符合的UserId,在查询问答数据的时候使用in/not in进行过滤,排序,分页等。过滤出有效的问答数据后,再调用用户服务获取数据进行组装。

    这种方式明显更优雅点。笔者以前在某个项目的特殊场景中就是采用过这种方式实现。

跨库事务(分布式事务)的问题

按业务拆分数据库以后,不可避免的就是“分布式事务”的问题。以往在代码中经过spring注解简单配置就能实现事务的,如今则须要花很大的成本去保证一致性。这里不展开介绍, 
感兴趣的读者能够自行参考《分布式事务一致性解决方案》,连接地址: 
http://www.infoq.com/cn/articles/solution-of-distributed-system-transaction-consistency

垂直分库总结和实践建议

本篇中主要描述了几种常见的拆分方式,并着重介绍了垂直分库带来的一些问题和解决思路。读者朋友可能还有些问题和疑惑。

1. 咱们目前的数据库是否须要进行垂直分库?

根据系统架构和公司实际状况来,若是大家的系统仍是个简单的单体应用,而且没有什么访问量和数据量,那就别着急折腾“垂直分库”了,不然没有任何收益,也很难有好结果。

切记,“过分设计”和“过早优化”是不少架构师和技术人员常犯的毛病。

2. 垂直拆分有没有原则或者技巧?

没有什么黄金法则和标准答案。通常是参考系统的业务模块拆分来进行数据库的拆分。好比“用户服务”,对应的可能就是“用户数据库”。可是也不必定严格一一对应。有些状况下,数据库拆分的粒度可能会比系统拆分的粒度更粗。笔者也确实见过有些系统中的某些表本来应该放A库中的,却放在了B库中。有些库和表本来是能够合并的,却单独保存着。还有些表,看起来放在A库中也OK,放在B库中也合理。

如何设计和权衡,这个就看实际状况和架构师/开发人员的水平了。

3. 上面举例的都太简单了,咱们的后台报表系统中join的表都有n个了, 
分库后该怎么查?

有不少朋友跟我提过相似的问题。其实互联网的业务系统中,原本就应该尽可能避免join的,若是有多个join的,要么是设计不合理,要么是技术选型有误。请自行科普下OLAP和OLTP,报表类的系统在传统BI时代都是经过OLAP数据仓库去实现的(如今则更可能是借助离线分析、流式计算等手段实现),而不应向上面描述的那样直接在业务库中执行大量join和统计。

因为篇幅关系,下篇中咱们再继续细聊“水平分库分表”相关的话题。

做者介绍

丁浪,技术架构师。关注高并发、高可用的架构设计,对系统服务化、分库分表、性能调优等方面有深刻研究和丰富实践经验。热衷于技术研究和分享。

相关文章
相关标签/搜索