从业务场景看分库分表
互联网行业中,业务场景通常写少读多的情况居多,在MySQL的使用前期,读性能大多可以通过SQL优化来解决,但随着业务的持续发展,单纯依靠SQL的查询优化会越来越难以达到业务服务要求。
因此,量级较大的业务场景,MySQL的读压力往往会首先成为系统瓶颈所在。
此时,在数据库层面,DBA通常会建议通过横向扩展备库节点的方式,采用读写分离技术来提升业务系统的读性能、读并发能力。
以上是典型的互联网读写分离需求。
除了这种多见的读瓶颈问题,在大型网站和海量数据的业务场景,数据库常见的性能瓶颈下面的两地问题会更加突出:
一是大量的并发读写操作,导致单库出现负载压力过大;
二是单表存储数据量过大,导致查询效率低下。
该种情况,由于业务的读写请求较高,MySQL在主从之间的数据同步容易引发主从延迟问题。改进的做法是,我们可能会架构设计上,需要敦促业务在写入主库之前最好将同一份数据落到缓存,以避免高并发场景下从从库中获取不到指定数据的情况发生。
如果写压力进一步扩大,并且数据量急剧快速增长,DB写节点即主库就会成为整个系统的瓶颈。在MySQL的日常运营中,如果DB中表和表之间的数据很多是没有关系的,或者根本不需要表关联Join操作,我们可以考虑按照业务把不同的数据放到不同的服务器中,即垂直分库或叫垂直切分。
不过需要注意的是,垂直分库无法解决单表数据量过大的问题,由于单一业务的数据信息仍然落盘在单表中,如果单表数据量太大,就会极大地影响SQL执行的性能。
由此,在MySQL应用领域,水平分表也是互联网场景应对高并发、单表数据量过大的解决方案之一。
分表在本质上可以概括为业务表在逻辑上公用一个路由结构,物理上分散存储。这就是常说的Sharding分片或者分区。
比如以用户ID字段user_id按照一定策略(hash、range等),将表中数据拆分到多个子表中(分片或分区),以确保子表中数据量在读写性能可接受的范围内。每个子表的结构都一样,每个表的数据都不一样,没有交集,所有表的并集是全量数据。
水平分表主要用于业务架构无法继续垂直细分、数据库中单张表数据量太大、查询性能下降的场景。
应该使用哪一种方式来实施数据库分库分表,需要从数据库的瓶颈所在和项目的业务角度进行综合考虑。如果数据库是因为表太多而造成海量数据,并且项目的各项业务逻辑划分清晰、耦合度较低,建议优先使用垂直切分。
如果数据库中的表并不多,但单表的数据量很大且数据热度很高,这种情况之下就应该选择水平切分。
在现实项目中,往往是这两种情况兼而有之,综合使用了垂直与水平切分,我们首先对数据库进行垂直切分,然后针对一部分表进行水平切分。
但是,采用分库分表也会引入新的问题。
分库分表存在的问题及注意点
l跨库Join问题
分库分表后,表之间的关联操作将受到限制,无法join位于不同分库的表,也无法join分表粒度不同的表,结果导致原本一次查询能够完成的业务可能需要多次查询才能完成。
因此,分库分表的设计,需要结合业务数据的关联场景,适当考虑数据的冗余和拆分策略。比如:根据之前表之间的关系,将相关表以相同的拆分策略,确保关联数据存放到一个分片上。或者使用表级冗余将基础数据在所有库都写一份。使用字段冗余:把需要join的字段冗余在各个表中,这样有些字段就不用join去查询了。再者就是在应用逻辑层进行数据组装:将原来的请求分两次查询,在第一次查询的结果集中找出关联数据id,然后根据id发起第二次请求得到关联数据,最后将获得到的数据进行字段拼装。
l 排序合并问题
由于数据的分库、分片导致的分散存储,原来的业务请求会引发结果集合并、排序问题。尤其是当排序字段不是分片字段的似乎,问题会变得比更加复杂。
需要先在不同的分片节点中将数据进行排序并返回,然后将不同分片返回的结果集进行汇总和再次排序,最终返回给用户。
因此,分库分表,还需要考虑业务具体的排序场景,尽量保障排序字段和分布键一致,确保请求通过分片规则就比较容易定位到指定的分片。
l 迁移和扩容问题
迁移和扩容,属于DB日常运营中常规场景。分库分表后,如果数据采用的是数值范围range分片,那么我们只需要添加节点就可以进行扩容;但如果采用的是数值hash取模分片,由于扩容涉及数据重分布过程,扩容相对比较麻烦。
所以,分库分表,需要根据业务当前、预期的数据量、QPS来进行容量规划,推算
出大概需要多少分片,尽量减少后续扩容及迁移的发生。
l分布式事务问题
由于分库分表之后,业务需要跨库跨分片进行SQL请求,类似分布式事务的问题就会出现。为了确保事务的原子性,事务的提交需要协调多个节点,加大事务的执行时间及复杂性。
因此,对于性能要求很高,但对一致性要求不高的系统,在分库分表设计的实时候可能需要采用事务补偿的方式,将实时一致性转化为最终一致性,结合业务系统比如对数据进行对账检查、基于日志进行对比等进行事后补偿。
总 结
分库分表作为一种横向扩展的解决方案,问题还是比较明显的:
从运维侧来看:会极大的加大系统的复杂度、运维成本相对较高;
从业务侧来看:会极大增加开发编码工作量,并使业务逻辑复杂化。
所以,如果无需分片,则尽量避免分库分表;如果确实到了需要分库分表的情况,相对分库分表的通用方案,如果采用分布式中间件或原生分布式数据库,业务侧无需花大量的工作来处理如何分片和分片后的问题,有更多的时间原本该是专注于业务的应用。