mysql aop_使用AOP进行MySQL切表

2ff34e647e2e3cdfd8dca593e17d9b0a.png

一. 为什么需要切表

在实现这个功能是首先考虑的是,为什么我们需要进行分库分表。

关系型数据库本身比较容易成为系统的瓶颈(再深入则为关系型数据库的读写一般都为对文件的I/O操作,而硬盘I/O是整个计算机系统中性能比较差的一个部分)。所以当查询维度较多,即使添加了从库,优化了索引,很多操作的性能仍然无法满足需求时,则需要对数据库进行切分。

数据库分布式核心内容就是数据切分(Sharding),以及切分后对数据的定位,整合。依据切分类型,可分为垂直(纵向)切分和水平(横向)切分。

1. 垂直切分

垂直切分常见垂直分库和垂直分表。

(1) . 垂直分库依据业务耦合性,将关联度低的不同表存储在不同的数据库中。做法同大系统和多个小系统类似,按照业务分类进行独立划分。与“微服务治理”的做法类似。例如:

主要数据库—-一个集群

日志库—-一个集群

报表库—-一个集群

(2) . 垂直分表则是基于数据库中的“列”进行,某个表的字段过多,则可以新建一张扩展表,将不常使用的字段或者字段长度较大的字段拆分出去。例如:[用户基本信息表,用户详情表,用户地址表] 。通过“大表拆小表”避免了跨页问题(MySQL底层通过数据页存储,一条记录占用空间过大会导致跨页,造成额外的性能开销)。另外数据库以行为单位将数据加载到内存中,当字段长度较短时且访问频率高时,内存能加载更多数据,命中率更高,从而减少对磁盘的I/O。

优点:解决业务层面耦合。

微服务治理类似的分级管理,维护,监控和扩展。

高并发下提高一定的性能优化

缺点:部分表无法join,只能接口聚合,提高了开发难度

分布式事务处理复杂

依然存在单表过大问题

2. 水平切分

当一个应用无法以再细粒度的垂直切分,或者切分后的数据量行数巨大,存在单库读写,存储性能瓶颈,此时就需要水平切分。水平切分分为库内分表和分库分表,根据表内数据内在的逻辑关系,将同一个表按不同的条件分散到多个数据库或多个表中,每个表中只包含一部分数据,从而使得单个表的数据量变小,达到分布式的效果。

如:日志数据库或者报表数据库(以时间为粒度):以年份分库(log_2018,log_2019)

以月份分表(log_2019.log_2019_01,log_2019.log_2019_02)

优点:单库数据量保持在一定程度,高并发性能好,系统稳定性和负载能力强。

应用端改造小,不需要拆分业务模块

缺点:跨分片的事务一致性难以保证

跨库的Join关联查询性能较差

数据多次扩展难度和维护量极大

(1) . 根据数值范围切分

按照时间区间或者ID区间来切分:按日期将不同月甚至是日的数据分散到不同的库中;将userId为19999的记录分到第一个库,1000020000的分到第二个库,以此类推。某种意义上,某些系统中使用的”冷热数据分离”,将一些使用较少的历史数据迁移到其他库中,业务功能上只提供热点数据的查询,也是类似的实践。

优点:单表大小可控

天然便于水平扩展,后期如果相对整个分片集群扩容时,只需要添加节点即可,无需迁移数据

使用分片字段进行范围查找时,连续分片可以快速定位分片进行快速查询,避免了跨分片查询问题。

缺点:热点数据成为性能瓶颈。连续分片可能存在数据热点,如按时间反字段分片时,最近时间段的数据可能会被频繁访问。而历史数据可能很少被查询。

(2) . 根据数据取模

一般采用Hash取模Mod的切分方式,如:将Partner表根据partner_id 切分到4个库中,余数为0的放到第一个库中,余数为1的放到第二个库中,以此类推。这样同一个用户的数据会分散到同一个库中,如果查询条件带有partner_id,则可明确定位到相应库中查询。

优点:数据分片相对均匀,不容易出现热点和并发访问的瓶颈。

缺点:后期分片集群扩容时,需要迁移旧的数据(使用一致性HASH算法则可以较好的避免这个问题)

容易面临跨分片查询的复杂问题。如果频繁使用了不带partner_id的查询条件,则无法定位数据库,从而向4个分片数据库发起查询,再在内存中合并数据,取最小集返回给应用,分库反而成为瓶颈。

3. 引发的各种问题

数据切分有可能会引发各种事物一致性等问题,在这里不做过多赘述,只是为了记录在代码层面上如何优化水平切分业务。

二. 使用AOP对相关业务表进行水平切分

上面提到了一个水平切分,在这里用实际案例:将报表以时间为维度切分为多表。

在我实际操作时,遇到了Aspect不生效的问题,google了很久之后莫名其妙解决了,至今未发现问题在哪里。

1. 引入依赖1

2

3

4

org.springframework.boot

spring-boot-starter-aop

由于Spring的属性[ spring.aop.auto=true ] 默认开启为true,也就是引入依赖后默认开启AOP。所以无须做过多Config操作1

2

3

4

5

6

7

8

9/**

* 声明一个以注解,在需要进行分表的方法中进行AOP切面

*

* /

@Documented

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

public @interface TimeSpiltTable {

}

3. 编写Aspect1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34/**

* 带时间切分表的AOP,格式为{##_yyyy_MM}.

*/

@Slf4j

@Component

@Aspect

public class TimeSpiltAspect{

/**

* 定义注解声明的方法,并进行切入

*/

@Pointcut("@annotation(com.exmaple.aop.annotation.TimeSpiltTable)")

public void pointcut(){

}

@Before("pointcut()")

public void concatTimeToTableName(JoinPoint joinPoint){

//获取参数数组

Object[] args = joinPoint.getArgs();

try {

for (Object parameter : args) {

//若为Map

if (parameter instanceof Map) {

Map map = (Map) parameter;

//这里是业务代码,获取传入的参数Map中的表名,然后再获取当前时间,转换为{yyyy_MM}格式,再拼接表名。

//重新赋值

map.put(Constants.DB_TABLE_NAME_PARAM, tableName);

}

}

} catch (Exception e) {

log.info("concatTimeToTableName Exception,exception={}, args={} ,", e.getMessage(), JSON.toJSON(args));

}

}

}

这一部分的代码为什么这样做,因为本项目使用的是MyBatis,我们进行了大量的分库分表,所以需要用MyBatis拼接SQL来进行定位不同的数据库和数据表。为了方便传入MyBatis,Mapper使用的参数统一为Map。所以在这里其实是对Mapper进行了AOP,并将Mapper方法的参数中的表名获取,再拼接成为分表的SQL。

为什么使用AOP去做这件事呢,因为这个表和这个业务原本就已经存在,只是在系统发展过程中,单表不满足性能需求,则进行了切分,使用AOP来做这一件事,第一是高效,这些代码编写不过几分钟。第二是不影响原本的代码,直接进行了扩展而已。

不过这种AOP做法也会有缺陷(无考证):就是在AOP不生效时,将会查询到没有分表后的原表名,可能导致程序错误,此时排查定位对维护的人有一定的难度。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值