• mysql 多数据源与事务嵌套不兼容解决方案


    需求与问题

    最近,因为某一个项目需求的数据量过大,已经对其它服务造成了一定的影响。所以为了给数据库减负,将这部分业务需求涉及到的数据给迁到新库中。但这就出现了个问题,这部分业务涉及到的业务表我虽然迁到新库中,但涉及到一些扩展类的业务表,比如日志信息等,因为监控的是整个系统的日志,所以肯定不能把这个表给单独拿出来。所以,这里就涉及到了双数据源的访问。并且在一个方法中涉及到双数据源问题,切换以及对应的事务信息怎么解决。

    数据源配置

    1. spring:
    2. autoconfigure:
    3. exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
    4. datasource:
    5. dynamic:
    6. primary: master
    7. druid: # 全局druid参数,绝大部分值和默认保持一致。(现已支持的参数如下,不清楚含义不要乱设置)
    8. initial-size: 5
    9. max-active: 20
    10. min-idle: 5
    11. max-wait: 60000
    12. time-between-eviction-runs-millis: 50000
    13. min-evictable-idle-time-millis: 120000
    14. max-evictable-idle-time-millis: 280000
    15. test-while-idle: true
    16. test-on-borrow: false
    17. test-on-return: false
    18. validation-query: SELECT 1
    19. keep-alive: true
    20. pool-prepared-statements: true
    21. max-pool-prepared-statement-per-connection-size: 20
    22. filters: stat,slf4j # 注意这个值和druid原生不一致,默认启动了stat,wall
    23. datasource:
    24. master:
    25. url: jdbc:p6spy:mysql://172.16.15.92:3306/questionnaire_audit?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false&connectTimeout=6000&socketTimeout=30000&autoReconnect=true&failOverReadOnly=false&serverTimezone=Asia/Shanghai
    26. driver-class-name: com.p6spy.engine.spy.P6SpyDriver
    27. username: airita
    28. # @s#wwsOPw$RF6tfc%e
    29. password: ENC(u2iKA7ejQVSQW32OvNAcErwYiXI2RjacdCTGli7PjxQ=)
    30. ds2:
    31. url: jdbc:p6spy:mysql://172.16.15.92:3306/airita?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false&connectTimeout=6000&socketTimeout=30000&autoReconnect=true&failOverReadOnly=false&serverTimezone=Asia/Shanghai
    32. driver-class-name: com.p6spy.engine.spy.P6SpyDriver
    33. username: airita
    34. password: ENC(u2iKA7ejQVSQW32OvNAcErwYiXI2RjacdCTGli7PjxQ=)
    35. jasypt:
    36. encryptor:
    37. password: airita

    解决方案

    个人在思考怎么解决这方面的问题时,想的解决方案。现在切换数据源的操作,我就用baomidou的@DS注解来做了。后续主要目的是为了解决事务与@DS注解的兼容问题,自身设想的方案为mysql触发器,spring事务隔离级别,以及@DSTransaction注解三种方案。最后采用了最后一种。

    mysql触发器方案

    既然问题是双数据源造成的,那么我把涉及到的扩展类业务表(这类业务表不多,而且只有查询方法)通过触发器的方式,实时同步到新库中,这样,在一个方法中,我访问的都是单独一个数据源的数据。那么@Transaction事务,不就没问题了?

    1. - 创建sys_user触发器
    2. create table questionnaire_audit.sys_user like sys_user;
    3. insert into questionnaire_audit.sys_user
    4. select * from sys_user;
    5. CREATE TRIGGER sys_user_insert AFTER INSERT ON sys_user FOR EACH ROW
    6. BEGIN
    7. -- 插入目标表
    8. INSERT INTO
    9. questionnaire_audit.sys_user(user_id,login_id,password,org_id,user_name,user_sex,user_mobile,user_mail,create_time,update_time,user_state,comments,incharge_flag,source,accessMethod,userCreateType,userAuditStatus,userOrg,secretKey,foura_status,qyzx_login_id,qyzx_status)
    10. VALUES
    11. (new.user_id,new.login_id,new.password,new.org_id,new.user_name,new.user_sex,new.user_mobile,new.user_mail,new.create_time,new.update_time,new.user_state,new.comments,new.incharge_flag,new.source,new.accessMethod,new.userCreateType,new.userAuditStatus,new.userOrg,new.secretKey,new.foura_status,new.qyzx_login_id,new.qyzx_status);
    12. END;
    13. CREATE TRIGGER sys_user_delete AFTER DELETE ON sys_user FOR EACH ROW
    14. BEGIN
    15. -- INSE
    16. DELETE FROM questionnaire_audit.sys_user WHERE user_id = old.user_id;
    17. END;
    18. CREATE TRIGGER sys_user_update AFTER UPDATE ON sys_user FOR EACH ROW
    19. BEGIN
    20. UPDATE questionnaire_audit.sys_user
    21. SET user_id=new.user_id,login_id=new.login_id,password=new.password,org_id=new.org_id,user_name=new.user_name,user_sex=new.user_sex,user_mobile=new.user_mobile,user_mail=new.user_mail,create_time=new.create_time,update_time=new.update_time,user_state=new.user_state,comments=new.comments,incharge_flag=new.incharge_flag,source=new.source,accessMethod=new.accessMethod,userCreateType=new.userCreateType,userAuditStatus=new.userAuditStatus,userOrg=new.userOrg,secretKey=new.secretKey,foura_status=new.foura_status,qyzx_login_id=new.qyzx_login_id,qyzx_status=new.qyzx_status
    22. WHERE user_id = new.user_id;
    23. END;

    对旧库的表来创建新建,修改,删除三方面的触发器。

    后续想了下,这块的业务虽然很少改动,但后期也不敢保证完全不变。也就是说,后续我在新库要对扩展业务表想进行插入时,假设老库同时有大批量数据插入,会出问题吗?而且有的表是整个大项目通用的全量业务表,如果其它核心业务内容进行操作,那同步到我新库,我觉得也不合适。而且性能因为同步,所以性能也降低了太多。废弃。

    这个其实我的目的就是练习一下触发器,以前也没用过。

    Spring事务隔离级别

    上面的解决方案是从数据库层面来解决的,该怎么说,太粗暴了。那么,既然事务出现的问题,那么我是否可以在事务层面来对这个问题进行解决?一个事务有问题,多个事务行不行?

    @Transaction开的事务,可以理解为它是默认开启了主数据源的事务,在这个主事务的数据远源中,使用@DS注解然后对数据源进行切换,肯定是不行的。

    Spring的事务隔离级别,默认情况下是require类型的事务。这种事务代表了,当外部存在事务时,内部方法存在事务,则需要将内部事务加入到外部事务中。而外部不存在事务时,内部事务自己新开一个事务来处理。也就是说,如果存在外部事务,内部事务与外部事务同属一个事务。这肯定不行的。

    那么别的事务级别,require_new级别。当外部存在事务时,内部方法存在事务,则需要将内部事务新开一个事务处理。而外部不存在事务时,内部事务自己新开一个事务来处理。如果这么处理的话,把针对各自不同的数据源操作,放到不同的方法中,使用require_new开启新的事务,可以解决的。具体过程去网上查一下,很多文章的,只不过这种方法我没实践,后续也会练习一下。

    不采用这种方式的原因有俩,一方面,将针对不同的数据源的操作存放到不同的方法中,设置对应的隔离级别。那么对业务的操作顺序有一定要求。另一方面,因为在不同的事务中,事务回滚该怎么办。这种的回滚方式,我后续也网上查下资料,并且实践下。

    @DSTransaction注解

    这个也是在网上找到的,看名字就知道了,和@DS注解同属baomidou一家公司的,不过包的版本要求是高版本。使用方法很简单,使用@DSTransaction直接替换了原先的@Transaction即可。

    1. /**
    2. * 语音转写结果回传地址
    3. */
    4. @RequestMapping("/{version}/{identificationType}/returnService/inside")
    5. @ResponseBody
    6. //baomidou的DS与事务注解冲突
    7. // @Transactional(rollbackFor = Exception.class)
    8. @DSTransactional
    9. public void transferReturnServiceInside(HttpServletRequest request, @PathVariable String version, @PathVariable String identificationType) {
    10. }

    感想

    其实,学习和解决问题,我觉得我们还得有个系统的方式,就像这次的@DS注解一样,很明显同一家公司人家有配套的工具,我却一定要去网上乱查,说实在的,也是浪费时间。个人觉得,这也是大牛与菜鸡的一个区别,搜索问题,该怎么培养自己的思路呢?

  • 相关阅读:
    numpy数据库
    MVCC解决的问题是什么
    Kubernetes-----介绍
    要有自己的科研笔记
    docker 安装 redis 6.0.8 cluster 实战 (3主3从) 动态缩容
    人工智能基础 | 回归分析(四)
    06-JS字符串
    使用 L293D 电机驱动器 IC 和 Arduino 控制直流电机
    2023年第四届MathorCup高校数学建模挑战赛——大数据竞赛B题
    【操作系统】进程的状态
  • 原文地址:https://blog.csdn.net/key_wu/article/details/126427585