zoukankan      html  css  js  c++  java
  • 数据库分表之Mybatis+Mysql实践

            随着我们系统用户数量的日增,业务数据处于一个爆发前,增长的数据量已经给我们的系统造成了很大的不确定。在用户量较多,并发较大的情况下,读写频繁的验证码表,数据量达到几十万上百万的时候出现了锁表阻塞,导致用户登录验证失败,进而导致系统的一度反应较慢,甚至登录不上等问题。
           由于这种读写更新频繁的表,造成性能下降,仅仅通过添加redis缓存已经解决不了问题。所以我们引入了数据库表切分的解决方案。
           考虑到业务表数据量的增长情况,我们决定采用按周或者按月的方式进行表的切分,具体路由规则如下:
          1、 切分策略,见仁见智。
     1 import java.util.Calendar;
     2 import org.apache.commons.lang.StringUtils;
     3 public class TableRouter {
     4             
     5             /**
     6              * table路由规则,获取新表名称
     7              * @param prefix  表明前缀
     8              * @param strategy 切分策略,
     9              * @return
    10              */
    11             public static String getUcCaptchaTable(String prefix,String strategy ){
    12                         //根据切分策略进行切分,添加一定的容错,该部分主要是针对读写频繁的验证码表,故部分代码写死为主表的数据;
    13                         //切分策略为周时,返回“表名_年份周次”,也就是说一年会有52张表
    14                         //切分策略为月时,返回“表名_年份月份”,也就是说一年会有12张表
    15                         //该种切分策略的弊端,是在周末凌晨或者月末凌晨的几分钟,存在验证不存在的情况,在我们的系统允许范围内,故此处未做特殊处理。
    16                         if(StringUtils.isNotBlank(prefix)&&StringUtils.isNotBlank(strategy)&&prefix.equals("uc_captcha")&&"week".equals(strategy)){
    17                                     Calendar c=Calendar.getInstance();
    18                     int i = c.get(Calendar.WEEK_OF_YEAR);
    19                     StringBuffer sb = new StringBuffer();
    20                     int year = c.get(Calendar.YEAR);
    21                     String suffix = sb.append(year).append(i).toString();
    22                     System.out.println(suffix);
    23                     return prefix+"_"+suffix;
    24                         }elseif(StringUtils.isNotBlank(prefix)&&StringUtils.isNotBlank(strategy)&&prefix.equals("uc_captcha")&&"month".equals(strategy)){
    25                                     Calendar c=Calendar.getInstance();
    26                     int i = c.get(Calendar.MONTH);
    27                     StringBuffer sb = new StringBuffer();
    28                     int year = c.get(Calendar.YEAR);
    29                     String suffix = sb.append(year).append(i).toString();
    30                     System.out.println(suffix);
    31                     return prefix+"_"+suffix;
    32                         }
    33                         //获取不到分表名称,则返回主表名称
    34                         return "uc_captcha";
    35             }
    36             
    37 }
    2、切分策略写好后,关键的是我们需要将我们的sql中对应的表名更改为动态传入,此处用到的是mybatis的多参数映射属性。
    部分java代码与xml代码如下:
    java代码如下(支持集中基本的数据类型,注意map的写法,list的话,采用list 小写):

     
    1              /**插入一条数据 **/
    2    public int add(@Param("table") String table  ,@Param("map") Map<String,Object> map);
    3              /**更新一条数据**/
    4    public int update(@Param("table") String table  ,@Param("map") Map<String,Object> map);
    xml文件:注意表名的写法 ${table}使用${}不携带jabcType,也不能使用#;map取参数,使用map.prapmeter 取参数
     1       <!-- 插入一条新记录 -->
     2       <insert id="add" parameterType="map">
     3        insert into ${table}(pid,btype,uid,naccount,capthcha,ntype,ctime,expiration)
     4        values(
     5        #{map.pid, jdbcType=VARCHAR},
     6        #{map.type, jdbcType=VARCHAR},
     7        #{map.uid, jdbcType=VARCHAR},
     8        #{map.phone, jdbcType=VARCHAR},
     9        #{map.code, jdbcType=VARCHAR},
    10        #{map.is_active, jdbcType=VARCHAR},
    11        #{map.ctime, jdbcType=VARCHAR},
    12        #{map.invalid_time, jdbcType=VARCHAR}
    13        )
    14        <selectKey resultType="int" keyProperty="pid" >
    15              SELECT @@IDENTITY AS pid
    16        </selectKey>
    17       </insert>
     domain层调用如下:
    1 int validation_id = validationDao.add(getCurrentTableName(),map);
     
    其中getCurrentTableName()为内部方法,其中是根据配置的策略以及路由规则获取分表表名,代码如下:
     
    /**
                 * 获取当前分表名称
                 */
                public String getCurrentTableName() {
                            String tableName =  TableRouter.getUcCaptchaTable("uc_captcha", strategy);
                            if(!this._this.existTable(tableName)){//不存在新表,则创建新表,并返回新表表名
                                        try {
                                                    int tableCreateRes = validationDao.dynamicCreateTable(tableName);         
                                                    if(tableCreateRes >=0){
                                                                //创建新表,清空表不存在的缓存,
                                                                this._this.notExistTable(tableName);
                                                    }
                                        } catch (Exception e) {
                                                    return "uc_captcha";
                                        }
                            }
                            return tableName;
                }
     
                /**
                 * 缓存表是否存在,减轻
                 */
                @Cacheable(value="uc2cache", key="'uc_captcha_exist_'+#tableName")
                public boolean existTable(String tableName){
                            int tableCount = validationDao.existTable(tableName);
                            if(tableCount == 0){//不存在新表,则创建新表,并返回新表表名
                                        return false;
                            }
                            return true;//存在
                }
               @CacheEvict(value="uc2cache", key="'uc_captcha_exist_'+#tableName")
                public void notExistTable(String tableName){}
     
     
    考虑到每次都会调用数据库查询表是否存在,我们为减少对数据库的IO,我们采用了redis缓存的方式,其中AOP切面,自调用不起作用的情况,不在此处赘述。
    你可以看到,不存在路由的分表的时候,我们会进行创建表,创建语句如下:
     
    <!-- 查询表是否存在 -->
    <select id="existTable" parameterType="String" resultType="Integer"> 
            select count(1)
            from information_schema.tables 
            where LCASE(table_name)=#{table,jdbcType=VARCHAR} 
        </select>
    <!-- 创建表 -->
    <update id="dynamicCreateTable" parameterType="String">
          CREATE TABLE  if not EXISTs ${table} (
      `pid` varchar(36) NOT NULL,
      `uid` int(11) DEFAULT NULL,
      `btype` varchar(30) NOT NULL COMMENT '业务类型例如:sign 用户注册。login 用户登陆',
      `ntype` varchar(30) NOT NULL COMMENT '短信、邮箱、微信等。根据系统支持取值',
      `naccount` varchar(30) NOT NULL COMMENT '手机号、邮箱、微信等',
      `capthcha` varchar(6) NOT NULL COMMENT '6位随机验证码',
      `expiration` int(11) NOT NULL COMMENT '有效期,距离1970年秒数',
      `ctime` int(11) NOT NULL COMMENT '创建时间距离1970年秒数',
      PRIMARY KEY (`pid`),
      KEY `fk_uccaptcha_uid` (`uid`),
      KEY `uk_uc_captcha_ub` (`btype`) USING BTREE,
      CONSTRAINT ${table}_ibfk_1 FOREIGN KEY (`uid`) REFERENCES `uc_users_ext` (`uid`) ON DELETE CASCADE
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8
    </update>
    <!--成功返回0  失败会跑错,我们已经做了容错处理-->
     
    至此,数据库的分表功能基本完成。
  • 相关阅读:
    win10前面板耳机没声音
    学计算机的值得一看的文章,跟帖也很有水平啊
    ubuntu下编译caffe
    pip卡住不动的解决方案
    数字图像处理处理中的数学怎么提高?
    安装vmall5:从ebak恢复数据,需要配置php.ini
    python入门(7)Python程序的风格
    python入门(6)输入和输出
    python入门(5)使用文件编辑器编写代码并保存执行
    python入门(4)第一个python程序
  • 原文地址:https://www.cnblogs.com/baorantHome/p/8404409.html
Copyright © 2011-2022 走看看