mysql分库分表实践

分库分表实践

1. 为什么要分库分表?

在日常开发中,mysql中的表有大有小,通常msyql开发手册会提示我们每张表不易存放太多数据,否则会造成单表IO剧增,导致并发量急剧下降,至于大表的查询性能会降低到什么程度,大家没有一个明显的认知,下面我拿实际场景中的一个大表为大家解释,这张表目前存储了大概4000W条数据:

-- 后台运营管理系统统计场景
select * from t_order order by Id asc limit 10,20;  -- 0.028s
select * from t_order order by Id asc limit 100,20;  -- 0.025s
select * from t_order order by Id asc limit 1000,20;  -- 0.029s
select * from t_order order by Id asc limit 10000,20;  -- 0.035s
select * from t_order order by Id asc limit 100000,20;  -- 0.135s
select * from t_order order by Id asc limit 1000000,20;  -- 1.154s
select * from t_order order by Id asc limit 10000000,20;  -- 28.697s
-- 最后一次查询比第一次查询慢了1025倍

-- 用户分页查询我的订单场景
select * from t_order where UserId=1811091645161408 limit 1,20 ;  -- 0.028s
select * from t_order where UserId=1811091645161408 limit 10,20 ;  -- 0.025s
select * from t_order where UserId=1811091645161408 limit 100,20 ;  -- 0.025s
select * from t_order where UserId=1811091645161408 limit 10000,20 ;  -- 0.095s
select * from t_order where UserId=1811091645161408 limit 100000,20 ;  -- 0.32s
select * from t_order where UserId=1811091645161408 limit 180000,20 ;  -- 0.509s
-- 最后一次查询比第一次查询慢了18倍

可以看出随着分页页数的增大,数据的查询效率急剧下降,最后会导致表的并发路数被这些慢查询占满。

数据库的DQL/DML的效率下降有几种可能:

  1. 数据库服务器配置

    如CPU或磁盘配置也会影响数据库的性能。

  2. msyql自身并发配置

    mysql自身并发配置也会影响数据库的性能。

  3. 表设计不合理

    某些情况下,不合理的表结构会直接造成大量的连表查询或其他的一些不合理的操作,也会影响数据库的性能。

  4. 带宽

服务器带宽也会影响数据库的性能。

  1. 大型事务操作

    一些大型的事务,如批量DML、存储过程、视图等也会影响数据库的性能。

  2. B+树存储制约

    表中的数据量过大,导致B+树深度增加,也会一定程度上影响数据库的性能。

以上几种情况都会影响数据库的性能,当数据库存在性能问题时大家可以从以上方面进行诊断,今天主要讲述B+树存储制约的解决方式。

我们知道B+树的特性,mysql的索引是按照B+树来存储的,我们可以通过一些公式简单的估算出我们的表的B+树的深度,计算mysql索引B+树深度方法如下:

  1. 假设主键ID为bigint类型,占8字节
  2. B+树每层默认存储大小16K
  3. B+树叶子节点存储指针大小默认6字节
  4. 计算我们业务表每条数据的平均大小,我举例的业务表为593B/条

则计算方法如:

则B+树每层能存储主键索引容量为:

(16K*1024)/(8+6) = 1170

叶子层能存储真实数据条数为:

(16K*1024)/593 ≈ 28

则可以算出,一个3层的B+树在这个表中的极限容量为:

1170*1170*28 = 38329200条

如果这张表数据量已经超过38329200条,则这张表的B+树深度为4,B+树增加一层索引,导致索引查询时速度变慢。

我们应该怎么优化呢?

2. 分库分表的几种常见策略

单表或单库数据量过大,可以从这几个维度来优化:

  1. 垂直分库

    将一张大数据库实例垂直切分,分为若干个较小的数据库实例,降低单数据库的数据量,从而提高单数据库的性能。

  2. 水平分库

    将一个大表按照一定的规则拆分成若干个小表,每个小表放在不同的数据库中,以水平切分方式拆分大表,降低但库数据量,提高单库性能。

  3. 垂直分表

    将一张字段比较多的宽表垂直切分为若干个字段比较少的小表,每个小表存储大表的一部分字段,避免单表IO争抢并减少锁表机率,提高单表性能。

  4. 水平分表

    在同一个数据库中,按照一定规则拆分大表为若干小表,降低单表的数据量,提高单表性能。

