MySQL分库分表详解
摘要:本文是MySQL第七讲:MySQL分库分表详解流程,非常全面的总结,强烈建议保存下来,在需要时看看
1、什么是分库分表,何时要分库分表
背景知识:
1、海量数据的存储问题
- 传统的关系性数据库已经无法满足快速查询与插入数据的需求。这个时候NoSQL的出现暂时解决了这一危机。它通过降低数据的安全性,减少对事务的支持,减少对复杂查询的支持,来获取性能上的提升。有些场合NoSQL是无法满足使用场景的,比如有事务与安全指标的。从单机mysql --》memcached+mysql+垂直拆分 --》mysql主从复制 --》分库分表+水平拆分+mysql集群
2、如果使用关系型数据库解决海量存储的问题呢?
- 此时就需要做数据库集群,为了提高查询性能将一个数据库的数据分散到不同的数据库中存储。
3、mysql性能瓶颈分析
- 数据库连接数,默认是100个连接数
- 单表数据量大,阿里规定单表数据量500万条 数据量大,IO操作就多
- 硬件资源(QPS/TPS 每秒查询数/每秒事务量)
4、大数据量数据库性能的解决方案?
- 分库分表
- 索引优化(数据量1个亿且查询不太复杂时,没什么问题)
- 读写分离
1.1、数据库分片
- 1、什么是数据库分片
指通过某种特定的条件,将我们存放在同一个数据库中的数据分散存放到多个数据库上面,以达到分散单台设备负载的效果
概念:
1.2、为什么需要分库分表
为什么进行分库分表?
- MySQL 支持不了这么大的数据量,这么高的并发,但还必须要用它
分库分表的目的
- ①数据量太大查询慢(事务中的查询和更新操作)
- 只读的查询可以通过缓存和主从分离来解决
- 解决查询慢,只要减少每次查询的数据总量就可以了,也就是说,分表就可以解决问题
- ②应对高并发的问题
- 一个数据库实例撑不住,就把并发请求分散到多个实例中去
- 数据量大,就分表;并发高,就分库
- 这时候分多少个库,多少张表,分别用预估的并发量和数据量来计算就可以
- 越简单的设计可靠性越高
1.3、拆分方式
分库分表有垂直切分和水平切分
- ①垂直切分(复杂度低):即将表按照功能模块、关系密切程度划分出来,部署到不同的库上。我们会建立定义数据库 workDB、商品数据库 payDB、用户数据库 userDB、日志数据库 logDB 等,分别用于存储项目数据定义表、商品定义表、用户数据表、日志数据表等
垂直拆分带来的问题? 分布式事务(通过模块调用接口来解决) –》从连接数和硬件资源两方面进行了解决 - ②水平切分(复杂度高):当一个表中的数据量过大(超过1000万,就要警觉起来)时,我们可以把该表的数据按照某种规则,例如userID散列,进行划分,然后存储到多个结构相同的表,和不同的库上。
如下图所示
垂直拆分如图所示:
水平拆分如图所示:
1.4、何时分库分表
总体来说:性能出现瓶颈,并且其他优化手段无法很好的解决问题
- 单表出现瓶颈:
- 单库出现瓶颈
- CPU压力过大(busy,load过高),导致读写性能较慢
- 内存不足(缓存池命中率较低、磁盘读写IOPS过高),导致读写性能较慢
- 磁盘空间不足,导致无法正常写入数据
- 网络带宽不足,导致读写性能较慢
Action : 数据量超过多少应该分库分表:看对应业务复杂情况,如果是表字段较为简单,即使数据量超过亿级,整体读写性能也较好,不用分表;如果表比较复杂,可能即使数据量超过百万,读写性能就达到瓶颈。
1.5、如何选择分库分表
- 只分表:
- 单表数据量较大,单表读写性能出现瓶颈
- 经过评估单库的容量和性能可以支撑未来几年的增长
- 只分库:
- 分库分表
- 单表数据量较大,单表读写性能出现瓶颈;
- 数据库(读)写压力较大,数据库出现存储性能瓶颈
2、十亿级商品数据,分库分表核心流程详解
2.1、完整流程如下所述
- 评估是否需要拆分
- 拆分详细技术方案
- 流程梳理及影响评估
- 方案选型
- 拆分SOP(标准操作程序,重要)
- 1、目标评估
- 评估:拆成几个库、几个表
- 目标:读写能力提升X倍、负载降低Y%,容量要支持未来Z年的发展
- 举例:当前2亿,5年后评估为10亿。分几个表? 分几个库?
- 解答:一个合理的答案,128个表,16个库。按128个表算,拆分完单表156万,5年后为781万
- 2、切分策略–范围切分
- 优点:天然水平扩展;单表大小可控
- 缺点:热点数据一般为新增数据,存在明显的写偏移
- 适用场景:数据归档
- 切分策略–中间表映射
- 优点:灵活;
- 缺点:引入了额外的单点,增加了流程复杂度。
- 切分策略–hash切分
- 优点:数据分片比较均匀,不容易出现热点和并发访问的瓶颈;
- 缺点:后续扩容需要迁移数据、存在跨节点查询等问题;
- 适用场景:大部分场景下都能适用。
- 3、分表字段(sharding key)
- 核心思路:合理选择,尽量减少出现跨库、跨表查询
- 例子:10个库,1000张表:0 ~ 99、100 ~ 199、200~999、…
- 分表字段:shopId,值为1234
- 数据表编号:shopId% 1000 =1234 % 1000 =234
- 数据库编号:shopId% 1000 =1234 % 1000 / 10 = 2
- 思考:订单应该如何选择分表字段? 见下文
- 4、资源准备、代码改造
- 核心流程:
- 数据库资源准备
- 分库分表规则配置等
- 代码改造:
- 写入:单写老库、双写、单写新库
- 读取:读老、读新、部分读老部分读新
- 灰度:指定门店灰度、比例灰度
- 所需资源尽早跟dba申请
- 1、将新集群的数据源引入到我们的服务中;
- 2、支持灵活的灰度读写切换;
- 3、数据全量迁移和一致性校验等任务;
- 4、增量数据同步:双写
- 5、常见的双写方案
- 作用:保证增量数据在新库和老库都存在
- 方案:
- 1、同步双写:同步写新库和老库
- 2、异步双写:写老库,监听binlog 异步同步到新表
- 3、中间件同步工具:通过一定的规则将数据同步到目标库表
- 我们公司使用的是阿里云DTS
- 注意点:
- 实现方案:
- 6、全量数据迁移
- 作用:迁移老库历史数据,保证新库有全量数据
- 方案:
- 1、开发Job:查询老库数据,写入新表;
- 2、中间件同步工具:通过一定的规则将数据同步到目标库表(建议)
- 注意点:
- 7、数据一致性校验、优化、补偿 (最重要)
- 作用:确保新库数据正确,达到切读标准、检查是否存在改造遗漏点
- 方案:增量数据校验、全量数据校验、人工抽检
- 核心流程:
- 读取老库数据
- 读取新库数据
- 比较新老库数据,一致则继续比较下一条数据
- 不一致则进行补偿:
- 新库存在,老库不存在:新库删除数据
- 新库不存在,老库存在:新库增加数据
- 新库存在,老库不存在:比较所有字段,不一致则将新库更新为老库数据
- 8、灰度切读
- 作用:开始将读流量切到新库
- 原则:
- 有问题及时切回老库
- 灰度放量先慢后快,每次放量观察一段时间
- 支持灵活的规则:门店维度灰度、百分比灰度
- 9、databus 切新库
- 作用:使用新库的databus、canal
- 核心流程:
- 启动新库databus、canal,此时下游会同时收到新老库的binlog
- 观察一段时间是否正常
- 有问题及时关闭
- 没问题后,关闭老库databus、canal
- 10、下游切换数据源
- 作用:确保下游迁移到新数据源,主要是数仓
- 数仓一般是每天同步一次数据,因此在指定时间内切换即可。
- 11、停写老库
- 原则:确认老库数据源全部迁移后,停写老库
- 至此,核心拆分流程结束,后续逐步将老数据库资源逐渐下线。
- 整体流程图汇总如下:
- 稳定性保障
- 技术方案内部评审及优化
- 同步相关影响方
- 进行拆分
2.2、使用到的分库分表工具
- binlog监听工具
- 分库分表工具
- 1、增强版的JDBC驱动
- 以客户端 jar包形式提供了对JDBC的封装,客户端直连数据库
- 开源:Sharding-JDBC、TDDL、Zebra
- 2、数据库代理
- 需要单独部署,客户端连接代理服务,代理服务负责跟数据库打交道
- 开源:Sharding-Proxy、MyCat
- 收费:阿里云DRDS
3、分库分表带来的问题及如何解决(重点)
问题1:分布式id:分库分表后,保证id的唯一性(要保证单调递增)
- 解决方案1:UUID
- 优点:
- 缺点:
- 更占用存储空间,一般为长度36的字符串
- 不适合作为MySQL主键
- 无序性会导致磁盘随机IO、叶分裂等问题
- 普通索引需要存储主键值,导致B+树“变高”,IO次数变多
- 基于MAC地址生成的算法可能导致MAC地址泄漏
- 方案2:雪花算法
- 41 bit时间戳:可用69年
- 10bit工作机器:可部署1024台服务器
- 12bit序列号:每毫秒可生成4096个ID,每秒也就是409万
- 方案3:号段模式
- 原来:是按数据表自增得到唯一id,现在:批量生成1000条数据,拿到其ids
以美团外卖为例,商品的分表
- 用户视角:查询商品时,会展示shopId
- 商家视角:查询商品时,也会展示shopId
- shopId覆盖了最高频的几个使用场景
订单的分表
- 用户视角:查询到自己的所有订单,用户id
- 商家视角:商品查询自己的所有订单,商家id
- 运营视角:订单id字段
- 为了支持按订单号查询,可以把用户id的后几位放到订单号中
如何选择 Sharding Key?
- 选择一个合适的列或者说是属性,作为分表的依据,这个属性一般称为 Sharding Key
问题2、分布式事务问题
- 原本在同一个数据库中不同的表可以在同一个事务中修改,业务分库后,表分散到不同的数据库中,无法通过事务统一修改。虽然数据库厂商提供了一些分布式事务的解决方案(例如,MySQL 的 XA),但性能实在太低,与高性能存储的目标是相违背的。
- 解决方案
- 1、2PC(Two Phase Commitment)
- 如下图所示,核心思想是将事务操作分为两个阶段,第一阶段是协调者首先询问所有事务参与者是否可以执行事务的提交操作;第二阶段是协调者根据所有参与者返回结果决定是否提交操作,如果全部的参与者都返回成功,则协调者向所有参与者发送事务提交请求;否则协调者向所有参与者发送事务中断回滚请求
- 优点:流程简单
- 缺点:存在同步阻塞、协调者单点等问题
- 数据库层面的处理
- 方案2:TCC (try confirm cancel)
- 核心思想:针对每个操作都有一个对应的确定和取消操作 ,TCC中有主服务(可以理解为交易)和从服务(可以理解为订单和库存)两个角色。首先主服务会调用所有从服务的try接口进行业务检查和资源预留,然后主服务会根据所有从服务返回结果决定是否提交事务;如果从服务确认成功,则调用所有从服务的confirm接口进行事务确认提交操作,否则会调用所有从服务的cancel接口执行事务的取消,并释放预留资源;
- 应用层面的处理
- 实际业务中的使用方式(保证最终一致即可,金融场景是个特例)
- 回滚
- 重试
- 监控
- 告警
- 幂等
- 对账
- 人工补偿(终极手段)
问题3、跨库join / 分页查询问题
- 业务分库后,原本在同一个数据库中的表分散到不同数据库中,导致无法使用 SQL 的 join
查询。
拆分后:影响了哪些操作
-
join操作 水平分表后,数据分散在多个表中,如果需要与其他表进行 join 查询,需要在业务代码或 者数据库中间件中进行多次 join 查询,然后将结果合并。 -
count操作 水平分表后,虽然物理上数据分散到多个表中,但某些业务逻辑上还是会将这些表当作一个表来处理。例如,获取记录总数用于分页或者展示,水平分表前用一个 count() 就能完成的操作,在分表后就没那么简单了。常见的处理方式有下面两种:
- ①count() 相加:具体做法是在业务代码或者数据库中间件中对每个表进行 count() 操作,然后将结果相加。这种方式实现简单,缺点就是性能比较低。例如,水平分表后切分为 20 张表,则要进行 20 次 count(*) 操作,如果串行的话,可能需要几秒钟才能得到结果。
- ②记录数表:具体做法是新建一张表,假如表名为“记录数表”,包含 table_name、row_count 两个字段,每次插入或者删除子表数据成功后,都更新“记录数表”。
-
order by 操作 水平分表后,数据分散到多个子表中,排序操作无法在数据库中完成,只能由业务代码或者数据库中间件分别查询每个子表中的数据,然后汇总进行排序。
业界主流解决方案:
问题4、成本问题
- 业务分库同时也带来了成本的代价,本来 1 台服务器搞定的事情,现在要 3 台,如果考虑
备份,那就是 2 台变成了 6 台。
4、日千万级的订单系统分库分表核心内容简要分享
todo
5、阿里云drds方案处理分库分表
-
商品中心的方案
- 背景:商品主表共1亿6千多万条数据(截止20210810,有效数据共1亿1千万条数据)
- 架构:使用阿里云提供的drds,分为三层,第一层是drds代理层(读写库sg85 只读库wr20),第二层是主rds数据库,共4个实例(16台机器),从rds数据库,共8个实例(32台机器)
项目的读操作,只会读取从rds数据库中的部分实例 -
用户中心的方案
6、MySql分库分表与读写分离
6.1、什么是MyCat?
- 面向企业应用开发的“大数据集群”,支持事务、ACID、一个数据库中间件产品(作为后面MySql集群的proxy使用,默认端口:8066)
- 作用:提高可用数据分片集群,支持读写分离,支持MySql双主多从,一主(写数据库)多从(读数据库)
- 当添加一条记录时要向哪个数据库中插入呢?这些问题处理起来都是非常的麻烦。这种情况下可以使用一个数据库中间件mycat来解决相关的问题
- 用法:现在把数据库中所有的item一千五百万本(图书表)分片存储到三个数据节点中。可以是三台mysql数据库。
- 节点一:db1存储500万条数据 节点二:db2 500万;节点三:db3 500万;
- 如何分配:1、根据主键id按段切分 2、根据id取模运算
- 配置schema。xml文件,管理mycat的逻辑库,表,分片规则,数据节点,数据源 rule= auto-sharding-long mycat会根据此规则自动分片
- 读写分离:一个写节点master后面跟着多个读节点。读节点的数量取决于系统的压力。写数据库和读数据库建立主从复制,使用mysql厂商自带的binlog,然后建立mycat和mysql的心跳检查。
- 使用:mysql主数据库修改my.conf文件,musqld下面添加数据库及IP的配置信息 状态:File/position/binlog_Do_DB/binlog_ignore-DB。 从数据库修改my.conf文件 change master to master_host=‘’,master_post=‘’,master_user=‘’,master_password=‘‘master_log_file=’’,master_log_pos=‘’
当slave_io_Running:YES且slave_SQL_Running:YES,说明配置成功 mycat配置 支持mysql主从复制状态绑定的读写分离机制 balance writeType switchType
6.2、中间件操作数据库的步骤
- 1、解析sql语句
- 2、数据源管理
- 3、数据源分配
- 4、请求/响应
- 5、结果整合
6.3、MyCat使用案例
1、概念:逻辑库(使用mycat来管理)db_user,db_store 逻辑表:分片表user,全局表(数据字典 冗余),ER表(user地址 只能存放在单一的数据库中,跟着主表一起走),非分片表(门店表,店员表) 应用程序分为db_user(用户表、数据字典,用户地址表)和db_store(订单表,店员表) 经常使用的数据不变性,在后台的每个mysql中都保存一份(如数据字典最好冗余查询**) 用户表进行模2运算,store表进行主从复制
2、conf目录下,有三个关键的配置文件
- scheme.xml管理mycqt实例中的逻辑库,表,分片规则,DataNode,DataSource
- server.xml
- rule.xml 定义了我们队表进行拆分所设计到的规则定义
有连续分片和离散分片两种 连续分片扩容无需迁移数据,范围查询资源消耗小 但是存在热点数据问题 离散分片:分片均匀,并发强 缺点:移植性差
3、现有系统如何使用MyCat?
- 当当、美团正在做这方面的工作
- mysql Dump命令生成backup.sql文件, 控制台:mysql -f backup.sql或Source backup.sql
6.4、MyCat关联查询的问题
解决方案: 1、用好ER表,子表跟随父表 2、善用全局表 冗余数据 3、注解方式
6.5、Mycat的分布式事务?XA ?20181222
如何保证强一致性 两阶段提交 弱XA的两阶段提交 TCC补偿机制
当你的才华还撑不起你的野心时,就应该静下心来学习
|