数据库分库分表?
1.什么是分库分表?
通过一定的规则,将原本数据量大的数据库拆分成多个单独的数据库,将原本数据量大的表拆分成若干个数据表,使得单一的库、表性能达到最优的效果(响应速度快),以此提升整体数据库性能。
2.为什么分库分表?
单个数据库的存储能力是有限的、连接数是有限的,本身就会成为系统的瓶颈,单个表的数据量在百万以内,可以通过添加索引来提升性能。 一旦数据量朝着千万以上的趋势进行增长,那么再怎么优化数据库,性能依旧是下降的。为了减少数据库的负担提升数据库的性能,缩短查询时间,这个时候就需要分库分表。
为什么需要分库?
容量 我们给数据库实例分配的磁盘容量是固定的,数据量持续的大幅增长,用不了多久单机的容量就会承载不了这么多数据,解决办法简单粗暴,加容量!
连接数 单机的容量可以随意扩展,但数据库的连接数却是有限的,在高并发场景下多个业务同时对一个数据库操作,很容易将连接数耗尽导致 too many connections 报错,导致后续数据库无法正常访问。 可以通过 max_connections 查看 MySQL 最大连接数。
show variables like '%max_connections%'
+------------------------+-------+
| Variable_name | Value |
+------------------------+-------+
| max_connections | 151 |
| mysqlx_max_connections | 100 |
+------------------------+-------+
将原本单数据库按不同业务拆分成订单库、物流库、积分库等不仅可以有效分摊数据库读写压力,也提高了系统容错性。
为什么需要分表?
数据库查询慢的原因有: 1.SQL 没有使用索引 2.like 扫描全表 3.函数计算 4.数据量确实太大了 数据量确实太大了无法通过优化解决。慢的根本原因是:InnoDB 存储引擎,聚簇索引结构的 B+tree 层级变高,磁盘 IO 变多查询性能变慢。
单表行数超 500 万行或者单表容量超过 2GB,就推荐分库分表.实际上很多公司单表数据几千万、亿级别仍然不选择分库分表。
3.什么时候分库分表?
分库分表要解决的是现存海量数据访问的性能瓶颈,对持续激增的数据量所做出的架构预见性。
是否分库分表的关键指标是数据量?
系统在运行初始的时候,每天只有可怜的几十个资源上传,这时使用单库、单表的方式足以支持系统的存储,数据量小几乎没什么数据库性能瓶颈。 2.是否是持续激增的数据量。但某天开始一股神秘的流量进入,系统每日产生的资源数据量暴增至十万甚至上百万级别,这时资源表数据量到达千万级,查询响应变得缓慢,数据库的性能瓶颈逐渐显现。以 MySQL 数据库为例,单表的数据量在达到亿条级别,通过加索引、SQL 调优等传统优化策略,性能提升依旧微乎其微时,就可以考虑做分库分表了。
既然 MySQL 存储海量数据时会出现性能瓶颈,那么我们是不是可以考虑用其他方案替代它?比如高性能的非关系型数据库 MongoDB?
可以,但要看存储的数据类型!现在互联网上大部分公司的核心数据几乎是存储在关系型数据库(MySQL、Oracle 等),因为它们有着 NoSQL 如法比拟的稳定性和可靠性,产品成熟生态系统完善,还有核心的事务功能特性,也是其他存储工具不具备的,而评论、点赞这些非核心数据还是可以考虑用 MongoDB 的。
4.如何分库分表?
分库分表的关键:1.对数据如何进行分片?如何让数据路由到不同的库、不同的库中间的不同的表? 分库分表的关键:2.对分片的数据如何进行检索/定位?在不同的库,以及不同的表里面检索定位数据?
分库与分表的两种方式(水平)或者(纵向)?
垂直分库和垂直分表?
垂直分库:不同的业务数据在不同的数据库里。(用户数据库)(商品数据库)(订单数据库)(卖家数据库) 垂直分库的好处:把一个数据库的压力分给了很多数据库。垂直分库把一个库的压力分摊到多个库,提升了一些数据库性能,但并没有解决由于单表数据量过大导致的性能问题,所以就需要配合后边的分表来解决。
垂直分表:拆分一个表的字段(当这个表的字段很多的时候)(拆分出来的字段放在另外一个表,然后通过 ID『自增』和被拆分的表进行关联。) (服务器 1-Mysql-OrederDatabse-OrderTable | 服务器 1-Mysql-OrederDatabse-OrderTableExpansion)『OrderTable 和 OrderTableExpansion 的字段是不一样的』 垂直分表的好处:核心表是访问数据量比较大的表,但是因为被拆分后字段变得比较短,加载更多的核心表的数据量(因为 MYSQL 是按照行把数据加载到内存中)
水平分库和水平分表?
上边垂直分库、垂直分表后还是会存在单库、表数据量过大的问题,当我们的应用已经无法在细粒度的垂直切分时,依旧存在单库读写、存储性能瓶颈,这时就要配合水平分库、水平分表一起了。
水平分库: 同样的一个表放到不同的数据库(很多的数据库里面放着相同的表) (服务器 1-Mysql-OrederDatabse-OrderTable_1 | 服务器 2-Mysql-OrederDatabse-OrderTable_2) 『OrderTable1 和 OrderTable2 的字段是一样的』 举个例子: db_orde_1、db_order_2 两个数据库内有完全相同的 t_order 表访问某个订单的时候可以通过对订单编号取模的方式,决定把这个订单编号存储在哪个数据库。查询订单的时候,就对订单号取模,决定去哪个数据库查询。
水平分表: 同样的一个表放到相同的数据库(相同的数据库里面放着相同的表) 举个例子: 一个 Order 表:900 万数据,放到该数据库下面的相同的三个表 Order_1、Order_2、Order_3 每个数据表 300 万数据。 存在的问题: 水平分表尽管拆分了表,但子表都还是在同一个数据库实例中,只是解决了单一表数据量过大的问题,并没有将拆分后的表分散到不同的机器上,还在竞争同一个物理机的 CPU、内存、网络 IO 等。要想进一步提升性能,就需要将拆分后的表分散到不同的数据库中,达到分布式的效果。
提问:一个 Orders 表,1 水平分表怎么做?2 水平分库怎么做?3 怎么做? 1.水平分表就是把在一个相同的数据库里面,搞三个 Order_1 、Order_2 、Order_3 这个三个表的字段相同。 2.水平分库就是把在三个数据库里面,搞三个 Orders 、Orders 、Orders 这个三个表的字段相同。
提问:一个 Orders 表,1 垂直分表怎么做?2 垂直分库怎么做?3 怎么做? 1.垂直分表就是这个表的字段拆成好多部分,然后在这个数据库下面搞出这几个表:Orders OrdersExpansion1 、OrdersExpansion2、OrdersExpansion3、OrdersExpansion4…. 2.垂直分库就是把在这个 Orders 这个表所在的数据库里,只能存放 Orders 这一个表,其他的表就只能放在其他的数据库。
综合来看:一个 Order 表应该这么拆分:
第一层: DB1 DB2 DB3 第二层:DB1_Order1 DB2_Order2 DB3_Order3 第三层:DB1_Order1_table1 DB2_Order2_table2 DB3_Order3_table2
5.数据存在哪个库的表?(数据库的路由算法)
分库分表以后会出现一个问题,一张表会出现在多个数据库里,到底该往哪个库的哪个表里存呢?
上边我们多次提到过一定规则 ,其实这个规则它是一种路由算法,决定了一条数据具体应该存在哪个数据库的哪张表里。 常见的有 取模算法 、范围限定算法、范围+取模算法 、预定义算法
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 存第三个库,以此类推。 优点 实现简单,数据分布相对比较均匀,不易出现请求都打到一个库上的情况。 缺点 取模算法对集群的伸缩支持不太友好,集群中有 N 个数据库实·hash(user_id) mod N,当某一台机器宕机,本应该落在该数据库的请求就无法得到处理,这时宕掉的实例会被踢出集群。 此时机器数减少算法发生变化 hash(user_id) mod N-1,同一用户数据落在了在不同数据库中,等这台机器恢复,用 user_id 作为条件查询用户数据就会少一部分。
2、范围限定算法
根据 Id 的范围把数据决定存到哪个表,比如 table1 只存 0 ~ 1000 的 ID 数据 比如 table2 只存 1000 ~ 2000 的 ID 数据,好处是后续加数据库,那么只需要让 2000 ~ 3000 的数据存到 table3 范围限定算法以某些范围字段,如时间或 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,以此类推。按日期范围划分同理。 优点 单表数据量是可控的 水平扩展简单只需增加节点即可,无需对其他分片的数据进行迁移.
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、预定义算法 预定义算法是事先已经明确知道分库和分表的数量,可以直接将某类数据路由到指定库或表中,查询的时候亦是如此。
6.分库分表出来的问题
分页、排序、跨节点联合查询
分页、排序、联合查询,这些看似普通,开发中使用频率较高的操作,在分库分表后却是让人非常头疼的问题。把分散在不同库中表的数据查询出来,再将所有结果进行汇总合并整理后提供给用户。比如:我们要查询 11、12 月的订单数据,如果两个月的数据是分散到了不同的数据库实例,则要查询两个数据库相关的数据,在对数据合并排序、分页,过程繁琐复杂。
事务一致性
分库分表后由于表分布在不同库中,不可避免会带来跨库事务问题。后续会分别以阿里的 Seata 和 MySQL 的 XA 协议实现分布式事务,用来比较各自的优势与不足。
全局唯一的主键 分库分表后数据库表的主键 ID 业务意义就不大了,因为无法在标识唯一一条记录,例如:多张表 t_order_1、t_order_2 的主键 ID 全部从 1 开始会重复,此时我们需要主动为一条记录分配一个 ID,这个全局唯一的 ID 就叫分布式 ID,发放这个 ID 的系统通常被叫发号器。
多数据库高效治理
对多个数据库以及库内大量分片表的高效治理,是非常有必要,因为像某宝这种大厂一次大促下来,订单表可能会被拆分成成千上万个 t_order_n 表,如果没有高效的管理方案,手动建表、排查问题是一件很恐怖的事。
历史数据迁移 分库分表架构落地以后,首要的问题就是如何平滑的迁移历史数据,增量数据和全量数据迁移,这又是一个比较麻烦的事情
7.分库分表架构模式
分库分表架构主要有两种模式:client 客户端模式和 proxy 代理模式
客户模式 client 模式指分库分表的逻辑都在的系统应用内部进行控制,应用会将拆分后的 SQL 直连多个数据库进行操作,然后本地进行数据的合并汇总等操作。
代理模式 proxy 代理模式将应用程序与 MySQL 数据库隔离,业务方的应用不在需要直连数据库,而是连接 proxy 代理服务,代理服务实现了 MySQL 的协议,对业务方来说代理服务就是数据库,它会将 SQL 分发到具体的数据库进行执行,并返回结果。该服务内有分库分表的配置,根据配置自动创建分片表。
8.如何抉择?
如何选择 client 模式和 proxy 模式,我们可以从以下几个方面来简单做下比较。
1、性能 性能方面 client 模式表现的稍好一些,它是直接连接 MySQL 执行命令;proxy 代理服务则将整个执行链路延长了,应用->代理服务->MySQL,可能导致性能有一些损耗,但两者差距并不是非常大。
2、复杂度 client 模式在开发使用通常引入一个 jar 可以;proxy 代理模式则需要搭建单独的服务,有一定的维护成本,既然是服务那么就要考虑高可用,毕竟应用的所有 SQL 都要通过它转发至 MySQL。
3、升级 client 模式分库分表一般是依赖基础架构团队的 Jar 包,一旦有版本升级或者 Bug 修改,所有应用到的项目都要跟着升级。小规模的团队服务少升级问题不大,如果是大公司服务规模大,且涉及到跨多部门,那么升级一次成本就比较高; proxy 模式在升级方面优势很明显,发布新功能或者修复 Bug,只要重新部署代理服务集群即可,业务方是无感知的,但要保证发布过程中服务的可用性。
4、治理、监控 client 模式由于是内嵌在应用内,应用集群部署不太方便统一处理;proxy 模式在对 SQL 限流、读写权限控制、监控、告警等服务治理方面更优雅一些。