受群里小伙伴之邀,搞一个分库分表案例,这样让很多没用过分库分表的心里也有个底,不然永远看到的都是网上的各种概念和解决方案性的文章。需求 由于用户表过于庞大,采取相关SQL优化,还是不能满足,所以现对其进行做分库分表。 数据库:mysharding 数据库表:tuser 建表语句如下:DROPTABLEIFEXISTSCREATETABLEtuser(idbigintNOTNULLAUTOINCREMENT,usernamevarchar(32)CHARACTERSETutf8COLLATEutf8generalciNOTNULL,ageintNOTNULL,genderintNOTNULL,PRIMARYKEY(id))ENGINEInnoDBAUTOINCREMENT4DEFAULTCHARSETutf8; 关于数据库分库分表通常有两种方案:垂直拆分水平拆分 下面我们来演示水平拆分,大致思路:通过tuser表的id进行hash,然后再和数据库个数进行取模,得出对应数据库。 通过hash值和每个数据库中表的个数进行取模,得出对应表名。创建数据库和表 加入有2000万条数据,那么为了方便演示,我们就暂定分为五个库,每个数据库对应五个表。理想状态:2000万54,那么每个数据库分得400万,每个表分得80万。 总之,分库分表后,我们的每一张表的数据库和表都与之前的确实不是一个量级了。 五个数据库: 每个数据库有五张表: 建表语句如下:DROPTABLEIFEXISTStuser0;CREATETABLEtuser0(idbigintNOTNULLAUTOINCREMENT,usernamevarchar(32)CHARACTERSETutf8COLLATEutf8generalciNOTNULL,ageintNOTNULL,genderintNOTNULL,PRIMARYKEY(id))ENGINEInnoDBAUTOINCREMENT4DEFAULTCHARSETutf8;DROPTABLEIFEXISTStuser1;CREATETABLEtuser1(idbigintNOTNULLAUTOINCREMENT,usernamevarchar(32)CHARACTERSETutf8COLLATEutf8generalciNOTNULL,ageintNOTNULL,genderintNOTNULL,PRIMARYKEY(id))ENGINEInnoDBAUTOINCREMENT3DEFAULTCHARSETutf8;DROPTABLEIFEXISTStuser2;CREATETABLEtuser2(idbigintNOTNULLAUTOINCREMENT,usernamevarchar(32)CHARACTERSETutf8COLLATEutf8generalciNOTNULL,ageintNOTNULL,genderintNOTNULL,PRIMARYKEY(id))ENGINEInnoDBAUTOINCREMENT3DEFAULTCHARSETutf8;DROPTABLEIFEXISTStuser3;CREATETABLEtuser3(idbigintNOTNULLAUTOINCREMENT,usernamevarchar(32)CHARACTERSETutf8COLLATEutf8generalciNOTNULL,ageintNOTNULL,genderintNOTNULL,PRIMARYKEY(id))ENGINEInnoDBAUTOINCREMENT8DEFAULTCHARSETutf8;DROPTABLEIFEXISTStuser4;CREATETABLEtuser4(idbigintNOTNULLAUTOINCREMENT,usernamevarchar(32)CHARACTERSETutf8COLLATEutf8generalciNOTNULL,ageintNOTNULL,genderintNOTNULL,PRIMARYKEY(id))ENGINEInnoDBAUTOINCREMENT5DEFAULTCHARSETutf8;项目创建 使用技术栈:JDK8MySQLSpringBootMybatisShardingsphereDruid maven相关依赖:dependencygroupIdorg。springframework。bootgroupIdspringbootstarterwebartifactIddependencydependencygroupIdorg。mybatis。spring。bootgroupIdmybatisspringbootstarterartifactIdversion2。1。0versiondependencydependencygroupIdorg。mybatisgroupIdmybatisartifactIdversion3。5。2versiondependencydependencygroupIdmysqlgroupIdmysqlconnectorjavaartifactIdversion8。0。16versionscoperuntimescopedependencydependencygroupIdcom。github。pagehelpergroupIdpagehelperspringbootstarterartifactIdversion1。2。3versiondependencydependencygroupIdorg。springframework。bootgroupIdspringbootstartertestartifactIdscopetestscopedependencydependencygroupIdorg。apache。shardingspheregroupIdshardingjdbcspringbootstarterartifactIdversion4。0。1versiondependencydependencygroupIdcom。alibabagroupIddruidartifactIdversion1。1。17versiondependencydependencygroupIdcom。google。guavagroupIdguavaartifactIdversion29。0jreversiondependency 配置文件相关配置如下:server。port9002mybatis。mapperlocationsclasspath:mapper。xmlmybatis。typealiasespackagecom。neutral。idmapping。dbshard。pojo连接池配置过滤器设置(第一个stat很重要,没有的话会监控不到SQL)spring。datasource。druid。filtersstat,wall,log4j2WebStatFilter配置启用StatFilterspring。datasource。druid。webstatfilter。enabledtrue添加过滤规则spring。datasource。druid。webstatfilter。urlpattern排除一些不必要的urlspring。datasource。druid。webstatfilter。exclusions。js,。gif,。jpg,。png,。css,。ico,druid开启session统计功能spring。datasource。druid。webstatfilter。sessionstatenabletrue缺省sessionStatMaxCount是1000个spring。datasource。druid。webstatfilter。sessionstatmaxcount1000spring。datasource。druid。webstatfilter。principalsessionnamespring。datasource。druid。webstatfilter。principalcookienamespring。datasource。druid。webstatfilter。profileenableStatViewServlet配置启用内置的监控页面spring。datasource。druid。statviewservlet。enabledtrue内置监控页面的地址spring。datasource。druid。statviewservlet。urlpatterndruid关闭ResetAll功能spring。datasource。druid。statviewservlet。resetenablefalse设置登录用户名spring。datasource。druid。statviewservlet。loginusernameadmin设置登录密码spring。datasource。druid。statviewservlet。loginpasswordadminspring。shardingsphere。props。sql。showfalse数据库名spring。shardingsphere。datasource。namesdp0,dp1,dp2,dp3,dp4datasourcespring。shardingsphere。datasource。dp0。typecom。alibaba。druid。pool。DruidDataSourcespring。shardingsphere。datasource。dp0。driverclassnamecom。mysql。jdbc。Driverspring。shardingsphere。datasource。dp0。urljdbc:mysql:localhost:3306mysharding0?useUnicodetruecharacterEncodingutf8serverTimeZoneCTTallowPublicKeyRetrievaltrueserverTimezoneUTCspring。shardingsphere。datasource。dp0。usernamerootspring。shardingsphere。datasource。dp0。password123456相同的代码部分这里就不贴了对应dp1、dp2、dp3、dp4和上面dp0配置类似,不一样的就是数据库名字不一样因为我使用的本地创建多个数据库演示的,这里就没有必要重复累赘了actualdatanodes这里是配置所有的库。表的集合比如我这里配置的意思是dp0。data0,dp0。data1,dp0。data2,。。。此缩写方式使用了shardingsphere官方推荐的语法tuser逻辑表名在UserMapper。xml中使用spring。shardingsphere。sharding。tables。tuser。actualdatanodesdp{0。。4}。tuser{0。。4}table设置了以data中字段id作为分表的标准,这样到时候就会将id作为参数传入到下面配置的我们自定义的分表方法中做具体操spring。shardingsphere。sharding。tables。tuser。tablestrategy。standard。shardingcolumnidspring。shardingsphere。sharding。tables。tuser。tablestrategy。standard。precisealgorithmclassnamecom。tian。shardingdemo。common。TableShardingAlgorithmdatabase设置了以data中字段id作为分库的标准,这样到时候就会将id作为参数传入到下面配置的我们自定义的分库方法中做具体操作spring。shardingsphere。sharding。tables。tuser。databasestrategy。standard。shardingcolumnidspring。shardingsphere。sharding。tables。tuser。databasestrategy。standard。precisealgorithmclassnamecom。tian。shardingdemo。common。DbShardingAlgorithm 分库分表的两个分片类:分库publicclassDbShardingAlgorithmimplementsPreciseShardingAlgorithmLong{privateLoggerloggerLoggerFactory。getLogger(DbShardingAlgorithm。class);OverridepublicStringdoSharding(CollectionStringavailableTargetNames,PreciseShardingValueLongshardingValue){StringdatabaseNameavailableTargetNames。stream()。findFirst()。get();for(StringdbName:availableTargetNames){shardingValue。getValue()就是配置的传入的值我们这里选用的是传入sql中的id字段的值StringtargetDbNamedpgenderToTableSuffix(shardingValue。getValue());if(dbName。equals(targetDbName)){匹配到对应的数据库,比如dp0这个数据库名对应数据源处配置的dp0,dp1,。。。logger。info(数据库名dbName);databaseNamedbN}}returndatabaseN}privateStringgenderToTableSuffix(Longvalue){将id字段的值去hash值后去模运算得到分库的数字(就是一种算法而已)intiHashing。murmur3128(1823977)。newHasher()。putString(String。valueOf(value),Charsets。UTF8)。hash()。asInt();hash与表个数进行取模returnString。valueOf(Math。abs(i)5);}}分表publicclassTableShardingAlgorithmimplementsPreciseShardingAlgorithmLong{privateLoggerloggerLoggerFactory。getLogger(TableShardingAlgorithm。class);OverridepublicStringdoSharding(CollectionStringavailableTargetNames,PreciseShardingValueLongshardingValue){StringtableavailableTargetNames。stream()。findFirst()。get();StringtargetNametusergenderToTableSuffix(shardingValue。getValue());for(StringtableName:availableTargetNames){检查计算出来的表名是否存在if(tableName。equals(targetName)){logger。info(表名tableName);tabletableN}}}privateStringgenderToTableSuffix(Longvalue){算出一个hash值int类型intiHashing。murmur3128(8947189)。newHasher()。putString(String。valueOf(value),Charsets。UTF8)。hash()。asInt();hash与表个数进行取模returnString。valueOf(Math。abs(i)5);}} 下面是业务部分代码,先看UserMapper。xml内容:?xmlversion1。0encodingUTF8?!DOCTYPEmapperPUBLICmybatis。orgDTDMapper3。0ENhttp:mybatis。orgdtdmybatis3mapper。dtdmappernamespacecom。tian。shardingdemo。mapper。UserMapperresultMapidUsertypecom。tian。shardingdemo。entity。UseridcolumnidpropertyidresultcolumnusernamepropertyuserNameresultMapinsertidinsertINSERTINTOtuser(id,username,age,gender)VALUES({id},{userName},{age},{gender});insertselectidselectUserByIdresultMapUserselectfromtuserwhereiftestid!nullid{id}ifwhereselectupdateidupdateAuthorIfNecessaryupdatetusertrimprefixSETsuffixOverrides,iftestuserName!nullanduserName!username{userName},ififtestgender!nullandgender!0gender{gender},ififtestage!nullandage!0age{age},iftrimwhereid{id}updatemapper UserMapper接口:importcom。tian。shardingdemo。entity。Uimportorg。apache。ibatis。annotations。Mimportorg。apache。ibatis。annotations。Pimportorg。springframework。stereotype。RMapperRepositorypublicinterfaceUserMapper{UserselectUserById(Param(id)Longid);intupdateAuthorIfNecessary(Useruser);intinsert(Useruser);} 为了更好地演示,我这里加入了controller层和service层,这也是大家平常开发套路。 service层代码如下:publicinterfaceIUserService{UserselectUserById(Longid);voidadd(Longid);}ServicepublicclassUserServiceImplimplementsIUserService{ResourceprivateUserMapperuserMOverridepublicUserselectUserById(Longid){returnuserMapper。selectUserById(id);}Overridepublicvoidadd(Longid){UserusernewUser();user。setAge(22);user。setGender(1);user。setId(id);user。setUserName(tianid);userMapper。insert(user);}} controller层代码如下:RestControllerRequestMappingpublicclassUserController{ResourceprivateIUserServiceuserSRequestMapping(valueuser{id},methodRequestMethod。GET)publicUserselectUserById(PathVariable(id)Longid){returnuserService。selectUserById(id);}PostMapping(add)publicObjectadd(RequestBodyMapString,Longparams){Longidparams。get(id);userService。add(id);}} 最后是项目的启动类:SpringBootApplicationMapperScan({com。tian。shardingdemo。mapper})publicclassShardingDemoApplication{publicstaticvoidmain(String〔〕args){SpringApplication。run(ShardingDemoApplication。class,args);}} 启动项目,启动成功: 下面我们来演示一下新增数据和查询。添加数据到数据库中 先来添加数据到数据库中,这里使用的是IDEA中restful工具: 后台日志: 再查看数据库表中: 到此,我们的数据依旧落库,下面我们来演示一下数据查询。数据查询 浏览器里输入:http:localhost:9002user7 返回数据:{id:7,userName:tian7,age:22,gender:1} 后台日志: 从日志和返回结果可以看出,已经为我们正确的选择到对应的数据库和表了,这样,一个分库分表的查询就成功了。总结 本文没有太多的概念,直接使用案例演示。相关概念性的文章,还有分库分表解决方案的文章,网上一堆堆的,感兴趣可以自行查阅。