MySQL分库分表介绍
什么是分库分表
分库分表是在海量数据下,由于单库、表数据量过大,导致数据库性能持续下降的问题,演变出的技术方案。
分库分表是由分库
和分表
这两个独立概念组成的,只不过通常分库与分表的操作会同时进行,以至于我们习惯性的将它们合在一起叫做分库分表。
通过一定的规则,将原本数据量大的数据库拆分成多个单独的数据库,将原本数据量大的表拆分成若干个数据表,使得单一的库、表性能达到最优的效果(响应速度快),以此提升整体数据库性能。
为什么需要分库分表
单机数据库的存储能力、连接数是有限的,它自身就很容易会成为系统的瓶颈。当单表数据量在百万以里时,我们还可以通过添加从库、优化索引提升性能。一旦数据量朝着千万以上趋势增长,再怎么优化数据库,很多操作性能仍下降严重。为了减少数据库的负担,提升数据库响应速度,缩短查询时间,这时候就需要进行分库分表。
在分库分表之前,就需要考虑为什么需要拆分。我们做一件事,肯定是有充分理由的。所以得想好分库分表的理由是什么。我们现在就从两个维度去思考它,为什么要分库?为什么要分表?
1 为什么要分库
如果业务量剧增,数据库可能会出现性能瓶颈,这时候我们就需要考虑拆分数据库。从这两方面来看:
- 磁盘存储、容量问题
业务量剧增,MySQL单机磁盘容量会撑爆,拆成多个数据库,磁盘使用率大大降低。我们给数据库实例分配的磁盘容量是固定的,数据量持续的大幅增长,用不了多久单机的容量就会承载不了这么多数据,解决办法简单粗暴,加容量!
- 并发连接支撑
我们知道数据库连接数是有限的。在高并发的场景下,大量请求访问数据库,MySQL单机是扛不住的!高并发场景下,会出现too many connections
报错。
单机的容量可以随意扩展,但数据库的连接数却是有限的,在高并发场景下多个业务同时对一个数据库操作,很容易将连接数耗尽导致too many connections
报错,导致后续数据库无法正常访问。
可以通过max_connections
查看MySQL最大连接数。
1 | show variables like '%max_connections%' |
将原本单数据库按不同业务拆分成订单库、物流库、积分库等不仅可以有效分摊数据库读写压力,也提高了系统容错性。
当前非常火的微服务架构出现,就是为了应对高并发。它把订单、用户、商品等不同模块,拆分成多个应用,并且把单个数据库也拆分成多个不同功能模块的数据库(订单库、用户库、商品库),以分担读写压力。
2 为什么要分表
假如你的单表数据量非常大,存储和查询的性能就会遇到瓶颈了,如果你做了很多优化之后还是无法提升效率的时候,就需要考虑做分表了。一般千万级别数据量,就需要分表。
这是因为即使SQL
命中了索引,如果表的数据量超过一千万的话,查询也是会明显变慢的。这是因为索引一般是B+
树结构,数据千万级别的话,B+树的高度会增高,查询就变慢啦。MySQL的B+树的高度怎么计算的呢?跟大家复习一下:
InnoDB存储引擎最小储存单元是页,一页大小就是16k。B+树叶子存的是数据,内部节点存的是键值+指针。索引组织表通过非叶子节点的二分查找法以及指针确定数据在哪个页中,进而再去数据页中找到需要的数据,B+树结构图如下:
假设B+树的高度为2的话,即有一个根结点和若干个叶子结点。这棵B+树的存放总记录数为=根结点指针数*单个叶子节点记录行数。
如果一行记录的数据大小为1k,那么单个叶子节点可以存的记录数 =16k/1k =16. 非叶子节点内存放多少指针呢?我们假设主键ID为bigint类型,长度为8字节(面试官问你int类型,一个int就是32位,4字节),而指针大小在InnoDB源码中设置为6字节,所以就是 8+6=14 字节,16k/14B =16*1024B/14B = 1170
因此,一棵高度为2的B+树,能存放1170 16=18720条这样的数据记录。同理一棵高度为3的B+树,能存放1170 1170 *16 =21902400,大概可以存放两千万左右的记录。B+树高度一般为1-3层,如果B+到了4层,查询的时候会多查磁盘的次数,SQL就会变慢。
因此单表数据量太大,SQL查询会变慢,所以就需要考虑分表啦。
一个没有进行分库分表的示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | MySQL [(none)]> select count(*) from xxt_backup.sensor; +------------+ | count(*) | +------------+ | 2367966510 | +------------+ 1 row in set (48 min 33.07 sec) MySQL [(none)]> desc xxt_backup.sensor -> ; +-------------+------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-------------+------------+------+-----+---------+----------------+ | id | bigint(20) | NO | PRI | NULL | auto_increment | | record_date | datetime | YES | MUL | NULL | | | value | double | YES | MUL | NULL | | | sid | int(11) | YES | MUL | NULL | | | gid | int(11) | YES | | NULL | | +-------------+------------+------+-----+---------+----------------+ 5 rows in set (0.17 sec) MySQL [(none)]> show indexes from xxt_backup.sensor; -> ; +--------+------------+-------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+ | Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | +--------+------------+-------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+ | sensor | 0 | PRIMARY | 1 | id | A | 2284530432 | NULL | NULL | | BTREE | | | | sensor | 1 | sensor_search | 1 | sid | A | 113562 | NULL | NULL | YES | BTREE | | | | sensor | 1 | sensor_search | 2 | gid | A | 3942968 | NULL | NULL | YES | BTREE | | | | sensor | 1 | sensor_search | 3 | record_date | A | 2318465024 | NULL | NULL | YES | BTREE | | | | sensor | 1 | value_index | 1 | value | A | 51657928 | NULL | NULL | YES | BTREE | | | | sensor | 1 | record_date_index | 1 | record_date | A | 2843846 | NULL | NULL | YES | BTREE | | | +--------+------------+-------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+ 6 rows in set (0.03 sec) |
23亿行的表做count操作需要50分钟!!!!
什么时候考虑分库分表?
存储占用100G+
数据增量每天200w+
单表条数1亿条+
对于MySQL InnoDB
存储引擎的话,若数据量存储很多(2000万行),性能就会非常差。一般数据量千万级别,B+
树索引高度就会到3
层以上了,查询的时候会多查磁盘的次数,SQL
就会变慢。
阿里巴巴的《Java开发手册》
提出:
单表行数超过
500万
行或者单表容量超过2GB
,才推荐进行分库分表。
那我们是不是等到数据量到达五百万,才开始分库分表呢?
不是这样的,我们应该提前规划分库分表,如果估算
3
年后,你的表都不会到达这个五百万,则不需要分库分表。
MySQL服务器如果配置更好,是不是可以超过这个500万这个量级,才考虑分库分表?
虽然配置更好,可能数据量大之后,性能还是不错,但是如果持续发展的话,还是要考虑分库分表
一般什么类型业务表需要才分库分表?
通用是一些流水表、用户表等才考虑分库分表,如果是一些配置类的表,则完全不用考虑,因为不太可能到达这个量级。
如何分库分表
分库分表的核心就是对数据的分片(
Sharding
)并相对均匀的路由在不同的库、表中,以及分片后对数据的快速定位与检索结果的整合。
分库与分表可以从:垂直(纵向)和 水平(横向)两种纬度进行拆分。下边我们以经典的订单业务举例,看看如何拆分。
垂直拆分
(1)垂直分库
垂直分库一般来说按照业务和功能的维度进行拆分,将不同业务数据分别放到不同的数据库中,核心理念 专库专用
。
按业务类型对数据分离,剥离为多个数据库,像订单、支付、会员、积分相关等表放在对应的订单库、支付库、会员库、积分库。不同业务禁止跨库直连,获取对方业务数据一律通过API
接口交互,这也是微服务拆分的一个重要依据。
垂直分库很大程度上取决于业务的划分,但有时候业务间的划分并不是那么清晰,比如:电商中订单数据的拆分,其他很多业务都依赖于订单数据,有时候界线不是很好划分。
垂直分库把一个库的压力分摊到多个库,提升了一些数据库性能,但并没有解决由于单表数据量过大导致的性能问题,所以就需要配合后边的分表来解决。
(2)垂直分表
垂直分表针对业务上字段比较多的大表进行的,一般是把业务宽表中比较独立的字段,或者不常用的字段拆分到单独的数据表中,是一种大表拆小表的模式。
例如:一张t_order
订单表上有几十个字段,其中订单金额相关字段计算频繁,为了不影响订单表t_order
的性能,就可以把订单金额相关字段拆出来单独维护一个t_order_price_expansion
扩展表,这样每张表只存储原表的一部分字段,通过订单号order_no
做关联,再将拆分出来的表路由到不同的库中。
数据库它是以行为单位将数据加载到内存中,这样拆分以后核心表大多是访问频率较高的字段,而且字段长度也都较短,因而可以加载更多数据到内存中,减少磁盘IO,增加索引查询的命中率,进一步提升数据库性能。
水平拆分
上边垂直分库、垂直分表后还是会存在单库、表数据量过大的问题,当我们的应用已经无法在细粒度的垂直切分时,依旧存在单库读写、存储性能瓶颈,这时就要配合水平分库、水平分表一起了。
(1)水平分库
水平分库是把同一个表按一定规则拆分到不同的数据库中,每个库可以位于不同的服务器上,以此实现水平扩展,是一种常见的提升数据库性能的方式。
例如:db_orde_1
、db_order_2
两个数据库内有完全相同的t_order
表,我们在访问某一笔订单时可以通过对订单的订单编号取模的方式 订单编号 mod 2 (数据库实例数)
,指定该订单应该在哪个数据库中操作。
这种方案往往能解决单库存储量及性能瓶颈问题,但由于同一个表被分配在不同的数据库中,数据的访问需要额外的路由工作,因此系统的复杂度也被提升了。
(2)水平分表
水平分表是在同一个数据库内,把一张大数据量的表按一定规则,切分成多个结构完全相同表,而每个表只存原表的一部分数据。
例如:一张t_order
订单表有900万数据,经过水平拆分出来三个表,t_order_1
、t_order_2
、t_order_3
,每张表存有数据300万,以此类推。
水平分表尽管拆分了表,但子表都还是在同一个数据库实例中,只是解决了单一表数据量过大的问题,并没有将拆分后的表分散到不同的机器上,还在竞争同一个物理机的CPU、内存、网络IO等。要想进一步提升性能,就需要将拆分后的表分散到不同的数据库中,达到分布式的效果。
数据存在哪个库的表
分库分表以后会出现一个问题,一张表会出现在多个数据库里,到底该往哪个库的哪个表里存呢?
上边我们多次提到过一定规则
,其实这个规则它是一种路由算法,决定了一条数据具体应该存在哪个数据库的哪张表里。
常见的有 取模算法
、范围限定算法
、范围+取模算法
、预定义算法
1、取模算法
关键字段取模(对hash结果取余数 hash(XXX) mod N),N为数据库实例数或子表数量)是最为常见的一种路由方式。
以t_order
订单表为例,先给数据库从 0 到 N-1进行编号,对 t_order
订单表中order_no
订单编号字段进行取模hash(order_no) mod N
,得到余数i
。i=0
存第一个库,i=1
存第二个库,i=2
存第三个库,以此类推。
同一笔订单数据会落在同一个库、表里,查询时用相同的规则,用t_order
订单编号作为查询条件,就能快速的定位到数据。
优点
实现简单,数据分布相对比较均匀,不易出现请求都打到一个库上的情况。
缺点
取模算法对集群的伸缩支持不太友好,集群中有N个数据库实·hash(user_id) mod N
,当某一台机器宕机,本应该落在该数据库的请求就无法得到处理,这时宕掉的实例会被踢出集群。
此时机器数减少算法发生变化hash(user_id) mod N-1
,同一用户数据落在了在不同数据库中,等这台机器恢复,用user_id
作为条件查询用户数据就会少一部分。
2、范围限定算法
范围限定算法以某些范围字段,如时间
或ID区
拆分。
用户表t_user
被拆分成t_user_1
、t_user_2
、t_user_3
三张表,后续将user_id
范围为1 ~ 1000w的用户数据放入t_user_1
,1000~ 2000w放入t_user_2
,2000~3000w放入t_user_3
,以此类推。按日期范围划分同理。
优点
- 单表数据量是可控的
- 水平扩展简单只需增加节点即可,无需对其他分片的数据进行迁移
缺点
- 由于连续分片可能存在
数据热点
,比如按时间字段分片时,如果某一段时间(双11等大促)订单骤增,存11月数据的表可能会被频繁的读写,其他分片表存储的历史数据则很少被查询,导致数据倾斜,数据库压力分摊不均匀。
3、范围 + 取模算法
为了避免热点数据的问题,我们可以对上范围算法优化一下
这次我们先通过范围算法定义每个库的用户表t_user
只存1000w数据,第一个db_order_1
库存放userId
从1 ~ 1000w,第二个库1000~2000w,第三个库2000~3000w,以此类推。
每个库里再把用户表t_user
拆分成t_user_1
、t_user_2
、t_user_3
等,对userd
进行取模路由到对应的表中。
有效的避免数据分布不均匀的问题,数据库水平扩展也简单,直接添加实例无需迁移历史数据。
4、地理位置分片
地理位置分片其实是一个更大的范围,按城市或者地域划分,比如华东、华北数据放在不同的分片库、表。
5、预定义算法
预定义算法是事先已经明确知道分库和分表的数量,可以直接将某类数据路由到指定库或表中,查询的时候亦是如此。
分库分表后带来的问题
了解了上边分库分表的拆分方式不难发现,相比于拆分前的单库单表,系统的数据存储架构演变到现在已经变得非常复杂。看几个具有代表性的问题,比如:
分页、排序、跨节点联合查询
分页、排序、联合查询,这些看似普通,开发中使用频率较高的操作,在分库分表后却是让人非常头疼的问题。把分散在不同库中表的数据查询出来,再将所有结果进行汇总合并整理后提供给用户。
比如:我们要查询11、12月的订单数据,如果两个月的数据是分散到了不同的数据库实例,则要查询两个数据库相关的数据,在对数据合并排序、分页,过程繁琐复杂。
事务一致性
分库分表后由于表分布在不同库中,不可避免会带来跨库事务问题。后续会分别以阿里的Seata
和MySQL的XA
协议实现分布式事务,用来比较各自的优势与不足。
全局唯一的主键
分库分表后数据库表的主键ID业务意义就不大了,因为无法在标识唯一一条记录,例如:多张表t_order_1
、t_order_2
的主键ID全部从1开始会重复,此时我们需要主动为一条记录分配一个ID,这个全局唯一的ID就叫分布式ID
,发放这个ID的系统通常被叫发号器。
多数据库高效治理
对多个数据库以及库内大量分片表的高效治理,是非常有必要,因为像某宝这种大厂一次大促下来,订单表可能会被拆分成成千上万个t_order_n
表,如果没有高效的管理方案,手动建表、排查问题是一件很恐怖的事。
历史数据迁移
分库分表架构落地以后,首要的问题就是如何平滑的迁移历史数据,增量数据和全量数据迁移,这又是一个比较麻烦的事情,后边详细讲。
分库分表架构模式
分库分表架构主要有两种模式:client
客户端模式和proxy
代理模式
客户模式
client
模式指分库分表的逻辑都在你的系统应用内部进行控制,应用会将拆分后的SQL直连多个数据库进行操作,然后本地进行数据的合并汇总等操作。
代理模式
proxy
代理模式将应用程序与MySQL数据库隔离,业务方的应用不在需要直连数据库,而是连接proxy代理服务,代理服务实现了MySQL的协议,对业务方来说代理服务就是数据库,它会将SQL分发到具体的数据库进行执行,并返回结果。该服务内有分库分表的配置,根据配置自动创建分片表。
如何抉择
如何选择client
模式和proxy
模式,我们可以从以下几个方面来简单做下比较。
(1)性能
性能方面client
模式表现的稍好一些,它是直接连接MySQL执行命令;proxy
代理服务则将整个执行链路延长了,应用->代理服务->MySQL,可能导致性能有一些损耗,但两者差距并不是非常大。
(2)复杂度
client
模式在开发使用通常引入一个jar可以;proxy
代理模式则需要搭建单独的服务,有一定的维护成本,既然是服务那么就要考虑高可用,毕竟应用的所有SQL都要通过它转发至MySQL。
(3)升级
client
模式分库分表一般是依赖基础架构团队的Jar包,一旦有版本升级或者Bug修改,所有应用到的项目都要跟着升级。小规模的团队服务少升级问题不大,如果是大公司服务规模大,且涉及到跨多部门,那么升级一次成本就比较高;
proxy
模式在升级方面优势很明显,发布新功能或者修复Bug,只要重新部署代理服务集群即可,业务方是无感知的,但要保证发布过程中服务的可用性。
(4)治理、监控
client
模式由于是内嵌在应用内,应用集群部署不太方便统一处理;proxy
模式在对SQL限流、读写权限控制、监控、告警等服务治理方面更优雅一些。
如何选择分表键
分表键,即用来分库/分表的字段,换种说法就是,你以哪个维度来分库分表的。比如你按用户ID分表、按时间分表、按地区分表,这些用户ID、时间、地区就是分表键。
一般数据库表拆分的原则,需要先找到业务的主题。比如你的数据库表是一张企业客户信息表,就可以考虑用了客户号做为分表键
。
为什么考虑用客户号做分表键呢?
这是因为表是基于客户信息的,所以,需要将同一个客户信息的数据,落到一个表中,避免触发全表路由。
非分表键如何查询
分库分表后,有时候无法避免一些业务场景,需要通过非分表键来查询。
假设一张用户表,根据userId
做分表键,来分库分表。但是用户登录时,需要根据用户手机号来登陆。这时候,就需要通过手机号查询用户信息。而手机号是非分表键。
非分表键查询,一般有这几种方案:
- 遍历:最粗暴的方法,就是遍历所有的表,找出符合条件的手机号记录(不建议)
- 将用户信息冗余同步到ES,同步发送到ES,然后通过ES来查询(推荐)
其实还有基因法:比如非分表键可以解析出分表键出来,比如常见的,订单号生成时,可以包含客户号进去,通过订单号查询,就可以解析出客户号。但是这个场景除外,手机号似乎不适合冗余userId。
分表策略如何选择
1 range范围
range
,即范围策略划分表。比如我们可以将表的主键order_id
,按照从0~300万
的划分为一个表,300万~600万
划分到另外一个表。如下图:
有时候我们也可以按时间范围来划分,如不同年月的订单放到不同的表,它也是一种range
的划分策略。
- 优点:
range
范围分表,有利于扩容。 - 缺点:可能会有热点问题。因为
订单id
是一直在增大的,也就是说最近一段时间都是汇聚在一张表里面的。比如最近一个月的订单都在300万~600万
之间,平时用户一般都查最近一个月的订单比较多,请求都打到order_1
表啦。
2 hash取模
hash取模策略:
指定的路由key(一般是
user_id、order_id、customer_no
作为key)对分表总数进行取模,把数据分散到各个表中。
比如原始订单表信息,我们把它分成4张分表:
- 比如id=1,对4取模,就会得到1,就把它放到
t_order_1
; - id=3,对4取模,就会得到3,就把它放到
t_order_3;
一般,我们会取哈希值,再做取余:
1 | Math.abs(orderId.hashCode()) % table_number |
- 优点:hash取模的方式,不会存在明显的热点问题。
- 缺点:如果未来某个时候,表数据量又到瓶颈了,需要扩容,就比较麻烦。所以一般建议提前规划好,一次性分够。(可以考虑一致性哈希)
3 一致性Hash
如果用hash方式分表,前期规划不好,需要扩容二次分表,表的数量需要增加,所以hash值需要重新计算,这时候需要迁移数据了。
比如我们开始分了
10
张表,之后业务扩展需要,增加到20
张表。那问题就来了,之前根据orderId
取模10
后的数据分散在了各个表中,现在需要重新对所有数据重新取模20
来分配数据
为了解决这个扩容迁移问题,可以使用一致性hash思想来解决。
一致性哈希:在移除或者添加一个服务器时,能够尽可能小地改变已存在的服务请求与处理请求服务器之间的映射关系。一致性哈希解决了简单哈希算法在分布式哈希表存在的动态伸缩等问题
如何避免热点问题数据倾斜(热点数据)
如果我们根据时间范围分片,某电商公司11
月搞营销活动,那么大部分的数据都落在11
月份的表里面了,其他分片表可能很少被查询,即数据倾斜了,有热点数据问题了。
我们可以使用range范围+ hash哈希取模
结合的分表策略,简单的做法就是:
在拆分库的时候,我们可以先用range范围方案,比如订单id在
0~4000万
的区间,划分为订单库1;id在4000万~8000万
的数据,划分到订单库2
,将来要扩容时,id在8000万~1.2亿
的数据,划分到订单库3。然后订单库内,再用hash取模
的策略,把不同订单划分到不同的表。
分库后,事务问题如何解决
分库分表后,假设两个表在不同的数据库,那么本地事务已经无效啦,需要使用分布式事务了。
常用的分布式事务解决方案有:
- 两阶段提交
- 三阶段提交
- TCC
- 本地消息表
- 最大努力通知
- saga
大家可以看下这几篇文章:
跨节点Join关联问题
在单库未拆分表之前,我们如果要使用join
关联多张表操作的话,简直so easy
啦。但是分库分表之后,两张表可能都不在同一个数据库中了,那么如何跨库join
操作呢?
跨库Join的几种解决思路:
- 字段冗余:把需要关联的字段放入主表中,避免关联操作;比如订单表保存了卖家ID(
sellerId
),你把卖家名字sellerName
也保存到订单表,这就不用去关联卖家表了。这是一种空间换时间的思想。 - 全局表:比如系统中所有模块都可能会依赖到的一些基础表(即全局表),在每个数据库中均保存一份。
- 数据抽象同步:比如A库中的a表和B库中的b表有关联,可以定时将指定的表做同步,将数据汇合聚集,生成新的表。一般可以借助
ETL
工具。 - 应用层代码组装:分开多次查询,调用不同模块服务,获取到数据后,代码层进行字段计算拼装。
order by,group by等聚合函数问题
跨节点的count,order by,group by
以及聚合函数等问题,都是一类的问题,它们一般都需要基于全部数据集合进行计算。可以分别在各个节点上得到结果后,再在应用程序端进行合并。
分库分表后的分页问题
- 方案1(全局视野法):在各个数据库节点查到对应结果后,在代码端汇聚再分页。这样优点是业务无损,精准返回所需数据;缺点则是会返回过多数据,增大网络传输
比如分库分表前,你是根据创建时间排序,然后获取第2页数据。如果你是分了两个库,那你就可以每个库都根据时间排序,然后都返回2页数据,然后把两个数据库查询回来的数据汇总,再根据创建时间进行内存排序,最后再取第2页的数据。
- 方案2(业务折衷法-禁止跳页查询):这种方案需要业务妥协一下,只有上一页和下一页,不允许跳页查询了。
这种方案,查询第一页时,是跟全局视野法一样的。但是下一页时,需要把当前最大的创建时间传过来,然后每个节点,都查询大于创建时间的一页数据,接着汇总,内存排序返回。
分布式ID
数据库被切分后,不能再依赖数据库自身的主键生成机制啦,最简单可以考虑UUID
,或者使用雪花算法生成分布式ID
。
雪花算法是一种生成分布式全局唯一ID的算法,生成的ID称为
Snowflake IDs
。这种算法由
一个Snowflake ID
有64
位。
- 第
1
位:Java中long的最高位是符号位代表正负,正数是0,负数是1,一般生成ID都为正数,所以默认为0。 - 接下来前
41
位是时间戳,表示了自选定的时期以来的毫秒数。 - 接下来的
10
位代表计算机ID,防止冲突。 - 其余
12
位代表每台机器上生成ID的序列号,这允许在同一毫秒内创建多个Snowflake ID。
分库分表选择哪种中间件
目前流行的分库分表中间件比较多:
- Sharding-JDBC
- cobar
- Mycat
- Atlas
- TDDL(淘宝)
- vitess
我们项目当前就是使用Sharding-JDBC
实现的分库分表。
如何评估分库数量
- 对于MySQL来说的话,一般单库超过
5千万
记录,DB
的压力就非常大了。所以分库数量多少,需要看单库处理记录能力。 - 如果分库数量少,达不到分散存储和减轻
DB
性能压力的目的;如果分库的数量多,对于跨多个库的访问,应用程序需要访问多个库。 - 一般是建议分
4~10
个库,我们公司的企业客户信息,就分了10
个库。
垂直分库、水平分库、垂直分表、水平分表的区别
- 水平分库:以字段为依据,按照一定策略(hash、range等),将一个库中的数据拆分到多个库中。
- 水平分表:以字段为依据,按照一定策略(hash、range等),将一个表中的数据拆分到多个表中。
- 垂直分库:以表为依据,按照业务归属不同,将不同的表拆分到不同的库中。
- 垂直分表:以字段为依据,按照字段的活跃性,将表中字段拆到不同的表(主表和扩展表)中。
分表要停服嘛?不停服怎么做?
不用停服。不停服的时候,应该怎么做呢,主要分五个步骤:
- 编写代理层,加个开关(控制访问新的
DAO
还是老的DAO
,或者是都访问),灰度期间,还是访问老的DAO
。 - 发版全量后,开启双写,既在旧表新增和修改,也在新表新增和修改。日志或者临时表记下新表
ID
起始值,旧表中小于这个值的数据就是存量数据,这批数据就是要迁移的。 - 通过脚本把旧表的存量数据写入新表。
- 停读旧表改读新表,此时新表已经承载了所有读写业务,但是这时候不要立刻停写旧表,需要保持双写一段时间。
- 当读写新表一段时间之后,如果没有业务问题,就可以停写旧表啦
分库分表总结
分库分表的顺序应该是先垂直分,后水平分,先垂直分表,再垂直分库,再水平分库,最后水平分表。因为垂直分更简单,更符合人们处理现实世界问题的方式。
一般来说,在系统设计阶段就应该根据业务耦合松紧来确定垂直分库,垂直分表方案,在数据量及访问压力不是特别大的情况,首先考虑缓存、读写分离、索引技术等方案。若数据量极大,且持续增长,再考虑水平分库水平分表方案
总体来说:垂直拆分有以下优点:
跟随业务进行分割,和最近流行的微服务概念相似,方便解耦之后的管理及扩展。
高并发的场景下,垂直拆分使用多台服务器的CPU、I/O、内存能提升性能,同时对单机数据库连接数、一些资源限制也得到了提升。
能实现冷热数据的分离。
垂直拆分的缺点:
部分业务表无法join,应用层需要很大的改造,只能通过聚合的方式来实现。增加了开发的难度。
当单库中的表数据量增大的时候依然没有得到有效的解决。
分布式事务也是一个难题。
总体来说:水平拆分的优点有以下:
水平扩展能无线扩展。不存在某个库某个表过大的情况。
能够较好的应对高并发,同时可以将热点数据打散。
应用侧的改动较小,不需要根据业务来拆分。
水平拆分的缺点:
路由是个问题,需要增加一层路由的计算,而且像前面说的一样,不带分片键查询会产生广播SQL。
跨库join的性能比较差。
需要处理分布式事务的一致性问题。
垂直分表:可以把一个宽表的字段按访问频次、是否是大字段的原则拆分为多个表,这样既能使业务清晰,还能提升部分性能。拆分后,尽量从业务角度避免联查,否则性能方面将得不偿失。
垂直分库:可以把多个表按业务耦合松紧归类,分别存放在不同的库,这些库可以分布在不同服务器,从而使访问压力被多服务器负载,大大提升性能,同时能提高整体架构的业务清晰度,不同的业务库可根据自身情况定制优化方案。但是它需要解决跨库带来的所有复杂问题。
水平分库:可以把一个表的数据(按数据行)分到多个不同的库,每个库只有这个表的部分数据,这些库可以分布在不同服务器,从而使访问压力被多服务器负载,大大提升性能。它不仅需要解决跨库带来的所有复杂问题,还要解决数据路由的问题(数据路由问题后边介绍)。
水平分表:可以把一个表的数据(按数据行)分到多个同一个数据库的多张表中,每个表只有这个表的部分数据,这样做能小幅提升性能,它仅仅作为水平分库的一个补充优化。
。