其实拆分策略主要体现在垂直和水平上面:

垂直拆分:就如一个大的西瓜,只能一个人去吃,累死累活也吃不了,把西瓜垂直切分为几小块,每一小块安排一个人去吃,垂直拆分造成的结果就是每个表里面都不是一个订单完整的属性,要想查出订单所有属性,唯有每个表都查出来并拼接:

水平拆分:水平拆分则是把一个大的西瓜置换为几个小的西瓜,每个西瓜都是完整的,但是不是全部的数据。

我们在做分库分表实践时一定要搞清楚每一种拆分的特点和产生后果,一定要切合业务来选择拆分方式:

  1. 垂直分库

    垂直分库带来的提升:

    1. 按照业务领域模型分库,解决业务耦合,为服务拆分和微服务的无状态设计提供领域模型方面的支撑。

    2. 不同业务的数据库分而治之,可按需为不同的数据库提供不同的管理、维护、监控、扩展等策略。

    3. 高并发场景下,垂直分库并部署在不同的服务器上,可有效提升单服务器IO、并发量,降低单机硬件资源的瓶颈。

    4. 降低部分表异常对其他无关表的影响。

    垂直分库一般方式:

    1. 按照独立产品维度拆分大库。

    2. 特定大数据量的业务场景,独立拆分。

  2. 水平分库

    水平分库就是把同一张表中的数据,按照一定的规则拆分到不同的数据库中,每个库存放一部分数据,理论上我们把表分成几分,单库的数据量就少几倍,这种水平分库的方式也可以大幅度提升数据库的性能。

    但是水平分库比垂直分库要复杂的多,它改变了数据的存放位置,意味着编码、运维、数据管理都要随之改变,

    并且第一次水平分库也在一定程度上直接影响数据库未来的可扩展性和可用性,当然,水平分库也有一定的套路,找出数据的特点,才能正确的进行水平分库,我用这几个场景来说明水平分库的方法。

  3. 垂直分表

    垂直分表的定义是:将一张大表按照字段分成多表,每个表存储其中一部分数据。

    垂直分表的原则是:

    • 按照访问热度把常用字段和不常用字段分开。

    • 尽量确保常见查询场景能一次DQL搞定。

    • 把text/blob等大字段拆分出来单独建立附表。

  4. 水平分表

    垂直分表的定义:在同一个数据库内,将一张大表的数据按照一定规则拆分到多个表中,以降低单个表的数据量。

    垂直分表的提升:

    • 优化单表数据量过大而产生的性能问题。

    • 避免IO争抢并减少锁表的机率。

    垂直分表的几种常见策略:

    • 按照时间范围。

    • 按照ID区间。

    • 按照ID哈希。

