大表优化分库分表

529 阅读9分钟

前言:内容基本来自于自己之前做过的笔记

当MySQL单表记录数过大时,数据库的CRUD性能会明显下降,一些常见的优化措施如下:

1. 限定数据的范围

务必禁止不带任何限制数据范围条件的查询语句。比如:我们当用户在查询订单历史的时候,我们可以控制在一个月的范围内;

2. 读/写分离

经典的数据库拆分方案,主库负责写,从库负责读;

数据是按照订单id或者某id进行hash,分布到对应的数据库系统中去

拆完以后,每个数据库压力大大降低,降低为1/3 跑sql的时间也会降低

一开始是先分库,把库拆开,每个库一模一样的表

1.分库分表中间件

收到数据的时候怎么把数据分发到各个数据库上面去? 此时需要借助分库分表的中间件(数据库中间件,收到数据 根据orderid或者其他字段进行分发到不同的数据库)

有两种,一种是独立部署的proxy,还有一种是client形式,引用jar包 放在一块,直接分发

sharding JDBC(属于client层的方案)比较稳定,现在用的比较多;不需要额外部署,不需要二次转发,性能很高。 缺点是如果遇到升级,各个系统重新升级再发布,各个系统都需要耦合sharding JDBC的依赖

mycat,属于proxy层方案,比较年轻,需要自己去部署,需要运维。好处在于比较透明,升级的时候直接去升级就可以了,独立部署的

2.如何具体的拆分的?

水平拆分和垂直拆分

**垂直拆分:**列变少了 ,字段拆分;每个库表包含部分字段,一般来说,会将较少的访问频率很高的字段放在一个表里, 访问频率很低的字段放在一个表里; 访问频率高的字段 拆完以后数据量小(放到缓存里面 可以放的行数就越多); 把一个大表拆开,订单表,订单支付表,订单商品表

  • 垂直拆分的优点: 可以使得列数据变小,在查询时减少读取的Block数,减少I/O次数。此外,垂直分区可以简化表的结构,易于维护。
  • 垂直拆分的缺点: 主键会出现冗余,需要管理冗余列,并会引起Join操作,可以通过在应用层进行Join来解决。此外,垂直分区会让事务变得更加复杂;

**水平拆分:**单表数据 600万行,拆完以后放到几个数据库里; 每个数据库里放一部分(然后再分成多个表)

1.根据id来取模 hash 决定放到哪个数据库里去(还可以根据订单的创建时间,一月份的,二月份的,第一周,第二周 第三周 ,按时间来分 可以按照range来分),hash的好处是 可以平衡数据库的请求和压力; 坏处是扩容的时候比较麻烦(扩容的时候会有数据迁移的过程)

2.按照range来分的话,在后面扩容的时候比较方便。缺点是:一般数据查询请求 大部分都是访问最新的数据,所以前面月份的数据很少被访问到。 实际生产中用range ,要看场景,你的用户不仅仅是访问最新的数据,而是均匀的访问现在的数据和历史的数据

相对均匀的分布,sql的执行效率增加好几倍,磁盘占用率也下来了!

分库分表一般说的是水平拆分,垂直拆分一般在数据库设计的时候就做了

**优点:**水平拆分可以支持非常大的数据量。需要注意的一点是:分表仅仅是解决了单一表数据过大的问题,但由于表的数据还是在同一台机器上,其实对于提升MySQL并发能力没有什么意义,所以 水平拆分最好分库

**缺点:**水平拆分能够 支持非常大的数据量存储,应用端改造也少,但 分片事务难以解决 ,跨节点Join性能较差,逻辑复杂。《Java工程师修炼之道》的作者推荐 尽量不要对数据进行分片,因为拆分会带来逻辑、部署、运维的各种复杂度 ,一般的数据表在优化得当的情况下支撑千万以下的数据量是没有太大问题的。如果实在要分片,尽量选择客户端分片架构,这样可以减少一次和中间件的网络I/O。

3.如果把系统不停机把未分库分表的系统迁移到分库分表的系统里去?

600W的数据量分到3个数据库里,每个数据库里4个表,每个表里50万数据量,如何迁移呢?

1.长时间停机迁移分表(挂一个公告,哪一点哪一点,停机运维,网站维护)

