分表的概念和策略
对于大型的互联网应用来说,数据库单表的记录行数可能达到千万级甚至是亿级,并且数据库面临着极高的并发访问。采用主从复制(Master-Slave)模式的MySQL架构,只能够对数据库的读进行扩展,而对数据库的写入操作还是集中在Master上,并且单个Master挂载的Slave也不可能无限制多,Slave的数量受到Master能力和负载的限制。因此就需要对数据库的吞吐能力进一步扩展,以满足高并发访问与海量数据存储的需要。
对于访问频繁且数据量巨大的单表(百万到千万级别)来说,我们首先需要做的是减少单表的记录条数,以便减少数据查询所需的时间,提高数据库的吞吐,这就是分表的概念,在分表之前,首先需要选择适当的分表策略,使得数据能够较为均衡地分布到多张表中,且不能影响正常的查询。
对于互联网企业来说,大部分数据都是与用户关联的,因此,用户id是最常用的分表字段。因为大部分查询都需要带上用户ID,这样的分表策略既不会影响正常查询,又能够使数据较为均衡地分布到各个表中(有些场景可能会出现冷热数据分配不均衡的情况)
假设有一个用来记录用户购买信息的订单表(ORDER),由于ORDER表中的记录条数太多,需要被拆分为256张表(拆分表的数量一般是2的N次方),拆分的规则是根据USER_ID%256取得对应的表存储记录。而前台应用则能根据USER_ID%256的规则去找到对应订单存储的表,再去存储该记录的表中取出数据(余数为0,则查0号表,余数为233,则查233号表)。这样以来,USER_ID便变成了一个必须的查询条件,否则将会因为无法定位数据存储的表而无法对数据进行访问。
这时,如果要访问USER_ID是257的订单记录,则根据USER_ID%256的规则得到要访问的表是ORDER_1。这里可能会有个疑问,就是如果257这个用户特别有钱,下了一亿个订单,ORDER_1表岂不是又数据量过大了。当然这种情况现实是不可能出现的,但是这个疑问可以推出一个观点,就是说具体的分表策略是要根据实际情况来制定的。
分库的概念和策略
分表能够解决单表数据量过大带来的查询效率下降的问题,但是却无法给数据库的并发处理能力带来质的提升。面对高并发的读写访问,当数据库Master服务器无法承载写操作压力时,不管如何扩展Slave服务器,都没有什么意义了。因此必须换一种思路:既然拆分多个表不行了,那就拆分多个数据库好了,聚集多个单数据库的写入能力提高整体的写入能力,这就是分库的概念。
与分表策略类似,分库也可以采用通过一个关键字取模的方式来对数据访问进行路由。比如还是之前的订单表,假设USER_ID字段的值是258,将原有的单库分为256个库,那么应用程序对数据库的访问请求将被路由到第二个库(258%256 = 2)
分库分表的概念和策略
大多数时候,数据库是会同时面临高并发访问的压力和海量数据的存储问题的。这时候就需要采用分表策略,又要采用分库策略,以便能既扩展系统的并发处理能力,又提升单表的查询性能。这种分库和分表联合使用的方式,就是分库分表的概念。
分库分表的策略比起仅分库或仅分表的策略要更为复杂,一种分库分表的路由策略如下:
1.中间变量=USER_ID%(分库数量*每个库的表数量)
2.库=取整数(中间变量/每个库的表数量)
3.表=中间变量%每个库的表数量
同样是订单表,同样采用用户ID作为路由字段,首先使用USER_ID对库数量*每个库表的数量取模,得到一个中间变量;然后使用中间变量除以每个库表的数量,取整,便得到对应的库;而中间变量对每个库表的数量取模,即得到对应的表。
假设将原来的单库单表ORDER表拆分为256个库,每个库包含1024个表,那么按照前面所提到的路由策略,对于USER_ID=262145的访问,路由的计算过程如下:
1.中间变量=262145%(256*1024)=1
2.库=(1/1024)取整=0
3.表=1%1024=1
因此得出,对于USER_ID=262145的订单记录的查询和修改,将会被路由到第0个库的第1个ORDER_1表中执行。
垂直(纵向)切分
垂直分库就是根据业务耦合性,将关联度低的不同表存储在不同的数据库。做法与大系统拆分为多个小系统类似,按业务分类进行独立划分。与 “微服务治理” 的做法相似,每个微服务使用单独的一个数据库。如图:
垂直分表是基于数据库中的 “列” 进行,某个表字段较多,可以新建一张扩展表,将不经常用或字段长度较大的字段拆分出去到扩展表中。在字段很多的情况下 (例如一个大表有 100 多个字段),通过 “大表拆小表”,更便于开发与维护,也能避免跨页问题,MySQL 底层是通过数据页存储的,一条记录占用空间过大会导致跨页,造成额外的性能开销。另外数据库以行为单位将数据加载到内存中,这样表中字段长度较短且访问频率较高,内存能加载更多的数据,命中率更高,减少了磁盘 IO,从而提升了数据库性能。
垂直切分的优点:
解决业务系统层面的耦合,业务清晰
与微服务的治理类似,也能对不同业务的数据进行分级管理、维护、监控、扩展等
高并发场景下,垂直切分一定程度的提升 IO、数据库连接数、单机硬件资源的瓶颈
缺点:
部分表无法 join,只能通过接口聚合方式解决,提升了开发的复杂度
分布式事务处理复杂
依然存在单表数据量过大的问题 (需要水平切分)
水平(横向)切分
当一个应用难以再细粒度的垂直切分,或切分后数据量行数巨大,存在单库读写、存储性能瓶颈,这时候就需要进行水平切分了。
水平切分分为库内分表和分库分表,是根据表内数据内在的逻辑关系,将同一个表按不同的条件分散到多个数据库或多个表中,每个表中只包含一部分数据,从而使得单个表的数据量变小,达到分布式的效果。如图所示:
库内分表只解决了单一表数据量过大的问题,但没有将表分布到不同机器的库上,因此对于减轻 MySQL 数据库的压力来说,帮助不是很大,大家还是竞争同一个物理机的 CPU、内存、网络 IO,最好通过分库分表来解决。
水平切分的优点:
不存在单库数据量过大、高并发的性能瓶颈,提升系统稳定性和负载能力
应用端改造较小,不需要拆分业务模块
缺点:
跨分片的事务一致性难以保证
跨库的 join 关联查询性能较差
数据多次扩展难度和维护量极大