3. 一次分库分表的落地实践

  1. 大表分析

    这次分库分表目标表如下:

    描述
    表名 t_order
    总数量 4500W
    年增常量 2100W
    日增常量 57000
    3年后预计总数据量 1亿条以上
    数据特点 1.C端业务中绝大部分是用户查询自己的订单信息,不会跨用户查询,也很少多用户同时查询<br>2.个体用户数据量差异较小,数据分布均匀<br>3.用户会经常查询历史订单
    拆分前提 1.原则上每张表数据量控制在1000W*80% = 800W条以内<br>2.考虑分片必须满足未来3~5年的业务需求<br>3.分表要考虑后续扩容和尽量业务无感知
    拆分依据 1.总体方案:按照UserId垂直分表<br>2.补充方案:读写分离
    分片建议 表中添加列UserId,按照UserId 32分片
  2. 分表过程步骤设计

    • 阶段一:(迁移前)优化t_order表以支持分表

    • 阶段二:(迁移中)表分片,数据迁移,优化程序代码

      • 优化程序代码,添加分表操作逻辑,引入Sharding-JDBC分片中间件。

      • 现网数据库32分片,上线数据迁移工具,不停机迁移存量数据。

      • 上线迁移工具,停机迁移增量数据,并同时上线新服务,下线老服务。

    • 阶段三:(迁移后)回归测试

      • 回归测试。

      • 下线旧表。

    • 异常及回滚

      • 存量数据迁移过程中异常:停止迁移,还原现场,删除新表,记录异常堆栈,等待下次迁移。

      • 增量数据迁移过程中异常:停止迁移,删除已迁移的增量数据,记录异常堆栈,等待下次增量迁移。

      • 数据迁移完成后线上归回异常:运行数据回迁工具,从新表正式上线开始,回迁新表新数据到老表中,服务回滚。

  3. 所使用到的中间件或组件

    描述
    数据迁移组件 1.pub/sub方式<br>2.一个生产者时序读取AudioInfo数据<br>3.多个消费者并发消费数据,哈希并存入对应分片
    数据回迁组件 1.pub/sub方式<br>2.一个生产者时序读取t_order分片表中新产生的数据<br>3.一个消费者消费数据,存入t_order中
    数据校验组件 按照t_order.update_time>存量数据迁移完成时间,校验新表和老表数据是否一致,不一致则修改新表数据
    Sharding-JDBC 开源,支持java服务DML分片数据库

    Sharding-JDBC集成方案如下:

    1. 项目引入JDBC组件
    <dependency>
        <groupId>org.apache.shardingsphere</groupId>
        <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
        <version>4.1.1</version>
    </dependency>
    <dependency>
        <groupId>org.apache.shardingsphere</groupId>
        <artifactId>sharding-core-common</artifactId>
        <version>4.1.1</version>
    </dependency>
    2. 配置分表策略
    spring.shardingsphere.props.sql.show=true
    # 配置数据源
    spring.shardingsphere.datasource.driver-class-name=com.mysql.jdbc.Driver
    spring.shardingsphere.datasource.type=com.alibaba.druid.pool.DruidDataSource
    spring.shardingsphere.datasource.url=jdbc:mysql://127.0.0.1:3306/yc_test
    spring.shardingsphere.datasource.username=root
    spring.shardingsphere.datasource.password=123456
    # 配置具体表的分表策略,举例为按UserId字段取模4分表
    spring.shardingsphere.sharding.tables.ZmOrder.actual-data-nodes=master0.ZmOrder$->{0..3}
    spring.shardingsphere.sharding.tables.ZmOrder.table-strategy.inline.sharding-column=UserId
    spring.shardingsphere.sharding.tables.ZmOrder.table-strategy.inline.algorithm-expression=ZmOrder$->{UserId % 4}
    
    

4. 分库分表带来的问题和解决思路

分库分表之后,大表DQL和DML性能大幅度提升,满足了现有业务持续增长的需求,但是分库分表也引入了新的复杂度和新的问题:

  1. 跨库join

    跨库join可以从两个方面入手解决:

    业务层面:

    • 分析业务领域模型,识别主要模型,如ToC产品的用户、ToB产品的组织等,根据主要模型ID(UserId/GroupId)拆分数据。

    • 尽量避免另类的页面设计,如用户页面只显示用户自己的数据,避免多用户数据合并显示等。

    技术层面:

    • 拒绝连表查询,打散大join语句为小的单表查询,在代码里按需聚合数据,用多次小select代替一次大select。

    • 增加数据库字段冗余,代替连表查询,以空间换性能。

    • 冗余通用小表,如配置表、字典表等全量冗余到每个数据库中,保证程序尽量不跨库查询,以空间换性能。

  2. 多库数据一致性

    多库数据一致性可以使用分布式事务来解决。

  3. 排序/分页查询

    排序/分页查询问题需要改造现有功能,条件添加分片键。

  4. 多表主键冲突:分布式ID

  5. 全局数据统计

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章