分库分表
为什么要使用分库分表?什么时候使用分库分表?
关系型数据库本身比较容易成为系统性能的瓶颈,虽然读写分离能分散数据库的读写压力,但并没有分散存储压力,当数据量达到千万甚至上亿时,单台数据库服务器的存储能力会成为系统的瓶颈,主要体现在以下几个方面:
- 数据量太大,读写的性能会下降,即使有索引,索引也会变得很大,性能同样会下降
- 数据库文件会得很大,数据库备份和恢复耗时很长
- 数据库文件越大,极端情况下数据丢失的风险越高。发生故障时,比如一个数据库文件损坏、宕机,丢失的数据量都是很大的。
因此,当并发量越来越大、且单机容量达到上限时,往往需要分库分表,减轻单机数据库的存储压力、读写压力,由多台数据库服务器一起分担压力,提高数据库性能。
切分策略
有2种切分策略:水平切分、垂直(纵向)切分。
垂直切分
(2)垂直分表:如果某个表字段较多,可以新建一张扩展表,将不经常用(冷热分离)或者字段长度较大的字段放到扩展表中。
在字段很多的情况下,通过大表拆小表,更方便开发、维护,也能避免跨页问题。
mysql底层是通过数据页存储数据的,一条记录占用空间过大会导致跨页,造成额外的开销。
另外,数据库以行为单位将数据加载到内存中,表中字段长度越短且访问频次较高,内存能加载更多的数据、命中率更高(缓存),减少磁盘IO,从而提升数据库性能。
垂直切分是以整表、整列为单位进行拆分。
垂直切分的优点
- 解决业务层面的耦合,业务逻辑清晰
- 与微服务的治理类似,也能对不同业务的数据进行分级管理,维护,监控,扩展等
- 高并发场景下,垂直切分可以提升数据库系统IO性能,提高数据库整体的连接数,解决单机硬件资源不足导致的性能瓶颈
垂直切分的缺点
- 不同数据库的表之间的连接查询(join)稍微变得复杂,需要在表名前加上数据库名,且跨库的连接查询性能较差
- 分布式事务的处理变得复杂
- 可能依然存在单表数据量过大的问题。列是少了,但单表的记录数并没有减少
水平切分
当难以再进行细粒度的垂直切分来提升性能、或单表的记录数巨大,这时可以进行水平切分。
按拆分出来的表的存放位置来分,水平切分可分为2种:
(1)库内分表 将一个表的记录拆分为多个表,每个表中只包含部分记录,使得单表的记录数变少,拆分出来的表放在同一数据库中
(2)分库分表 拆分出来的表放在不同不同的数据库中
库内分表只解决了单一表记录数过大的问题,但没有将表分布到不同的库(机器)上,拆分出来的表还是竞争同一个机器的CPU、内存、网络IO,对于减轻数据库存储压力来说帮助不是很大,水平切分最好选择分库分表。
水分切分是以记录为单位进行拆分。
水平切分的优点
- 解决了单表记录数、数据量过大的问题,提升了数据库系统稳定性、负载能力
- 应用端改造较小,不需要拆分业务模块
水平切分的缺点
- 难以保证跨分片的事务的一致性
- 跨库的join关联查询性能较差
- 数据表维护起来很麻烦。原本只需维护一张表,现在要维护拆分出来的多张表
水平切分的路由配置
水平切分后原本一张表的记录会分散到一个数据库或多个数据库的多张表中,操作数据库时怎么知道哪条记录在哪个库里的哪个表里?水平切分需要配置路由。
水平切分按记录切分方式可分为2种:
(1)范围拆分
常见的,比如根据原表的id字段值的范围进行划分,1 ~ 9999999放到一张表中,10000000~199999999放到一张表中,以此累推。优、缺点
- 扩容方便。随着数据的增加,可以直接使用新表来存储数据,原有的数据不需要动 (优)
- 单表大小可控(优)
- 使用分片字段进行范围查找时,可快速定位到记录。比如查询id 1~100上记录,在同一个表中,很快就定位到了。(优)
- 热点数据可能会成为性能瓶颈。连续分片可能存在热点数据(频繁被读写的数据),热点数据没有单独划分出来。(缺)
(2)hash值
选取某个列或几个列的值进行hash运算,根据得到的hash值来划分记录到到不同的表。
比如以id列进行hash运算,拆分为10个表,计算(id % 10)的hash值,余数为0的都划到第一个表中,为1的都划到第二个表中.....为9的划到第10个表中,不推荐使用这种方式,查询起来不方便。
比如以商品信息表以商品类型(有限个数的常量)这一列计算hash值,把同一商品类型的所有商品记录划到一个表中,eg.手机类商品划到一个表中,电脑类商品划到一个表中,智能穿戴类商品划到一个表中.....
这种方式的关键在于初始表数量的选取上,表数量太多维护起来很麻烦,表数量太小又解决不了单表性能存在问题。
水平分表的关键在于子表数量的选取。
优缺点与范围划分的刚好相反:
- 扩容麻烦,扩容时所有数据需要重新分布(缺)。比如原来根据id拆分为10表,现在记录数剧增,要重新划分为20个表,所有数据都要重新划分、分布,很麻烦。当然根据商品类型这种值是有限常量来划分的,不存在这个问题。
- 单表大小不可控(缺)
- 范围查找性能差(缺)。比如查找id在1-100上的记录,这些记录分布在10个表中,要分别去10张表中找!
- 分布均匀(优)。分布均匀有2层含义:1、比如按id划分,每个表的记录数是差不多,记录数划分得比较均匀;2、可以把热点数据划出来,比如按商品类型来分,大家都关注手机、电脑,这些热点数据单独在一些表中,至于杯盏、文玩之类的没多少人关注的,又单独在一些表中,一张表中的数据几乎都是热点数据或非热点数据,比较均匀。
路由表
新建一张数据表作为路由表,一列存储划分出来的表的表名,
如果是以范围划分的,min_index、max_index2列分别存储该表的范围,比如[1,9999999];如果是以有限的常量来划分的,则用一列来存储该表对应的常量,比如存储商品类型。
水平分表,扩充表的时候,需要修改路由表;
操作数据库的时候,需要先查询路由表得到记录所在的表,要多查询一次,会影响性能,所以除非单表的记录数真的很多,否则不要轻易水平分表。
就算要水平分表,子表数量也不宜太多,一个是难以维护,另一个是字表太多会导致路由表中的记录数很多,查询路由表的时间开销会变大;如果路由表记录数很多,再将路由表分库分表,则又面临一个死循环。
垂直切分,表的结构一旦设计好,一般不会变化,比如将tb_user垂直划分为3个表,列一般不会增减,就算增加列,也是加在业务相关性大的子表中,代码修改幅度不大,我们可以直接在代码中写死拆分出来的子表名,不必使用路由表。
水平切分,记录数是经常变化的,经常要扩容,子表数量往往要增加,不能在代码中写死子表名,要使用路由表做中间人。
分库分表带来的问题
分表后,原本一张表中的记录分散到多张表中,很多时候需要多表连接查询,连接其他表是要花时间的;尤其是分库导致要跨库查询时,连接另一个数据库中的表,很花时间,效率低。
聚合函数、order by排序、group by分组使用起来也不是很方便,都要从多个子表中查数据,然后汇总。
分库分表,效率有提升的地方,也有拉低的地方;
分库分表主要是为了减轻单机数据库的存储压力,效率还再其次。