【分布式】分库分表知识点大全
为什么要分库分表随着业务量的增加导致数据库中数据量的增加,可能拖慢查询的性能,影响业务的可用性;假如数据库接纳读写分离,可能会导致从库的延伸较大,主库进行写操作后,从库因为延伸无法实时同步,会导致出现数据不划一的环境。
分表:应对大数据量。当单表的数据达到百万级时,sql 的实行性能就较差了;当凌驾千万级时,sql 的实行性能急剧下降,以是要保持 sql 的实行性能,就必须接纳分表。
分库:应对高并发。一个康健的单数据库并发最好维持到 1000 个毗连左右,凌驾就可能造成宕机崩溃。接纳分库就可以进步数据库的并发能力。
分库分表的原则
在进行分库分表时,我们最好遵循一下原则:
[*]优先进行 MySQL 调优,能不分就不分
数据量能稳定在千万级,近几年不会到达亿级,其实是不用发急拆的,先实行MySQL调优,优化读写性能。只有在MySQL调优已经无法办理慢查扣问题时,才可以考虑分库分表。
[*]分片的数目尽量少
分片就是将数据存储在多个数据库实例中,在分库中就是将数据拆分到多个独立的数据库中,在分表中就是将一个大表拆分成多个小表,每个小表存储数据的一部分。
假如实例太多,分的太细,那么查询一个 sql 可能要跨越多个分区,低落查询的性能。
[*]数据的分布要尽量匀称
数据应该尽量匀称的分布在多个分片中,原因同上,这就涉及到分片建的选择了。
分表方案
分表的应用场景是单表数据量增长速率过快,影响了业务接口的响应时间,但是 MySQL 实例的负载并不高,这时候只需要分表,不需要分库(拆分实例)。
一张表的大小由 字段数目 × 记录数目 构成,也就是说假如表太大,那么要么是表的记录太多,要么就是表的字段太多。这就产生了两种分表方案:
[*]垂直分表(拆分字段)
[*]水中分表(拆分记录)
https://i-blog.csdnimg.cn/blog_migrate/337c6b5527c0cd8484d4ecd794e38f57.png
垂直分表
以订单表 orders 为例,按照字段进行拆分,这内里需要考虑一个问题,如何拆分字段才能表上的DML性能最大化。
[*]常规的垂直分表方案就是冷热分离拆分(将利用频率高字段放到一张表里,剩下利用频繁低的字段放到另一张表里)。
https://i-blog.csdnimg.cn/blog_migrate/bacaab64f6ee5c4b32aaab4f8ba6bcc2.jpeg
什么环境下可以利用冷热分离?
[*]**数据走到终态后只有读没有写的需求。**比方订单完结后根本只会读不会改。
[*]**用户能接受新旧数据分开查询。**好比有些电商网站默认只让查询3个月内的订单,假如要查询3个月前的订单,还需要访问其他的页面。
orders 表通过拆分之后,就变成了 orders01 和 orders02 两张表,在磁盘上就会存储两个数据文件 orders01.ibd 和 orders02.ibd,orders 表最大尺寸就是 4TB 了,拆分完之后,该怎么查询呢?举个例子:
https://i-blog.csdnimg.cn/blog_migrate/98481cfb0d6e57afd8acc81a9d275e53.jpeg
分析下上面的 SQL,select 后面的列分别位于两张表中(order_id,order_sn在orders01中,source在orders02中),上面的SQL可以查询重写为如下形式。
https://i-blog.csdnimg.cn/blog_migrate/eaa41db137a13bad5e3c9cd2b0880971.jpeg
一样平常数据库中央件就会自动实现查询重写,但是每次剖析SQL时都需要根据原表名 + 字段名去获取需要的子表,然后再改写 SQL,实行 SQL 返回结果,这种代码改造量太大,而且容易堕落。
业务场景举例:
[*]邮件系统:邮件系统中最近邮件是用户常常访问和修改的,三个月前的邮件或已归档的邮件不常常访问的。可以将用户的收件箱、发件箱里最近三个月的邮件放在一个库里(热库),之前的邮件或者已读的邮件放在另一个库里(冷酷)。
[*]**日志系统:**在大型应用中,日志数据是非常庞大的,但并不是所有日志都需要常常查询或分析。可以将最近一段时间的运动日志存放在热库中,而将过去的历史日志存放在冷库中,以减轻热库的负载和优化查询性能。
[*]交际媒体平台:交际媒体平台上的用户数据量通常很大,但是只有少部分用户是活泼的,而且只有少量用户的数据会频繁访问和更新,假如所有用户都放在同一个库里,势必会影响活泼用户的查询效率。可以将活泼用户的个人信息、挚友关系等存放在热库中,而将不活泼用户的数据存放在冷库中,以提升热库的性能和镌汰冷库的存储成本。
[*]**电商平台:**电商平台上的商品数据也可以进行冷热分离。热库中存放热门商品的根本信息和库存等,以支持频繁的查询和更新操作,而将不活泼或下架的商品信息存放在冷库中,以镌汰热库的负载和优化查询性能。
[*]客服工单:在我们一样平常操作时,常常能看到查询历史工单时会有个“近三个月工单”的选项,实际业务场景中,用户根本只会关注近三个月工单,而且这些工单也会常常需要进行修改、删除的操作,而对很早期的历史订单根本就没有修改、删除的需求,只有少量的查询需求。
[*]还可以根据业务的层面来进行拆分:将混淆业务拆分为独立业务。
业务场景举例:
[*]**电商网站:**一个典型的混淆业务,包罗用户信息、订单信息、商品信息等。可以将用户信息、订单信息和商品信息分别拆分到不同的库或表中,以镌汰数据冗余并进步访问效率。
[*]交际媒体平台:包罗用户信息、挚友关系、动态信息等。可以将用户信息和挚友关系分离存储,以便更好地支持挚友关系的查询和更新。
[*]**在线游戏:**涉及脚色信息、道具信息、战斗日志等。可以将脚色信息和道具信息拆分到不同的表中,以提升查询效率,并将战斗日志存储到日志数据库中,以减轻主数据库的负载。
[*]物流系统:包罗订单信息、配送信息、运输信息等。可以将订单信息、配送信息和运输信息分别拆分到不同的表中,以便更好地支持订单的查询和跟踪。
[*]还有假如业务表中有 text 长文本范例的字段需要存储。这时可以利用垂直拆分来镌汰表大小,将 text 字段拆分到子表中。
https://i-blog.csdnimg.cn/blog_migrate/59d41f8cd15fb20a94da726f3fffdf5c.jpeg
如许将 text 范例拆分放到子表中之后,原表的匀称行长度就变小了,就可以存储更多的数据了。
水中分表
水平拆分表就是按照表中的记录进行分片,举个例子,目前订单表 orders 有 2000w 数据,根据业务的增长,估算一年之后会达到1亿,同时参考阿里云 RDS for MySQL 的最佳实践,单表不发起凌驾 500w,1亿数据分20个子表就够了。
问题来了,按照什么来拆分呢?主键id照旧用户的user_id,按主键ID拆分数据很匀称,通过ID查询 orders 的场景几乎没有,业务访问 orders 大部分场景都是根据 user_id来过滤的,而且 user_id 的唯一性又很高(一个 user_id 对应的 orders 表记录不多,选择性很好),按照 user_id 来作为 Sharding key能满足大部分业务场景,拆分之后每个子表数据也比较匀称。
一样平常利用散列算法,让数据匀称的分摊到不同的库表中。如把本来在一台机器上的数据库存放1000万数据,分摊到n台机上,拆分这1000万的数据和后续的增量。让每个数据库资源来分摊本来需要一台数据库所提供的服务。
公式: sharding_key%N
https://i-blog.csdnimg.cn/blog_migrate/ac9227589b6ad46077d1d264503a6518.jpeg
如许就将 orders 表拆分成20个子表,对应到InnoDB的存储上就是20个数据文件(orders_0.ibd,orders_1.ibd等),这时候实行SQL语句select order_id, order_sn, source from orders where user_id = 1001就能很快的定位到要查找记录的位置是在orders_1,然后做查询重写,转化为SQL语句select order_id, order_sn, source from orders_01 where user_id = 1001,这种查询重写功能很多中央件都已经实现了,常用的就是 sharding-sphere 或者 sharding-jdbc 都可以实现。
按日期分表
这种利用方式比较普遍,尤其是按照日期维度的拆分,其实在步调层面的改动很小,但是扩展性方面的收益很大。
[*]日维度拆分,如test_20191021
[*]月维度拆分,如test_201910
[*]年维度拆分,如test_2019
比方对于账务或者计费类系统,每天晚上都会做前一天的日结或日账任务,每月的1号都会做月结或月账任务,任务实行完之后相关表的数据都已静态化了(业务层不需要这些数据),根据业务的特性,可以按月创建表,好比对于账单表 bills,就可以创建按月分表(十月份表bills_202010,202011十一月份表),出完月账任务之后,就可以归档到历史库了,用于数据仓库ETL来做分析报表,确认数据都同步到历史库之后就可以删除这些表释放空间。
https://i-blog.csdnimg.cn/blog_migrate/c42b6e5def1e41ab5cf51e894efa3b32.jpeg
按主键范围分表
比方【1,200w】主键在一个表,【200w,400w】主键在一个表。长处是单表数据量可控。缺点是流量无法分摊,写操作集中在最后面的表。
分库方案
聊了下分表的方案,那什么时候分库呢?我们知道,MySQL 的高可用架构大多都是一主多从,所有写入操作都发生在 Master 上,随着业务的增长,数据量的增加,很多接口响应时间变得很长,常常出现 Timeout,而且通过升级 MySQL 实例配置已经无法办理问题了,这时候就要分库了。
按业务分库
举个例子,交易系统 trade 数据库单独部署在一台 RDS 实例,如今交易需求及功能越来越多,订单,代价及库存相关的表增长很快,部分接口的耗时增加,同时有大量的慢查询告警,升级 RDS 配置结果不大,这时候就需要考虑拆分业务,将库存,代价相关的接口独立出来。
https://i-blog.csdnimg.cn/blog_migrate/107610e7aeff77c84e3fc75d24841a45.jpeg
如许按照业务模块拆分之后,相应的 trade 数据库被拆分到了三个 RDS 实例中,数据库的写入能力提升,服务的接口响应时间也变短了,进步了系统的稳定性。
按表分库
上面介绍了分表方案,常见的有垂直分表和水中分表(拆分后的子表都在同一个 RDS 实例中存储),对应的分库就是垂直分库和水中分库,这里的分库其实是拆分 RDS 实例,是将拆分后的子表存储在不同的 RDS 实例中,垂直分库实际业务用的很少,就不介绍了,主要介绍下水中分库。
举个例子,交易数据库的订单表 orders 有2亿多数据,RDS 实例遇到了写入瓶颈,平凡的 insert 都需要50ms,时常也会收到 CPU 利用率告警,这时就要考虑分库了。根据业务量增长趋势,计划扩容一台同配置的RDS实例,将订单表 orders 拆分20个子表,每个 RDS 实例10个。
https://i-blog.csdnimg.cn/blog_migrate/ecdd1694c986b9e6fede7681912d0cbc.jpeg如许办理了订单表 orders 太大的问题,查询的时候要先通过分区键 user_id 定位是哪个 RDS 实例,再定位到具体的子表,然后做 DML操作,问题是代码改造的工作量大,而且服务调用链路变长了,对系统的稳定性有肯定的影响。其实已经有些数据库中央件实现了分库分表的功能,比方常见的 mycat,阿里云的 DRDS 等。
分片键的选择
选择最佳的分表字段是一个需要仔细考虑的问题。最佳的分表字段应该是能够让数据分布匀称、频繁查询的字段以及不可变的字段。通过选择最佳的分表字段,可以进步系统的性能和查询效率。
常用字段:
[*]**主键ID:**频繁查询而且唯一,非常适互助分表字段。比方,在用户表中,用户ID作为分表字段是一个不错的选择,因为用户ID是唯一的,而且在查询用户信息时常常会用到。
[*]**时间字段:**假如业务需要按时间范围查询数据,那么选择时间字段作为分表字段是公道的。比方,在日志表中,可以选择时间戳字段作为分表字段,以便按天、按月或按年分割数据,方便查询和维护。
[*]**地理信息字段:**假如业务需要按地区查询数据,那么选择地理信息字段作为分表字段是符合的。比方,在订单表中,可以选择订单地区字段作为分表字段,以便将订单数据按地区进行拆分,方便查询和扩展。
[*]**关联字段:**假如业务需要频繁进行关联查询,那么选择订单号等关联字段作为分表字段。比方,在订单表中,可以选择订单号作为分表字段,因为订单号唯一且包罗业务信息,而且一样平常查询、关联查询都是根据订单号查询的,很少根据id查询,方便查询和维护。
选择分表字段的原则:
\1. 数据分布匀称
最佳的分表字段应该是能够让数据分布匀称的字段,如许可以避免某个表的数据过多,导致查询效率低落。在用户表中,假如以地区作为分表字段,可能会导致某些地区的数据过多,而某些地区的数据过少。
2. 频繁查询的字段
尽量选择查询频率最高的字段(比方主键id),然后根据表拆分方式选择字段。在一个订单表中,假如常常需要根据用户ID查询订单信息,那么以用户ID作为分表字段是一个不错的选择。
3. 不可变字段
最佳的分表字段还应该是不可变的字段,如许可以避免在数据迁徙时出现问题。在一个商品表中,假如选择以商品名称作为分表字段,那么当商品名称发生变化时,就需要将数据移动到不同的表中,如许会增加系统的复杂度。
查询重写
修改代码里的查询、更新语句,以便让其适应分库分表后的环境。通常查询重写是通过一些工具来自动实现,好比 jdbc sharding,mycat 等
查询语句改造:
[*]**单库查询改为跨库查询:**对于需要查询的字段,需要明白指定查询的库和表,以避免查询到错误的数据。比方,原来的查询语句 “SELECT * FROM users WHERE id = 1” 可以修改为 “SELECT * FROM db.table_name WHERE id = 1”,其中 db 为目标数据库,table_name 为目标表。
[*]**单表查询改为跨表查询:**比方投诉记录表根据哈希取余的方式分成10个表,假如id%1=0,则查0号表complaint_records_0。
可能出现的问题
[*]分布式全局唯一 ID
MySQL InnoDB的表都是利用自增的主键ID,分库分表之后,数据表分布不同的分片上,假如利用自增 ID 作为主键,就会出现不同分片上的主机 ID 重复现象,可以利用 Snowflake 算法天生唯一ID。
[*]分片键的选择
选择分片键时,需要先统计该表上的所有的 SQL,尽量选择利用频率且唯一值多的字段作为分片键,既能做到数据匀称分布,又能快速定位到数据位置,比方user_id,order_id等。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]