一、名词解释

库:database;表:table;分库分表:sharding

二、数据库架构演变

刚开始我们只用单机数据库就够了,随后面对越来越多的请求,我们将数据库的写操作和读操作进行分离, 使用多个从库副本(Slaver Replication)负责读,使用主库(Master)负责写, 从库从主库同步更新数据,保持数据一致。架构上就是数据库主从同步。 从库可以水平扩展,所以更多的读请求不成问题。但是当用户量级上来后,写请求越来越多,该怎么办?加一个Master是不能解决问题的, 因为数据要保存一致性,写操作需要2个master之间同步,相当于是重复了,而且更加复杂。这时就需要用到分库分表(sharding),对写操作进行切分。

三、分库分表前的问题

任何问题都是太大或者太小的问题,我们这里面对的数据量太大的问题。

  1. 用户请求量太大
    因为单服务器TPS,内存,IO都是有限的。 解决方法:分散请求到多个服务器上; 其实用户请求和执行一个sql查询是本质是一样的,都是请求一个资源,只是用户请求还会经过网关,路由,http服务器等。
  2. 单库太大
    单个数据库处理能力有限;单库所在服务器上磁盘空间不足;单库上操作的IO瓶颈 解决方法:切分成更多更小的库
  3. 单表太大
    CRUD都成问题;索引膨胀,查询超时 解决方法:切分成多个数据集更小的表。

四、分库分表的方式方法

一般就是垂直切分和水平切分,这是一种结果集描述的切分方式,是物理空间上的切分。 我们从面临的问题,开始解决,阐述: 首先是用户请求量太大,我们就堆机器搞定(这不是本文重点)。然后是单个库太大,这时我们要看是因为表多而导致数据多,还是因为单张表里面的数据多。 如果是因为表多而数据多,使用垂直切分,根据业务切分成不同的库。如果是因为单张表的数据量太大,这时要用水平切分,即把表的数据按某种规则切分成多张表,甚至多个库上的多张表。 分库分表的顺序应该是先垂直分,后水平分。 因为垂直分更简单,更符合我们处理现实世界问题的方式。

1. 垂直拆分
  • 垂直分表
    也就是“大表拆小表”,基于列字段进行的。一般是表中的字段较多,将不常用的, 数据较大,长度较长(比如text类型字段)的拆分到“扩展表“。 一般是针对那种几百列的大表,也避免查询时,数据量太大造成的“跨页”问题。
  • 垂直分库(就是每个业务专门建库,不要把多个业务的表糅合在一个库里
    垂直分库针对的是一个系统中的不同业务进行拆分,比如用户User一个库,商品Producet一个库,订单Order一个库。切分后,要放在多个服务器上,而不是一个服务器上。为什么?我们想象一下,一个购物网站对外提供服务,会有用户,商品,订单等的CRUD。没拆分之前,全部都是落到单一的库上的,这会让数据库的单库处理能力成为瓶颈。按垂直分库后,如果还是放在一个数据库服务器上,随着用户量增大,这会让单个数据库的处理能力成为瓶颈,还有单个服务器的磁盘空间,内存,tps等非常吃紧。所以我们要拆分到多个服务器上,这样上面的问题都解决了,以后也不会面对单机资源问题。数据库业务层面的拆分,和服务的“治理”,“降级”机制类似,也能对不同业务的数据分别的进行管理,维护,监控,扩展等。 数据库往往最容易成为应用系统的瓶颈,而数据库本身属于“有状态”的,相对于Web和应用服务器来讲,是比较难实现“横向扩展”的。数据库的连接资源比较宝贵且单机处理能力也有限,在高并发场景下,垂直分库一定程度上能够突破IO、连接数及单机硬件资源的瓶颈。
  1. 水平拆分
  • 水平分表
    针对数据量巨大的单张表(比如订单表),按照某种规则(RANGE,HASH取模等),切分到多张表里面去。 但是这些表还是在同一个库中,所以库级别的数据库操作还是有IO瓶颈。不建议采用。
  • 水平分库分表
    将单张表的数据切分到多个服务器上去,每个服务器具有相应的库与表,只是表中数据集合不同。 水平分库分表能够有效的缓解单机和单库的性能瓶颈和压力,突破IO、连接数、硬件资源等的瓶颈。
  • 水平分库分表切分规则
    • RANGE
      从0到10000一个表,10001到20000一个表;
    • HASH取模
      一个商场系统,一般都是将用户,订单作为主表,然后将和它们相关的作为附表,这样不会造成跨库事务之类的问题。 取用户id,然后hash取模,分配到不同的数据库上。
    • 地理区域
      比如按照华东,华南,华北这样来区分业务,七牛云应该就是如此。
    • 时间
      按照时间切分,就是将6个月前,甚至一年前的数据切出去放到另外的一张表,因为随着时间流逝,这些表的数据 被查询的概率变小,所以没必要和“热数据”放在一起,这个也是“冷热数据分离”。
  • 水平分库
  • 水平分表(这些表还是在同一个库中,所以库级别的数据库操作还是有IO瓶颈)

    注意:随着数据库数据量增加,不要马上考虑做水平切分,首先考虑缓存处理,读写分离,使用索引等等方式,如果这些方式不能根本解决问题了,再考虑做水平分库和水平分表

五、分库分表后面临的问题

  1. 事务支持
    分库分表后,就成了分布式事务了。如果依赖数据库本身的分布式事务管理功能去执行事务,将付出高昂的性能代价; 如果由应用程序去协助控制,形成程序逻辑上的事务,又会造成编程方面的负担。
  2. 多库(多数据源问题)结果集合并(group by,order by)
  3. 跨库join(跨节点连接查询问题(分页、排序))
    分库分表后表之间的关联操作将受到限制,我们无法join位于不同分库的表,也无法join分表粒度不同的表, 结果原本一次查询能够完成的业务,可能需要多次查询才能完成。 粗略的解决方法: 全局表:基础数据,所有库都拷贝一份。 字段冗余:这样有些字段就不用join去查询了。 系统层组装:分别查询出所有,然后组装起来,较复杂。

六、Mysql分库分表例子(shardingsphere)

shardingsphere官网

  1. 水平分表

数据库创建两张一样结构的表

application.properties配置

# shardingjdbc分片策略
# 配置数据源,给数据源起名称
spring.shardingsphere.datasource.names=m1
# 一个实体类对应两张表,覆盖
spring.main.allow-bean-definition-overriding=true
#配置数据源具体内容,包含连接池,驱动,地址,用户名和密码
spring.shardingsphere.datasource.m1.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.m1.url=jdbc:mysql://localhost:63306/sharding?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8&useSSL=true&characterEncoding=UTF-8&rewriteBatchedStatements=true
spring.shardingsphere.datasource.m1.username=root
spring.shardingsphere.datasource.m1.password=root#指定course表分布情况,配置表在哪个数据库里面,表名称都是什么  m1.course_1 , m1.course_2
spring.shardingsphere.sharding.tables.course.actual-data-nodes=m1.course_$->{1..2}# 指定course表里面主键cid 生成策略  SNOWFLAKE
spring.shardingsphere.sharding.tables.course.key-generator.column=cid
spring.shardingsphere.sharding.tables.course.key-generator.type=SNOWFLAKE# 指定分片策略  约定cid值偶数添加到course_1表,如果cid是奇数添加到course_2表
spring.shardingsphere.sharding.tables.course.table-strategy.inline.sharding-column=cid
spring.shardingsphere.sharding.tables.course.table-strategy.inline.algorithm-expression=course_$->{cid % 2 + 1}# 打开sql输出日志
spring.shardingsphere.props.sql.show=true

测试:

@SpringBootTest
class ShardingApplicationTests {@AutowiredCourseMapper courseMapper;@Testvoid contextLoads() {for (long i = 0 ; i<20; i++){courseMapper.insert(new Course(null,String.valueOf(i+1),i,String.valueOf(i)));}}
}

course_1

course_2

  1. 水平分库分表
    分别创建sharding_1库,sharding_2库,每个库下再创建course_1表,course_2表,course_3表

application.properties配置

# shardingjdbc分片策略
# 配置数据源,给数据源起名称,
# 水平分库,配置两个数据源(分别对应shardin_g1,sharding_2)
spring.shardingsphere.datasource.names=m1,m2#配置第一个数据源具体内容,包含连接池,驱动,地址,用户名和密码
spring.shardingsphere.datasource.m1.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.m1.url=jdbc:mysql://localhost:63306/sharding_1?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8&useSSL=true&characterEncoding=UTF-8&rewriteBatchedStatements=true
spring.shardingsphere.datasource.m1.username=root
spring.shardingsphere.datasource.m1.password=root#配置第二个数据源具体内容,包含连接池,驱动,地址,用户名和密码
spring.shardingsphere.datasource.m2.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m2.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.m2.url=jdbc:mysql://localhost:63306/sharding_2?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8&useSSL=true&characterEncoding=UTF-8&rewriteBatchedStatements=true
spring.shardingsphere.datasource.m1.username=root
spring.shardingsphere.datasource.m1.password=root#指定数据库分布情况,数据库里面表分布情况
# m1  m2    course_1 course_2 course_3
spring.shardingsphere.sharding.tables.course.actual-data-nodes=m$->{1..2}.course_$->{1..3}# 指定course表里面主键cid 生成策略  SNOWFLAKE
spring.shardingsphere.sharding.tables.course.key-generator.column=cid
spring.shardingsphere.sharding.tables.course.key-generator.type=SNOWFLAKE# 指定表分片策略  约定cid%3=0加到course_1表,如果cid%3=1添加到course_2表, 如果cid%3=2添加到course_3表
spring.shardingsphere.sharding.tables.course.table-strategy.inline.sharding-column=cid
spring.shardingsphere.sharding.tables.course.table-strategy.inline.algorithm-expression=course_$->{cid % 3 + 1}# 指定库分片策略 约定user_id是偶数添加m1,是奇数添加m2
spring.shardingsphere.sharding.tables.course.database-strategy.inline..sharding-column=user_id
spring.shardingsphere.sharding.tables.course.database-strategy.inline.algorithm-expression=m$->{user_id % 2 + 1}# 打开sql输出日志
spring.shardingsphere.props.sql.show=true

测试:

@SpringBootTest
class ShardingApplicationTests {@AutowiredCourseMapper courseMapper;@Testvoid contextLoads() {for (long i = 0 ; i<20; i++){courseMapper.insert(new Course(null,String.valueOf(i+1),i,String.valueOf(i)));}}
}

这里只展示userid为偶数的数据(完美实现分库分表)


测试获取一条数据

 @Testvoid get(){Course course = courseMapper.selectById(631501645238239232L);}


可以看到,因为无法确定该数据是在哪个库(因为我们查询的时候并没有指定分库时用的userid),所以shardingproxy就到两个库里找,但是根据cid,shardingproxy能确定在course_3库,所以,只在course_3里查找。
参考文章

  1. 公共表
    公共表属于系统中数据量较小,变动少,而且属于高频联合查询的依赖表。参数表、数据字典表等属于此类型。可以将这类表在每个数据库都保存一份,所有更新操作都同时发送到所有分库执行。
  • 创建dict表

添加以下配置:

# 添加公共表
spring.shardingsphere.sharding.broadcast-tables=dict
spring.shardingsphere.sharding.tables.dict.key-generator.column=dict_id
spring.shardingsphere.sharding.tables.dict.key-generator.type=SNOWFLAKE

测试

  @Testvoid insertCommon(){dictMapper.insert(new Dict(null,"1","1","1"));}

结果可以看到,两个数据源中都插了该信息

删除的时候也是全部删除

  1. 面对日益增加的系统访问量,数据库的吞吐量面临着巨大瓶颈。 对于同一时刻有大量并发读操作和较少写操作类型的应用系统来说,将数据库拆分为主库和从库,主库负责处理事务性的增删改操作,从库负责处理查询操作,能够有效的避免由数据更新导致的行锁,使得整个系统的查询性能得到极大的改善

    可以看到,读写分离需要配合数据库的主从复制,而Sharding-JDBC读写分离则是根据SQL语义的分析,将读操作(select)和写操作(insert update)分别路由至主库与从库,它提供透明化的读写分离,让使用方尽量使用一个数据库一样使用主从数据库集群。

在这里我部署了两台mysql从机,和一台mysql主机,主机负责写,两台从机同步主机数据并负责读。

结构如下:

数据分离+读写分离的配置文件

# shardingjdbc分片策略
# 配置数据源,给数据源起名称
# 水平分库
spring.shardingsphere.datasource.names=m1,m2,s1,s2,s3,s4##配置数据源具体内容,包含连接池,驱动,地址,用户名和密码
spring.shardingsphere.datasource.m1.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.m1.url=jdbc:mysql://ip:13306/sharding_1?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8&useSSL=true&characterEncoding=UTF-8&rewriteBatchedStatements=true
spring.shardingsphere.datasource.m1.username=root
spring.shardingsphere.datasource.m1.password=rootspring.shardingsphere.datasource.m2.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m2.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.m2.url=jdbc:mysql://ip:13306/sharding_2?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8&useSSL=true&characterEncoding=UTF-8&rewriteBatchedStatements=true
spring.shardingsphere.datasource.m2.username=root
spring.shardingsphere.datasource.m2.password=rootspring.shardingsphere.datasource.s1.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.s1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.s1.url=jdbc:mysql://ip:23306/sharding_1?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8&useSSL=true&characterEncoding=UTF-8&rewriteBatchedStatements=true
spring.shardingsphere.datasource.s1.username=root
spring.shardingsphere.datasource.s1.password=rootspring.shardingsphere.datasource.s2.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.s2.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.s2.url=jdbc:mysql://ip:23306/sharding_2?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8&useSSL=true&characterEncoding=UTF-8&rewriteBatchedStatements=true
spring.shardingsphere.datasource.s2.username=root
spring.shardingsphere.datasource.s2.password=rootspring.shardingsphere.datasource.s3.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.s3.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.s3.url=jdbc:mysql://ip:33306/sharding_1?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8&useSSL=true&characterEncoding=UTF-8&rewriteBatchedStatements=true
spring.shardingsphere.datasource.s3.username=root
spring.shardingsphere.datasource.s3.password=rootspring.shardingsphere.datasource.s4.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.s4.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.s4.url=jdbc:mysql://ip:33306/sharding_2?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8&useSSL=true&characterEncoding=UTF-8&rewriteBatchedStatements=true
spring.shardingsphere.datasource.s4.username=root
spring.shardingsphere.datasource.s4.password=root#指定数据库分布情况,数据库里面表分布情况
# m1  m2    course_1 course_2 course_3
spring.shardingsphere.sharding.tables.course.actual-data-nodes=m$->{1..2}.course_$->{1..3}
# 指定course表里面主键cid及其生成策略:SNOWFLAKE
spring.shardingsphere.sharding.tables.course.key-generator.column=cid
spring.shardingsphere.sharding.tables.course.key-generator.type=SNOWFLAKE# 指定分片策略约定cid%3=0写入course_1表,cid%3=1写入course_2表,cid%3=2写入course_3表
spring.shardingsphere.sharding.tables.course.table-strategy.inline.sharding-column=cid
spring.shardingsphere.sharding.tables.course.table-strategy.inline.algorithm-expression=course_$->{cid % 3 + 1}# 指定数据库分片策略约定user_id是偶数添加m1,是奇数添加m2
spring.shardingsphere.sharding.tables.course.database-strategy.inline..sharding-column=user_id
spring.shardingsphere.sharding.tables.course.database-strategy.inline.algorithm-expression=m$->{user_id % 2 + 1}spring.main.allow-bean-definition-overriding=true# 指定公共表
spring.shardingsphere.sharding.broadcast-tables=dict
spring.shardingsphere.sharding.tables.dict.key-generator.column=dict_id
spring.shardingsphere.sharding.tables.dict.key-generator.type=SNOWFLAKE# 绑定主从库关系
spring.shardingsphere.sharding.master-slave-rules.m1.master-data-source-name=m1
spring.shardingsphere.sharding.master-slave-rules.m1.slave-data-source-names=s1,s3
spring.shardingsphere.sharding.master-slave-rules.m1.load-balance-algorithm-type=ROUND_ROBIN
spring.shardingsphere.sharding.master-slave-rules.m2.master-data-source-name=m2
spring.shardingsphere.sharding.master-slave-rules.m2.slave-data-source-names=s2,s4# 打开sql输出日志
spring.shardingsphere.props.sql.show=true
  • 查询:
    @Testvoid get(){LambdaQueryWrapper<Course> queryWrapper=new LambdaQueryWrapper();queryWrapper.eq(Course::getUserId, 43);System.out.println(courseMapper.selectOne(queryWrapper));}


可以看到,由于是使用user_id进行查询的,sharding根据43自然知道只需要在user_id为单数的那个库查找即可,而且由于查询是轮询的,所以可以看到两个从库被轮询查找,但是每次查找的表是不一样的

  • 插入
 @Testvoid contextLoads() {for (long i = 102 ; i<112; i++){Course course=new Course(null,String.valueOf(i),i+1,String.valueOf(i));courseMapper.insert(course);}}


可以看到,只在m1,m2之间轮询插入数据,而不会在从库(s1,s2,s3,s4)进行插入

更详细的配置文件参考官网

Mysql的分库分表(基于shardingsphere)相关推荐

  1. MySQL(十一):分库分表方案-ShardingSphere

    Sharding-JDBC Sharding-JDBC定位为轻量级Java框架,在Java的JDBC层提供的额外服务.可理解为增强版的JDBC驱动,完全兼容JDBC和各种ORM 框架的使用 Shard ...

  2. 最全的MySQL 常用分库分表方案,都在这里!

    点击上方蓝色字体,选择"标星公众号" 优质文章,第一时间送达关注公众号后台回复pay或mall获取实战项目资料视频 点击此链接:一套的SpringCloud版聚合支付项目,资料文档 ...

  3. node.js+mysql实现分库分表存查数据:

    node.js+mysql实现分库分表: 1.分库分表使用场景: 互联网项目中常用到的关系型数据库,如MySQL,随着用户和业务的增长,传统的单库单表模式难以满足大量的业务数据存储以及查询,单库单表中 ...

  4. MySQL之分库分表

    文章目录 MySQL之分库分表 1.问题分析 2.分库分表介绍 3.分库分表拆分策略 4.垂直分库 4.1介绍 4.2特点 5.垂直分表 5.1介绍 5.2特点 6.水平分库 6.1介绍 6.2特点 ...

  5. 一文搞懂MySQL数据库分库分表

    如果数据量过大,大家一般会分库分表.分库需要注意的内容比较少,但分表需要注意的内容就多了. 工作这几年没遇过数据量特别大的业务,那些过亿的数据,因为索引设置合理,单表性能没有影响,所以实战中一直没用过 ...

  6. mysql proxy 分库分表_OneProxy实现MySQL分库分表

    简介 Part1:写在最前 随着网站的壮大,MySQL数据库架构一般会经历一个过程: 当我们数据量比较小的时候,一台单实例数据库足矣.等我们数据量增大的时候,我们会采用一主多从的数据库架构来降低我们的 ...

  7. 互联网公司MySQL常用分库分表方案总结

    更多内容关注微信公众号:fullstack888 一.数据库瓶颈 1.IO瓶颈 2.CPU瓶颈 二.分库分表 1.水平分库 2.水平分表 3.垂直分库 4.垂直分表 三.分库分表工具 四.分库分表步骤 ...

  8. Mysql分库分表工具ShardingSphere

    ShardingSphere是一款起源于当当网内部的应用框架.2015年在当当网内部诞生,最初就叫ShardingJDBC.2016年的时候,由其中一个主要的开发人员张亮,带入到京东数科,组件团队继续 ...

  9. 如何完成一次快速的查询 - 从MySQL到分库分表到ES和HBASE

    哪个男孩不想完成一次快速的查询? 1. MySQL 查询慢是什么体验? 谢邀,利益相关. 大多数互联网应用场景都是读多写少,业务逻辑更多分布在写上.对读的要求大概就是要快.那么都有什么原因会导致我们完 ...

最新文章

  1. git解决“failed to push some refs to“问题
  2. 算法学习:最小圆覆盖
  3. C语言/C++ 中除法得到结果的精度问题(有浮点数参与除法,结果就是浮点数,除数和被除数都是整数,结果默认是整数)
  4. eta 深度学习 参数_深度学习中的参数梯度推导(一)
  5. How to remove ROM cfg in MAME
  6. python records库_Python Records库使用举例
  7. SOA架构设计和相关案例分析
  8. 18.nginx 服务器的代理服务
  9. 微信公众号号开发小记(六)使用开源框架开发
  10. 调用Windows远程桌面应用
  11. c# openxml Excel部分学习之路
  12. Image data cannot be converted to float
  13. 《自然语言处理实战入门》文本分类 ---- 使用词向量与SVM 进行文本分类
  14. 从软件工程师到创业二十年从业经历感悟
  15. 活动二维码怎么制作?如何将活动内容做成二维码图片?
  16. 配置ubuntu20.04 LTS深度学习工作站教程
  17. Error: Run Generate Functional Simulation Netlist
  18. 论文笔记 | 深度学习图像数据增广方法研究
  19. bugtrap microsoft 默认崩溃提示框_买了不吃亏?微软(Microsoft)Surface go P?鼠标仅售608.00元_...
  20. c/c++:UDP(udp通信、广播、组播),本地套接字

热门文章

  1. 动态规划入门_数塔问题
  2. 深度学习常见算法之训练自己的数据
  3. java实现随机验证码的图片
  4. 现代谱估计:Blackman-Tukey 相关图
  5. linux下网络排错与查看
  6. getopt的用法与optarg
  7. Ubuntu下安装Qt全部过程
  8. 智能系统课件@丛爽老师-中国科学技术大学自动化系
  9. vue手机端回退_vue移动端弹框组件,vue-layer-mobile
  10. win10删除开机密码_新版WIN10 如何取消开机密码的操作