美文网首页
分库后读写分离造成主键步长读取错误

分库后读写分离造成主键步长读取错误

作者: 多关心老人 | 来源:发表于2019-06-02 18:52 被阅读0次

    问题描述

    系统数据越来越大,按照租户id来对数据分库,没有采用类似snow-flake这种全局id方式,而是用的auto_increment_offset+auto_increment_increment这种,各个库有自己的offset,步长各个库都是一样的,这样增长的id就保证了全局唯一性。线上采用的是前置数据库代理+一主多从,,由于increment和offset只能在数据库实例级别设置,不能在库级别设置,因此开发环境是在一个实例上建多个库+在connection初始化话的时候设置session的offset来模拟。在发到预生产测试的时候发现保存多条数据时第二条数据取到的id是第一条数据的id+1,并不是加步长,但是存储到数据库中的2个id却是按步长递增的。

    问题分析

    数据存储到数据库是按步长递增,说明数据库的步长设置起了作用,通过mybatis返回的id却没有按步长递增而是按1递增,那么问题应该出在mybatis身上。猜测可能是mybatis取到last_insert_id()后根据影响的条数循环做+1。

    问题排查

    既然怀疑是mybatis的问题,那就开始排查,


    image.png

    进去之后是调 stmt.getGeneratedKeys取得数据库生成的id,在pstmt每次执行完数据库后都会把生成的第一条数据id设置到rs的updateId,因此对updateCount进行循环加步长,


    image.png
    image.png
    debug发现beginAt值是对的,那么猜测connection.getAutoIncrementIncrement()应该就是错的,而increment这个值是怎么来的呢? image.png

    关于serverVariables的描述:


    image.png

    serverVariables在每次连接建立的时候就会去读取数据库的信息,也就是show variables的内容(当然只取一部分变量,具体参考jdbc驱动的ConnectionImpl.loadServerVariables方法)。
    至此我们大概猜到问题的原因:预生产有数据库代理会解析sql,select请求走从库,insert,update,delete走主库。在取serverVariables的时候连的是从库,从库没有设置步长,默认是1,导致connection里面存储的步长是1,mybatis在计算generatedKeys的时候用的就是connection里保存的步长,因此导致计算出来的数据不对。

    问题解决

    数据库连接初始化的时候,就已经把数据库服务器变量保存到connection对象里了,而主从库的步长设置不一样,导致问题产生,把从库的步长设置和主库一样,问题解决,由于从库不会接受业务层面的数据更新请求,只是数据同步,在同步的时候id已经都有值了,因此不再走offset+步长那一套逻辑,从库设置步长不会产生副作用。

    延伸思考

    既然serverVariables是在连接建立的时候就已经读取过了,那么在刚建立成功时候如果执行初始语句改了session变量的话,那么connection里保存的值会自动刷新吗?
    前面我说了,我们在开发环境用的是一个实例多个数据库,而offset和步长是设置在实例上的,为此每个数据库连接在建立的时候都执行了初始语句,阿里druid连接池支持初始化语句:

    biz2.connectionInitSqls=set session auto_increment_offset=2
    

    我们通过这种方式为每个datasource设置了session级别的offset(步长是global级别)。这里设置offset对我们代码没有什么影响。如果我们在connectionInitSqls里设置了session级别的步长,那么后面在取generatedKeys的时候,用的是数据库设置的global级别的步长,还是session级别的步长?

    设置druid的connectionInitSqls,

    set session auto_increment_offset=2;set session auto_increment_increment=200;
    

    同时DruidDatasource开启initVariables,虽然在AbstractDruidDatasource的initPhysicalConnection方法里有去执行show variables,取到的结果保存在HashMap里,但是这个HashMap并没有在别的地方被引用到。测试插入多条数据,从connection里拿到的还是global级别的步长,connectionInitSqls设置的session级别步长没起作用。

    相关文章

      网友评论

          本文标题:分库后读写分离造成主键步长读取错误

          本文链接:https://www.haomeiwen.com/subject/wfnxxctx.html