多开几个机器,20个线程,开一个后台临时程序,多起几个线程,通过数据库中间件,配置好规则,然后不断的读出来,写到分好的数据库里去

修改系统的配置,让数据写入到数据库中间件里,然后数据库中间件进行分发; 很久以前是这么做的!!

缺点:几个小时的停机,如果还没有搞定,就先回滚,第二天凌晨继续搞

2.不停机双写方案

比较常见

修改系统中写库的代码,同时让他写老库和新的分库分表的库

后台捣数据

同时,开启后台数据迁移的工具(标准的数据库设计 都会包含一个字段,最后修改时间, 判断一下数据是否在分库分表里面,如果不存在直接写入,如果存在,那就比较两个数据的时间戳,如果要新,那就覆盖分库分表里的数据) 迁移一轮,再执行一次检查,检查两个库里的数据是否一致,如果不一样,针对不一样的数据,从单库单表里面读取出来,比对时间戳,判断是否需要覆盖;

最后一步 修改代码,仅仅写分库分表里的表就可以了 再次部署就可以了。

4.如何设计可以动态扩容缩容的分库分表方案?

1.选择一个数据库中间件,调研 学习,测试

2.设计你的分库分表的方案,要分成多少个库 多少个表

3.基于选择好的数据库中间件,在测试环境建立好分库分表的环境,然后测试一下能否进行正常的分库分表的读写

4.完成单库单表到分库分表的迁移,双写方案

5.线上系统开始基于分库分表对外提供服务

6.扩容的话,如何去动态扩容呢?

解决方案:

1.停机扩容

跟停机迁移一样,也是停机,通过数据库中间件去做,最后修改配置即可

缺点:花费的时间比较长,扩容的时间很长

2.优化后的方案

32 * 32 分成32个库,每个库里面32个表 基本可以每秒承载 几万条的写请求

第一次分库分表 ,就一次性分个够 32 * 32

启动mysql 可以在mysql里开多个服务,每个数据库服务器开了8个库,四台数据库服务器一共开了32个库,每个库有32张表

怎么把写并发扩一倍,加四台机器,分别迁移几个库;

数据库扩容的原因:数据库磁盘快满了,还有就是数据库写并发承载不了

只是库迁移

最多扩到32个服务器,每个数据库服务器里只有一个库,一个库里有32个表;不用改变数据库和表的数量(没太懂) 分到库 然后分到表里 ,先% 然后/了以后再%

最大的好处是 数据库不需要做迁移,只需要修改配置 ,地址就可以了(没懂)

反正就是可以扩服务器的数量,先取模再/

如果缩容的话 ,就是改变服务器的数量就可以了,不需要把里面的数据抽取出来

整体思路: 扩服务器的数量,然后由DBA去把里面的库整体的迁出来

5.分库分表以后,全局性的id主键怎么处理?

如果不处理,都是自增,会出现id相同的情况,因此不能自增

1.基于数据库自增id(专门生成主键的全局库)

生成主键的库,全局只有1个,插入一条数据以后,自增id为1;也就是插入之前先往一个全局的库里面插,然后按照里面的id来放入自己的数据库里;

适合并发很低,但是数据量很大的情况

2.uuid

基本可以保证是唯一的,但是缺点是太长了,作为主键的性能很差,不适合做主键

适合的场景是:随机生成一个文件名,编号什么的,主键最好不要用uuid

3.获取当前系统时间

并发量很高的时候,会有重复,基本不考虑;

适合的场景:和当前业务哦其他字段拼接起来,作为一个id是可以的,组成一个全局唯一的编号; 比如订单编号:时间戳+用户id+业务含义的编码

4.snowflake算法

64位long型的id 转化为二进制

第一位是0 表示是正数

中间的41位是放时间戳,做了一些计算,换算对应的二进制 用了其中41位来放

后面的五位放机房id, 把机房的id转成二进制,要求最多32个机房

再后面五位是机器id,把机器的id转成二进制,要求最多32个机器,如果大于或者小于 会报错

最后十二位是序号,在同一时间同一个机房同一个机器,一毫秒内,如果要求再次生成一个id,就再这个序号后面 + 1 按照二进制去累加; 最多只能生成4096个id