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
 

数据库分表之Mybatis+Mysql实践(含部分关键代码)的更多相关文章

  1. 重新学习Mysql数据13:Mysql主从复制,读写分离,分表分库策略与实践

    一.MySQL扩展具体的实现方式 随着业务规模的不断扩大,需要选择合适的方案去应对数据规模的增长,以应对逐渐增长的访问压力和数据量. 关于数据库的扩展主要包括:业务拆分.主从复制.读写分离.数据库分库 ...

  2. MySQL数据库分表的3种方法

    原文地址:MySQL数据库分表的3种方法作者:dreamboycx 一,先说一下为什么要分表 当一张的数据达到几百万时,你查询一次所花的时间会变多,如果有联合查询的话,我想有可能会死在那儿了.分表的目 ...

  3. 【大数据和云计算技术社区】分库分表技术演进&最佳实践笔记

    1.需求背景 移动互联网时代,海量的用户每天产生海量的数量,这些海量数据远不是一张表能Hold住的.比如 用户表:支付宝8亿,微信10亿.CITIC对公140万,对私8700万. 订单表:美团每天几千 ...

  4. zabbix 数据库分表操作

    近期zabbix数据库占用的io高,在页面查看图形很慢,而且数据表已经很大,将采用把数据库的数据目录移到新的磁盘,将几个大表进行分表操作 一.数据迁移: 1.数据同步到新的磁盘上,先停止mysql(不 ...

  5. Mycat(4):消息表mysql数据库分表实践

    本文的原文连接是: http://blog.csdn.net/freewebsys/article/details/46882777 未经博主同意不得转载. 1,业务需求 比方一个社交软件,比方像腾讯 ...

  6. 一致性Hash算法在数据库分表中的实践

    最近有一个项目,其中某个功能单表数据在可预估的未来达到了亿级,初步估算在90亿左右.与同事详细讨论后,决定采用一致性Hash算法来完成数据库的自动扩容和数据迁移.整个程序细节由我同事完成,我只是将其理 ...

  7. 什么是分表和分区 MySql数据库分区和分表方法

    1.为什么要分表和分区 日常开发中我们经常会遇到大表的情况,所谓的大表是指存储了百万级乃至千万级条记录的表.这样的表过于庞大,导致数据库在查询和插入的时候耗时太长,性能低下,如果涉及联合查询的情况,性 ...

  8. MySQL数据库分表分区(一)(转)

    面对当今大数据存储,设想当mysql中一个表的总记录超过1000W,会出现性能的大幅度下降吗? 答案是肯定的,一个表的总记录超过1000W,在操作系统层面检索也是效率非常低的   解决方案: 目前针对 ...

  9. mysql 数据库分表小实例

    项目开发中,我们的数据库数据越来越大,随之而来的是单个表中数据太多.以至于查询书读变慢,而且由于表的锁机制导致应用操作也搜到严重影响,出现了数据库性能瓶颈. 当出现这种情况时,我们可以考虑分表,即将单 ...

随机推荐

  1. Javascript一句代码实现JS字符串去除重复字符

    需求: 原字符串:abcdabecd 去重后字符串:abcde JS字符串去重,一个简单需求,网上找案例发现都是一大堆代码,对于强迫症的我 实再无法忍受,于是自己手动写出一段代码,完美解决该问题. 代 ...

  2. springmvc 访问时找不到配置文件

    运行tomcat时一切正常,访问时报了init()错误,后台报的是fileNotFoundException,原因时找不到spring的配置文件,web.xml主要配置如下 <!-- sprin ...

  3. 微信小程序之自定义toast弹窗

    微信小程序里面的自带弹窗icon只有两种,success和loading.有时候用户输入错误的时候想加入一个提醒图标,也可以使用wx.showToast中的image来添加图片达到使用自定义图标的目的 ...

  4. Django-- 多数据库联用

    django项目中使用多个数据库的方法, 多个数据库的联用 以及多数据库时数据导入导出的方法. 直接给出一种简单的方法吧,想了解更多的到官方教程,点击此处 给每个app都可以单独的设置一个数据库 se ...

  5. 关于soapui简介与入门

    SoapUI简介 SoapUI是一个开源测试工具,通过soap/http来检查.调用.实现Web Service的功能/负载/符合性测试.该工具既可作为一个单独的测试软件使用,也可利用插件集成到Ecl ...

  6. 关于JS中变量提升的规则和原理的一点理解

        关于变量提升,以前在一些教程和书籍上都听到过,平时开发中也知道有这个规律,但是今天突然在一个公开课中听到时,第一反应时一脸懵逼,然后一百度,瞬间觉得好熟悉啊,差点被这个概念给唬住了,不信我给你 ...

  7. $(document).ready()和window.onload之间的差异

    最近使用$(document).ready(function(){})遇到一个问题:加载页面后发送数据请求后台,得到的数据不对,后发现请求后台时,发送的数据为空,没有获取到值导致的.------改成w ...

  8. 【Java学习笔记之十八】Javadoc注释的用法

    Javadoc注释的用法 Java 文档 // 注释一行/* ...... */ 注释若干行/** ...... */ 注释若干行,并写入 javadoc 文档 通常这种注释的多行写法如下: /*** ...

  9. 2017ecjtu-summer training #4 CodeForces 731C

    C. Socks time limit per test 2 seconds memory limit per test 256 megabytes input standard input outp ...

  10. hdu_1017(水水水,坑格式)

    #include<cstdio> #include<cmath> using namespace std; int main() { int T; scanf("%d ...