數據庫分表之Mybatis+Mysql實踐(含部分關鍵代碼)


2018年01月31日
     隨着我們系統用戶數量的日增,業務數據處於一個爆發前,增長的數據量已經給我們的系統造成了很大的不確定。在上個周末用戶量較多,並發較大的情況下,讀寫頻繁的驗證碼表,數據量達到幾十萬上百萬的時候出現了鎖表阻塞,導致用戶登錄驗證失敗,進而導致系統的一度反應較慢,甚至登錄不上等問題。查了很多資料,發現大家都是偏理論,索性自己實現了,發出來以作記錄,也能給別人一些幫助。諸位有什么高明意見,歡迎交流。個人QQ:1612301243,非誠勿擾。
     由於這種讀寫更新頻繁的表,造成性能下降,僅僅通過添加redis緩存已經解決不了問題。所以我們引入了數據庫表切分的解決方案。
     考慮到業務表數據量的增長情況,我們決定采用按周或者按月的方式進行表的切分,具體路由規則如下:
           切分策略,見仁見智。
package com.**.uc.utils;
import java.util.Calendar;
import org.apache.commons.lang.StringUtils;
public class TableRouter {
            
             /**
             * table路由規則,獲取新表名稱
             * @param prefix  表明前綴
             * @param strategy 切分策略,
             * @return
             */
             public static String getUcCaptchaTable(String prefix,String strategy ){
                         //根據切分策略進行切分,添加一定的容錯,該部分主要是針對讀寫頻繁的驗證碼表,故部分代碼寫死為主表的數據;
                         //切分策略為周時,返回“表名_年份周次”,也就是說一年會有52張表
                         //切分策略為月時,返回“表名_年份月份”,也就是說一年會有12張表
                         //該種切分策略的弊端,是在周末凌晨或者月末凌晨的幾分鍾,存在驗證不存在的情況,在我們的系統允許范圍內,故此處未做特殊處理。
                         if (StringUtils.isNotBlank(prefix)&&StringUtils.isNotBlank(strategy)&&prefix.equals( "uc_captcha" )&& "week" .equals(strategy)){
                                    Calendar c=Calendar.getInstance();
                    int i = c.get(Calendar. WEEK_OF_YEAR );
                    StringBuffer sb = new StringBuffer();
                    int year = c.get(Calendar. YEAR );
                    String suffix = sb.append(year).append(i).toString();
                    System. out .println(suffix);
                    return prefix+ "_" +suffix;
                        } else if (StringUtils.isNotBlank(prefix)&&StringUtils.isNotBlank(strategy)&&prefix.equals( "uc_captcha" )&& "month" .equals(strategy)){
                                    Calendar c=Calendar.getInstance();
                    int i = c.get(Calendar. MONTH );
                    StringBuffer sb = new StringBuffer();
                    int year = c.get(Calendar. YEAR );
                    String suffix = sb.append(year).append(i).toString();
                    System. out .println(suffix);
                    return prefix+ "_" +suffix;
                        }
                         //獲取不到分表名稱,則返回主表名稱
                         return "uc_captcha" ;
            }
            
}
切分策略寫好后,關鍵的是我們需要將我們的sql中對應的表名更改為動態傳入,此處用到的是mybatis的多參數映射屬性。
部分java代碼與xml代碼如下:
java代碼如下(支持集中基本的數據類型,注意map的寫法,list的話,采用list 小寫):
             /** 插入一條數據 **/
   public int add( @Param ( "table" ) String table  , @Param ( "map" ) Map<String,Object> map);
             /** 更新一條數據 **/
   public int update( @Param ( "table" ) String table  , @Param ( "map" ) Map<String,Object> map);
 
xml文件:注意表名的寫法 ${table}使用${}不攜帶jabcType,也不能使用#;map取參數,使用map.prapmeter 取參數
       <!-- 插入一條新記錄 -->
       < insert id = "add" parameterType = "map" >
       insert into ${table}(pid,btype,uid,naccount,capthcha,ntype,ctime,expiration)
       values(
       #{map.pid, jdbcType=VARCHAR},
       #{map.type, jdbcType=VARCHAR},
       #{map.uid, jdbcType=VARCHAR},
       #{map.phone, jdbcType=VARCHAR},
       #{map.code, jdbcType=VARCHAR},
       #{map.is_active, jdbcType=VARCHAR},
       #{map.ctime, jdbcType=VARCHAR},
       #{map.invalid_time, jdbcType=VARCHAR}
       )
       < selectKey resultType = "int" keyProperty = "pid" >
             SELECT @@IDENTITY AS pid
       </ selectKey >
       </ insert >
domain層調用如下:
 
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  失敗會跑錯,我們已經做了容錯處理-->
至此,數據庫的切分表功能基本完成。
 
發一個beanselfaware的鏈接 : http://fyting.iteye.com/blog/109236
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM