mysql、oracle分库分表方案之sharding-jdbc使用(非demo示例)

选择开源核心组件的一个很是重要的考虑一般是社区活跃性,一旦项目团队没法进行本身后续维护和扩展的状况下更是如此。php

至于为何选择sharding-jdbc而不是Mycat,能够参考知乎讨论帖子https://www.zhihu.com/question/64709787。html

还能够参考https://blog.csdn.net/u013898617/article/details/79615427。java

关于分库分表和读写分离、主从

通常来讲,须要分库分表的系统是流量比较大的,并且比较容易出现峰值的好比说打折/活动的时候;其次,当单机扛不住业务流量的时候,分库分表必定不是第一选择,在分库分表以前,应该先保证垂直拆分完成了,子系统内都是高内聚的,其次基于Master-Slave的读写分离或者模糊查询不少的,可能NoSQL好比elastic就引流去很大一部分了。当读写分离也作完了,主库只剩下关键业务逻辑以后,流量仍是很高,这个时候才开始考虑分库分表。由于相对于读写分离、垂直拆分,分库分表对开发和运维的要求多得多,若是肯定业务一两年内不会剧增的,盲目引入只会致使成本高昂(尤为是各类SQL限制)。node

其次,分库分表会增长N倍的数据库服务器,通常来讲是4的倍数,若是某个应用说要作分库分表,又只有两台机器,那彻底就是凑热闹。mysql

读写分离和分库分表应该来讲是先后的两件事比较合理,很多人将这两个事情混到一块儿去讲准确的说不合理的。分库分表一般更多的是用于纯粹的OLTP的事情,让系统能够水平扩展。而读写分离更多的是为了把一部分能够容忍短时延迟/不保证100%(但应该在99%以上)准确的查询路由到查询库,准确的说是对业务根据优先级作个归类,这些查询从资源消耗的角度来讲相对逻辑上的PK查询要高几倍到数百倍,好比说查询某我的过去3个月的交易状况,但他从业务角度并不算是DSS概念,好比说查询已经T-N的订单/针对这些订单进行导出,并针对这个单子可能会进行一些操做,甚至人工修改状态。经过把这些功能从核心的订单/交易/资金OLTP中拆分出去,能够保证核心业务系统不会由于一个异常操做好比SQL不合理致使系统出现业务负载增长外的额外抖动因素。linux

从系统设计角度来讲,读写分离虽然从逻辑表结构角度来讲是相同的,都具备相同的字段定义,可是物理实现上是必定是不相同(要是彻底相同,说明没有领会读写分离的初衷)的,尤为是在索引上,写库可能除了PK、惟一索引(任何表都应该有惟一索引,分布式锁只能做为缓冲器)外,最多还有一两个简单的字段索引以最大化性能,任何SQL符合条件的数据通常不会超过几十条(极端客户除外),可是读库根据业务不一样,可能会有不少的索引以知足各类查询以及简单的统计汇总报表的需求。算法

那写库是否是必定就比读库重要呢?是,又不是。是是绝对的,不是是相对的。由于读库不是DSS库,是交易库中相对来讲不是特别重要的业务功能。因此,写库一旦挂了,就会致使业务下不去,读库挂了,可能会致使作业务的人决策错误。好比没有没有查到作过某交易,又从新交易一次。spring

考虑分库分表一个很重要的决策就是是否容许跨多库操做以及有没有必要。TODO。。。。待补充。。。。。。sql

其次,分库和分表是两件事,是到底选择分库仍是分表,若是量没有那么大的话并且是虚拟机的话,估计分库就够了。数据库

sharding-jdbc的版本及其架构差别

目前最新版本的sharding-jdbc是v3.0.0.M1,应该来讲还不稳定,包名又改为了io.shardingsphere,jar包名是sharding-jdbc。

1.5.4.1是目前最新版,也多是终版,1.x的,坐标是com.dangdang,jar包名是sharding-jdbc。

2.0.3是目前最新版,包名和坐标统一改为了io.shardingjdbc,jar包名是sharding-jdbc-core。

说明规划不是特别好,仍是有些乱。

由于2.0以后基本上纯粹分库分表的核心特性的加强就很少了,主要往治理和代理方向去了,因此若是没有特别的需求好比须要相似mycat的独立服务代理模式,使用1.x(注:1.x版本官网文档好像下线了)就能够了,不过若是是大规模的部署,同时已经使用了微服务架构中的注册中心或者基于spring boot,能够考虑使用2.0,由于2.0增长了基于注册中心的配置管理以及spring boot starter。因此2.0的架构是这样的:

3.0以后,增长了相似mycat角色的sharding-proxy无状态服务器(代理层能够有,可是不该该做为应用直接访问的入口,以下图所示),以及相似于Service Mesh的Database Mesh。不过核心没有变化,对SQL语法增长了部分新的支持。因此3.0的架构以下:

 

 就分库分表核心来讲,咱们就只要关心sharding-jdbc就能够了,不须要关心sharding-sphere的其余部分。

sharding-jdbc/Sharding-Proxy/Sharding-Sidecar三者的对好比下:

 

核心特性

事务

对任何重要的系统来讲,事务一致性都是关键的。对分布式系统来讲更是如此,最重要的就是事务一致性,从这个层面来讲,分库分表自己并无引入另一个层次的复杂性。由于它在jdbc驱动层包了一层,因此咱们有必要理解它对事务的支持性以及相关的限制。事务

sharding jdbc 2.x不支持强一致性的分布式事务,通常如今的系统设计也不追求强一致性,而是最终一致性。因此sharding jdbc 2.x支持2中事务:弱XA(同mycat)和最大努力投递事务(官方简称BED)(算是BASE的一种),具体选择哪种须要根据业务和开发进行权衡,若是架构规范和设计作得好,是能够作到不跨库分布式事务的

弱XA事务是默认的模式(即只要dml期间没有抛出异常,commit期间有机器断网或者宕机,是没法保证一致性的),没有特别的要求。

BED则在必定上增长了短时容忍,将执行的语句另做中心化存储,而后轮询commit期间失败的事务重试,因此BED的架构以下:

 

可是没有免费的午饭,BED对开发和维护有着必定的额外要求,并且这些要求都涉及面很广,绝对算得上伤筋动骨。开发层面包括:

  1. INSERT语句的主键不能自增
  2. UPDATE必须可重复执行,好比不支持UPDATE xxx SET x=x+1,对于更新余额类,这就至关于要求必须乐观锁了

运维层面包括:

  1. 须要存储事务日志的数据库
  2. 用于异步做业使用的zookeeper
  3. 解压sharding-jdbc-transaction-async-job-$VERSION.tar,经过start.sh脚本启动异步做业

咱们选择了从设计层面避免强一致性的分布式事务。

分片灵活性

对于分库分表来讲,很重要的一个特性是分片的灵活性,好比单个字段、多个字段的=、IN、>=、<=。为何多个字段很重要的,这里涉及到一个特殊的考虑

sharding-jdbc目前提供4种分片算法。

因为分片算法和业务实现紧密相关,所以并未提供内置分片算法,而是经过分片策略将各类场景提炼出来,提供更高层级的抽象,并提供接口让应用开发者自行实现分片算法。

  • 精确分片算法

对应PreciseShardingAlgorithm,用于处理使用单一键做为分片键的=与IN进行分片的场景。须要配合StandardShardingStrategy使用。

  • 范围分片算法

对应RangeShardingAlgorithm,用于处理使用单一键做为分片键的BETWEEN AND进行分片的场景。须要配合StandardShardingStrategy使用。

  • 复合分片算法

对应ComplexKeysShardingAlgorithm,用于处理使用多键做为分片键进行分片的场景,多分片键逻辑较复杂,须要应用开发者自行处理其中的复杂度。须要配合ComplexShardingStrategy使用。

  • Hint分片算法(Hint分片指的是对于分片字段非SQL决定,而由其余外置条件决定的场景,可以使用SQL Hint灵活的注入分片字段。例:内部系统,按照员工登陆ID分库,而数据库中并没有此字段。SQL Hint支持经过Java API和SQL注释(待实现)两种方式使用。)

对应HintShardingAlgorithm,用于处理使用Hint行分片的场景。须要配合HintShardingStrategy使用。

由于算法的灵活性,标准的方式是经过实现具体的java接口是实现具体的分片算法好比SingleKeyDatabaseShardingAlgorithm,有很多的状况下,分片是比较简单的,好比说纯粹是客户编号,此时提供了行内表达式分片策略,使用Groovy的表达式,提供对SQL语句中的=和IN的分片操做支持,不过这只支持单分片键。好比,t_user_${u_id % 8} 表示t_user表按照u_id按8取模分红8个表,表名称为t_user_0t_user_7

分片键+分片算法=真正可用的分片策略。

算法和分片键的选择是分库分表的关键,其直接决定了各个分库的负载是否均衡,以及扩展是否容易。在设计上的考虑一节笔者会详细阐述,订单和委托业务、用户在使用分库分表时设计上的考虑以及缘由。 

SQL语法限制

对于分库分表来讲,还须要知道有哪些SQL的限制,尤为是涉及到须要二次处理的,好比排序,去重,聚合等。

这里笔者就列下那些经常使用但没有被支持的。好比:

  • case when
  • distinct
  • union

不过好在这些在java/js中处理都比较方便。

若是有很复杂的SQL,那最大的可能就是设计上有问题,应该采用读写分离解决。

sharding-jdbc对SQL的限制完整能够参考http://shardingsphere.io/document/current/cn/features/sharding/usage-standard/sql/

设计上的考虑

哪些表要分库分表

首先从设计上要区分清楚哪些是广播表/哪些是分库表/哪些是只在一个库的全局表,由于是公用数据源的,因此无论是否是分库的表,都须要配置,不配置分片规则Sharding-JDB即没法精确的判定应该路由至哪一个数据源。可是通常分库分表组件包括Sharding-JDBC都会提供简化配置的方法。对于不分片的表:

方法1:sharding-jdbc能够在<sharding:sharding-rule />配置default-data-source-name,这样未配置分片规则的表将经过默认数据源定位。

方法2:将不参与分库分表的数据源独立于Sharding-JDBC以外,在应用中使用多个数据源分别处理分片和不分片的状况。

分库仍是分表

通常来讲应该选择分库(准确的说是分schema),不该该使用分表, 由于oracle能够包含n个schema,mysql能够包含多个database,并且就算真的须要,schema之间也是能够关联查询的,因此感受就算是为了扩展性问题,也没有必要使用分表,分表反而在扩展的时候更加麻烦。就算数据量多了一点,感受稍微偏慢,能够先采用分区挡一挡。

分片键的选择

 其中最重要的是分片键不能是自增字段,不然insert就不知道去哪里了。因此对于ID生成,须要一个ID生成中心。

分布式主键

分布式系统的主键生成能够经过设置增量或者也经过ID生成中心来生成,不过话说回来,既然使用ID生成中心了,就不要再使用数据库机制的ID了,这不必定须要经过代码所有重写,能够在dao层经过aop判断是否insert,是Insert的动态从ID中心获取,这样就避免了分库分表和非分库分表在开发商的差异。 

Sharding-JDBC使用说明

对于只有一个分片键的使用=和IN进行分片的SQL,建议使用行表达式代替Java类的配置。假设咱们不使用弱性事务(若是使用柔性事务,则还须要引入sharding-jdbc-transaction以及sharding-jdbc-transaction-async-job),这样就只要引入sharding-jdbc-core这个jar包就能够了,(由于sharding-jdbc的配置支持java、yaml、spring boot以及spring命名空间(相似dubbo),因此建议使用spring 命名空间方式)以下:

<dependency>
            <groupId>io.shardingjdbc</groupId>
            <artifactId>sharding-jdbc-core</artifactId>
            <version>2.0.3</version>
        </dependency>
        <!-- for sharding-jdbc spring namespace -->
        <dependency>
            <groupId>io.shardingjdbc</groupId>
            <artifactId>sharding-jdbc-core-spring-namespace</artifactId>
            <version>2.0.3</version>
        </dependency>

由于sharding jdbc能够支持dbcp、druid,因此就不用改动其余的依赖项了。

接下去配置数据源、数据源分片策略和表分片策略。

咱们假设分库结构(此处2个库仅作演示目的,实际应该至少4个分库)以下:

order和order_item经过order_id关联,且分片规则相同。

分库1和分库2经过user_id做为分片字段,采用取模(采用取模而不是区间的好处是负载从理论上最均衡,用区间实际上是不均衡的,好比假设客户比较稳定,就1000万,0-100万第一个分片,以此类推,实际上仍是可能很不均衡,由于某段时间增长的客户可能会特别活跃,尤为是在互联网应用中)

配置三个数据源:

一、globalDataSource,指向全局库,非分库分表和广播的表,好比系统参数;

二、dataSource_0、dataSource_1,指向分库1和分库2;

<bean name="dataSource_0" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="url" value="${jdbc.url_0}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
        <!-- 配置初始化大小、最小、最大 -->
        <property name="initialSize" value="${jdbc.initialSize}"/>
        <property name="minIdle" value="${jdbc.minIdle}"/>
        <property name="maxActive" value="${jdbc.maxActive}"/>
        <!-- 配置获取链接等待超时的时间 -->
        <property name="maxWait" value="${jdbc.maxWait}"/>
        <!-- 打开PSCache,而且指定每一个链接上PSCache的大小 -->
        <property name="poolPreparedStatements" value="${jdbc.pps}"/>
        <property name="maxPoolPreparedStatementPerConnectionSize" value="${jdbc.mpps}"/>
        <!-- 配置间隔多久才进行一次检测,检测须要关闭的空闲链接,单位是毫秒 -->
        <property name="timeBetweenEvictionRunsMillis" value="${jdbc.timeBetweenEvictionRunsMillis}"/>
        <!-- 配置一个链接在池中最小生存的时间,单位是毫秒 -->
        <property name="minEvictableIdleTimeMillis" value="${jdbc.minEvictableIdleTimeMillis}"/>
        <property name="removeAbandoned" value="${jdbc.removeAbandoned}"/>
        <property name="removeAbandonedTimeout" value="${jdbc.removeAbandonedTimeout}"/>
        <property name="logAbandoned" value="${jdbc.logAbandoned}"/>
        <!-- 配置监控统计拦截的filters -->
        <property name="filters" value="${jdbc.filters}"/>
    </bean>
    
    <bean name="dataSource_1" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="url" value="${jdbc.url_1}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
        <!-- 配置初始化大小、最小、最大 -->
        <property name="initialSize" value="${jdbc.initialSize}"/>
        <property name="minIdle" value="${jdbc.minIdle}"/>
        <property name="maxActive" value="${jdbc.maxActive}"/>
        <!-- 配置获取链接等待超时的时间 -->
        <property name="maxWait" value="${jdbc.maxWait}"/>
        <!-- 打开PSCache,而且指定每一个链接上PSCache的大小 -->
        <property name="poolPreparedStatements" value="${jdbc.pps}"/>
        <property name="maxPoolPreparedStatementPerConnectionSize" value="${jdbc.mpps}"/>
        <!-- 配置间隔多久才进行一次检测,检测须要关闭的空闲链接,单位是毫秒 -->
        <property name="timeBetweenEvictionRunsMillis" value="${jdbc.timeBetweenEvictionRunsMillis}"/>
        <!-- 配置一个链接在池中最小生存的时间,单位是毫秒 -->
        <property name="minEvictableIdleTimeMillis" value="${jdbc.minEvictableIdleTimeMillis}"/>
        <property name="removeAbandoned" value="${jdbc.removeAbandoned}"/>
        <property name="removeAbandonedTimeout" value="${jdbc.removeAbandonedTimeout}"/>
        <property name="logAbandoned" value="${jdbc.logAbandoned}"/>
        <!-- 配置监控统计拦截的filters -->
        <property name="filters" value="${jdbc.filters}"/>
    </bean>
    
    <bean name="globalDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
        <!-- 配置初始化大小、最小、最大 -->
        <property name="initialSize" value="${jdbc.initialSize}"/>
        <property name="minIdle" value="${jdbc.minIdle}"/>
        <property name="maxActive" value="${jdbc.maxActive}"/>
        <!-- 配置获取链接等待超时的时间 -->
        <property name="maxWait" value="${jdbc.maxWait}"/>
        <!-- 打开PSCache,而且指定每一个链接上PSCache的大小 -->
        <property name="poolPreparedStatements" value="${jdbc.pps}"/>
        <property name="maxPoolPreparedStatementPerConnectionSize" value="${jdbc.mpps}"/>
        <!-- 配置间隔多久才进行一次检测,检测须要关闭的空闲链接,单位是毫秒 -->
        <property name="timeBetweenEvictionRunsMillis" value="${jdbc.timeBetweenEvictionRunsMillis}"/>
        <!-- 配置一个链接在池中最小生存的时间,单位是毫秒 -->
        <property name="minEvictableIdleTimeMillis" value="${jdbc.minEvictableIdleTimeMillis}"/>
        <property name="removeAbandoned" value="${jdbc.removeAbandoned}"/>
        <property name="removeAbandonedTimeout" value="${jdbc.removeAbandonedTimeout}"/>
        <property name="logAbandoned" value="${jdbc.logAbandoned}"/>
        <!-- 配置监控统计拦截的filters -->
        <property name="filters" value="${jdbc.filters}"/>
    </bean>

 

全局表、分库不分表、分库分表、广播表、主、子表绑定的各自规则配置(下面使用行内策略,注:https://blog.csdn.net/shijiemozujiejie/article/details/80786231提到行表达式的策略不利于数据库和表的横向扩展,这是有道理的,由于java代码咱们能够随时从新加载分库和分表的基数,作到不停机扩展,可是行内表达式不行)以下:

<!-- 必须加上ignore-unresolvable,不然极可能会出现java.lang.IllegalArgumentException: Could not resolve placeholder以及cannot invoke method mod() on null object-->
<context:property-placeholder location="classpath:jrescloud.properties" ignore-unresolvable="true" order="1"/>
<!-- 分库策略,尽可能使用sharding:standard-strategy而不是inline-stragegy --> <sharding:inline-strategy id="databaseStrategy" sharding-column="user_id" algorithm-expression="dataSource_${user_id % 2}" /> <!-- 分表策略 --> <sharding:inline-strategy id="orderTableStrategy" sharding-column="order_id" algorithm-expression="t_order_${order_id % 2}" /> <sharding:inline-strategy id="orderItemTableStrategy" sharding-column="order_id" algorithm-expression="t_order_item_${order_id % 2}" /> <sharding:data-source id="shardingDataSource"> <!-- configDataSource为不参数分库分表的全局表的默认数据源,好比系统参数 --> <sharding:sharding-rule data-source-names="dataSource_0,dataSource_1,globalDataSource" default-data-source-name="globalDataSource"> <sharding:table-rules> <!-- 分库+分表 --> <sharding:table-rule logic-table="t_order" actual-data-nodes="dataSource_${0..1}.t_order_${0..1}" database-strategy-ref="databaseStrategy" table-strategy-ref="orderTableStrategy" /> <sharding:table-rule logic-table="t_order_item" actual-data-nodes="dataSource_${0..1}.t_order_item_${0..1}" database-strategy-ref="databaseStrategy" table-strategy-ref="orderItemTableStrategy" />
  
          <!-- 分库不分表 --> <sharding:table-rule logic-table="t_order" database-strategy-ref="databaseStrategy"/> <!-- 广播表 --> <sharding:table-rule logic-table="t_dict"/> </sharding:table-rules> <!-- 绑定表规则列表,表示分库分表的规则相同,这样万一涉及到多个分片的查询,sharding-jdbc就能够肯定分库之间不须要没必要要的二次关联,全部的查询都应该如此 --> <sharding:binding-table-rules> <sharding:binding-table-rule logic-tables="t_order,t_order_item"/> </sharding:binding-table-rules> </sharding:sharding-rule> </sharding:data-source> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="shardingDataSource" /> </bean> <!-- 使用annotation 自动注册bean, 并保证@Required、@Autowired的属性被注入 --> <tx:annotation-driven transaction-manager="transactionManager"/> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="configLocation" value="classpath:mybatis.xml"/> <property name="dataSource" ref="shardingDataSource"/> <property name="mapperLocations"> <list> <value>classpath*:sqlmap/com/yidoo/k3c/**/*.xml</value> </list> </property> </bean>

 

HINT分片策略的使用

Hint分片指的是对于分片字段非SQL决定,而由其余外置条件决定的场景,可以使用SQL Hint灵活的注入分片字段。举个实例的例子,咱们都知道mysql insert values相对于循环插入来讲,性能差距基本上能够说是网络延时的倍数,好比说插入1000条记录,网络来回1ms,1000条就得1m,若是是跨网段的,延时就更长了。而sharding-jdbc和Mycat都不支持多values(sharding-jdbc 3.x支持,但尚未正式发布),有些SQL语句比较复杂好比说有(a = var or b = var) and member_id = N,这个时候druid sqlparser并不支持的时候,虽然能够经过多数据源解决,可是咱们不但愿架构搞得太复杂,这种状况下,能够经过sharding-jdbc的Hint分片策略来实现各类sharding-jdbc不支持的语法的限制。由于Hint分片策略是绕过SQL解析的,因此对于这些比较复杂的须要分片的非DSS查询,采用Hint分片策略性能可能会更好。一样,咱们仍是使用mybatis做为数据库访问层做为例子,对于Hint分片策略,帖子大都是基于1.x版本进行源码分析路由,顺带说起,基本上都是瞎扯,同时在用法上sharding-jdbc-example没有说起。由于咱们实际要用,因此笔者基于2.0.3版本进行了完整的测试确保结果符合咱们的预期。

首先定义一个hint策略,hint策略须要实现io.shardingjdbc.core.api.algorithm.sharding.hint.HintShardingAlgorithm接口。

<sharding:hint-strategy id="hintDatabaseStrategy" algorithm-class="com.yidoo.common.route.hint.HintShardingAlgorithm"/>
package com.yidoo.common.route.hint; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.alibaba.druid.util.StringUtils; import com.yidoo.utils.JacksonHelper; import io.shardingjdbc.core.api.algorithm.sharding.ListShardingValue; import io.shardingjdbc.core.api.algorithm.sharding.ShardingValue; public class HintShardingAlgorithm implements io.shardingjdbc.core.api.algorithm.sharding.hint.HintShardingAlgorithm { private static final Logger logger = LoggerFactory.getLogger(HintShardingAlgorithm.class); @Override public Collection<String> doSharding(Collection<String> availableTargetNames, ShardingValue shardingValue) { logger.debug("shardingValue=" + JacksonHelper.toJSON(shardingValue)); logger.debug("availableTargetNames=" + JacksonHelper.toJSON(availableTargetNames)); List<String> shardingResult = new ArrayList<>(); availableTargetNames.forEach(targetName -> { String suffix = targetName.substring(targetName.lastIndexOf("_")+1); if(StringUtils.isNumber(suffix)) {
// hint分片算法的ShardingValue有两种具体类型: ListShardingValue和RangeShardingValue,取决于io.shardingjdbc.core.api.HintManager.addDatabaseShardingValue(String, String, ShardingOperator, Comparable<?>...)的时候,ShardingOperator的类型。见下文。 ListShardingValue
<Integer> tmpSharding = (ListShardingValue<Integer>) shardingValue; tmpSharding.getValues().forEach(value -> { if (value % 2 == Integer.parseInt(suffix)) { shardingResult.add(targetName); } }); } }); return shardingResult; } }
@SuppressWarnings("unchecked") private ShardingValue getShardingValue(final String logicTable, final String shardingColumn, final ShardingOperator operator, final Comparable<?>[] values) { Preconditions.checkArgument(null != values && values.length > 0); switch (operator) { case EQUAL: case IN: return new ListShardingValue(logicTable, shardingColumn, Arrays.asList(values)); case BETWEEN: return new RangeShardingValue(logicTable, shardingColumn, Range.range(values[0], BoundType.CLOSED, values[1], BoundType.CLOSED)); default: throw new UnsupportedOperationException(operator.getExpression()); } }

sharding:sharding-rule节点上定义属性default-database-strategy-ref为hintDatabaseStrategy,以下:

<sharding:sharding-rule data-source-names="dataSource_0,dataSource_1,globalDataSource" default-database-strategy-ref="hintDatabaseStrategy" default-data-source-name="globalDataSource">

不用定义<sharding:table-rules>。

接下去在执行sql语句前设置分片值便可,以下:

UnitQuery treq = new UnitQuery(); Unit record1 = new Unit(); record1.setUnitName("强制指定Hint-1"); record1.setRemark1(" "); record1.setInputMan("系统管理员"); Unit record2 = new Unit(); record2.setUnitName("强制指定Hint-3"); record2.setRemark1(" "); record2.setInputMan("系统管理员"); List<Unit> records = new ArrayList<>(); records.add(record1); records.add(record2); HintManager hintManager = HintManager.getInstance(); hintManager.setDatabaseShardingValue(1); // hintManager.addDatabaseShardingValue("t_Unit","user_id",1);
 unitMapper.insertValues(records); hintManager.close(); hintManager = HintManager.getInstance(); // 必须先clear,从新拿到instance hintManager.setDatabaseShardingValue(1); List<Unit> unitList = unitMapper.selectByHint(treq); logger.info("强制指定Hint-1提示" + JacksonHelper.toJSON(unitList)); hintManager.close(); Unit record3 = new Unit(); record3.setUnitName("强制指定Hint-2"); record3.setRemark1(" "); record3.setInputMan("系统管理员"); Unit record4 = new Unit(); record4.setUnitName("强制指定Hint-4"); record4.setRemark1(" "); record4.setInputMan("系统管理员"); records.clear(); records.add(record3); records.add(record4); hintManager = HintManager.getInstance(); hintManager.setDatabaseShardingValue(2); // hintManager.addDatabaseShardingValue("t_Unit","user_id",2);
 unitMapper.insertValues(records); hintManager.close(); hintManager = HintManager.getInstance(); unitList = unitMapper.selectByHint(treq); logger.info("强制指定Hint-2提示" + JacksonHelper.toJSON(unitList)); hintManager.close();

mapper中以下:

<insert id="insertValues"> insert into t_Unit( unit_name, remark1, input_man, input_date ) values <foreach collection="list" item="item" index="index" separator=","> ( #{item.unitName}, #{item.remark1}, #{item.inputMan}, now() ) </foreach>
  </insert>

  <select id="selectByHint" resultType="com.yidoo.k3c.global.model.pojo.Unit"> select * from t_Unit </select>

执行前:

执行后:

 

日志:

 

[] 2018-07-05 10:10:21 [61125] [Sharding-JDBC-SQL]-[INFO] localhost-startStop-1 io.shardingjdbc.core.util.SQLLogger.log(SQLLogger.java:59) Logic SQL: insert into t_Unit(
  unit_name,
  remark1,
  input_man,
  input_date
  ) values
      
      (
      ?,
      ?,
      ?,
      now()
      )
     , 
      (
      ?,
      ?,
      ?,
      now()
      )
[] 2018-07-05 10:10:21 [61125] [Sharding-JDBC-SQL]-[INFO] localhost-startStop-1 io.shardingjdbc.core.util.SQLLogger.log(SQLLogger.java:59) SQLStatement: DMLStatement(super=AbstractSQLStatement(type=DML, tables=Tables(tables=[]), conditions=Conditions(conditions={}), sqlTokens=[], parametersIndex=0))
[] 2018-07-05 10:10:21 [61125] [Sharding-JDBC-SQL]-[INFO] localhost-startStop-1 io.shardingjdbc.core.util.SQLLogger.log(SQLLogger.java:59) Actual SQL: dataSource_1 ::: insert into t_Unit(
  unit_name,
  remark1,
  input_man,
  input_date
  ) values
      
      (
      ?,
      ?,
      ?,
      now()
      )
     , 
      (
      ?,
      ?,
      ?,
      now()
      ) ::: [强制指定Hint-1,  , 系统管理员, 强制指定Hint-3,  , 系统管理员]
[] 2018-07-05 10:10:21 [61514] [c.a.d.p.PreparedStatementPool]-[DEBUG] localhost-startStop-1 com.alibaba.druid.pool.PreparedStatementPool.put(PreparedStatementPool.java:129) {conn-110005, pstmt-120000} enter cache
[] 2018-07-05 10:10:21 [61515] [o.m.s.SqlSessionUtils]-[DEBUG] localhost-startStop-1 org.mybatis.spring.SqlSessionUtils.closeSqlSession(SqlSessionUtils.java:168) Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2b3a6ec5]
[] 2018-07-05 10:10:21 [61515] [o.s.j.d.DataSourceUtils]-[DEBUG] localhost-startStop-1 org.springframework.jdbc.datasource.DataSourceUtils.doReleaseConnection(DataSourceUtils.java:329) Returning JDBC Connection to DataSource
[] 2018-07-05 10:10:21 [61519] [o.m.s.SqlSessionUtils]-[DEBUG] localhost-startStop-1 org.mybatis.spring.SqlSessionUtils.getSqlSession(SqlSessionUtils.java:104) Creating a new SqlSession
[] 2018-07-05 10:10:21 [61519] [o.m.s.SqlSessionUtils]-[DEBUG] localhost-startStop-1 org.mybatis.spring.SqlSessionUtils.getSqlSession(SqlSessionUtils.java:140) SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@41aefec2] was not registered for synchronization because synchronization is not active
[] 2018-07-05 10:10:21 [61523] [o.s.j.d.DataSourceUtils]-[DEBUG] localhost-startStop-1 org.springframework.jdbc.datasource.DataSourceUtils.doGetConnection(DataSourceUtils.java:110) Fetching JDBC Connection from DataSource
[] 2018-07-05 10:10:21 [61523] [o.m.s.t.SpringManagedTransaction]-[DEBUG] localhost-startStop-1 org.mybatis.spring.transaction.SpringManagedTransaction.openConnection(SpringManagedTransaction.java:86) JDBC Connection [io.shardingjdbc.core.jdbc.core.connection.ShardingConnection@5e996b5d] will not be managed by Spring
[] 2018-07-05 10:10:21 [61528] [i.s.c.r.t.h.DatabaseHintRoutingEngine]-[DEBUG] localhost-startStop-1 io.shardingjdbc.core.routing.type.hint.DatabaseHintRoutingEngine.route(DatabaseHintRoutingEngine.java:55) Before database sharding only db:[dataSource_0, dataSource_1, globalDataSource] sharding values: ListShardingValue(logicTableName=DB_TABLE_NAME, columnName=DB_COLUMN_NAME, values=[1])
[] 2018-07-05 10:10:21 [61529] [i.s.c.r.t.h.DatabaseHintRoutingEngine]-[DEBUG] localhost-startStop-1 io.shardingjdbc.core.routing.type.hint.DatabaseHintRoutingEngine.route(DatabaseHintRoutingEngine.java:59) After database sharding only result: [dataSource_1]
[] 2018-07-05 10:10:21 [61530] [Sharding-JDBC-SQL]-[INFO] localhost-startStop-1 io.shardingjdbc.core.util.SQLLogger.log(SQLLogger.java:59) Logic SQL: select * from t_Unit
[] 2018-07-05 10:10:21 [61530] [Sharding-JDBC-SQL]-[INFO] localhost-startStop-1 io.shardingjdbc.core.util.SQLLogger.log(SQLLogger.java:59) SQLStatement: SelectStatement(super=DQLStatement(super=AbstractSQLStatement(type=DQL, tables=Tables(tables=[]), conditions=Conditions(conditions={}), sqlTokens=[], parametersIndex=0)), containStar=false, selectListLastPosition=0, groupByLastPosition=0, items=[], groupByItems=[], orderByItems=[], limit=null, subQueryStatement=null)
[] 2018-07-05 10:10:21 [61530] [Sharding-JDBC-SQL]-[INFO] localhost-startStop-1 io.shardingjdbc.core.util.SQLLogger.log(SQLLogger.java:59) Actual SQL: dataSource_1 ::: select * from t_Unit
[] 2018-07-05 10:10:21 [61576] [c.a.d.p.PreparedStatementPool]-[DEBUG] localhost-startStop-1 com.alibaba.druid.pool.PreparedStatementPool.put(PreparedStatementPool.java:129) {conn-110005, pstmt-120001} enter cache
[] 2018-07-05 10:10:21 [61577] [o.m.s.SqlSessionUtils]-[DEBUG] localhost-startStop-1 org.mybatis.spring.SqlSessionUtils.closeSqlSession(SqlSessionUtils.java:168) Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@41aefec2]
[] 2018-07-05 10:10:21 [61578] [o.s.j.d.DataSourceUtils]-[DEBUG] localhost-startStop-1 org.springframework.jdbc.datasource.DataSourceUtils.doReleaseConnection(DataSourceUtils.java:329) Returning JDBC Connection to DataSource
[] 2018-07-05 10:10:21 [61582] [c.y.c.s.ParamexService]-[INFO] localhost-startStop-1 com.yidoo.common.service.ParamexService.afterPropertiesSet(ParamexService.java:79) 强制指定Hint-1提示[{"unitName":"体积","remark1":" ","inputMan":"系统管理员","inputDate":1528166814000,"updateMan":null,"updateDate":1528166814000},{"unitName":"容量","remark1":" ","inputMan":"系统管理员","inputDate":1528166814000,"updateMan":null,"updateDate":1528166814000},{"unitName":"广播","remark1":" ","inputMan":"系统管理员","inputDate":1529984218000,"updateMan":null,"updateDate":1529984218000},{"unitName":"强制指定Hint-1","remark1":" ","inputMan":"系统管理员","inputDate":1530756621000,"updateMan":null,"updateDate":1530756621000},{"unitName":"强制指定Hint-3","remark1":" ","inputMan":"系统管理员","inputDate":1530756621000,"updateMan":null,"updateDate":1530756621000},{"unitName":"数量","remark1":" ","inputMan":"系统管理员","inputDate":1528166814000,"updateMan":null,"updateDate":1528166814000},{"unitName":"重量","remark1":" ","inputMan":"系统管理员","inputDate":1528166814000,"updateMan":null,"updateDate":1528166814000},{"unitName":"长度","remark1":" ","inputMan":"系统管理员","inputDate":1528166814000,"updateMan":null,"updateDate":1528166814000},{"unitName":"面积","remark1":" ","inputMan":"系统管理员","inputDate":1528166814000,"updateMan":null,"updateDate":1528166814000}]
[] 2018-07-05 10:10:21 [61585] [i.s.c.r.t.h.DatabaseHintRoutingEngine]-[DEBUG] localhost-startStop-1 io.shardingjdbc.core.routing.type.hint.DatabaseHintRoutingEngine.route(DatabaseHintRoutingEngine.java:55) Before database sharding only db:[dataSource_0, dataSource_1, globalDataSource] sharding values: ListShardingValue(logicTableName=DB_TABLE_NAME, columnName=DB_COLUMN_NAME, values=[2])
[] 2018-07-05 10:10:21 [61586] [i.s.c.r.t.h.DatabaseHintRoutingEngine]-[DEBUG] localhost-startStop-1 io.shardingjdbc.core.routing.type.hint.DatabaseHintRoutingEngine.route(DatabaseHintRoutingEngine.java:59) After database sharding only result: [dataSource_0]
[] 2018-07-05 10:10:21 [61587] [Sharding-JDBC-SQL]-[INFO] localhost-startStop-1 io.shardingjdbc.core.util.SQLLogger.log(SQLLogger.java:59) Logic SQL: insert into t_Unit(
  unit_name,
  remark1,
  input_man,
  input_date
  ) values
      
      (
      ?,
      ?,
      ?,
      now()
      )
     , 
      (
      ?,
      ?,
      ?,
      now()
      )
[] 2018-07-05 10:10:21 [61587] [Sharding-JDBC-SQL]-[INFO] localhost-startStop-1 io.shardingjdbc.core.util.SQLLogger.log(SQLLogger.java:59) SQLStatement: DMLStatement(super=AbstractSQLStatement(type=DML, tables=Tables(tables=[]), conditions=Conditions(conditions={}), sqlTokens=[], parametersIndex=0))
[] 2018-07-05 10:10:21 [61587] [Sharding-JDBC-SQL]-[INFO] localhost-startStop-1 io.shardingjdbc.core.util.SQLLogger.log(SQLLogger.java:59) Actual SQL: dataSource_0 ::: insert into t_Unit(
  unit_name,
  remark1,
  input_man,
  input_date
  ) values
      
      (
      ?,
      ?,
      ?,
      now()
      )
     , 
      (
      ?,
      ?,
      ?,
      now()
      ) ::: [强制指定Hint-2,  , 系统管理员, 强制指定Hint-4,  , 系统管理员]
[] 2018-07-05 10:10:21 [61624] [c.a.d.p.PreparedStatementPool]-[DEBUG] localhost-startStop-1 com.alibaba.druid.pool.PreparedStatementPool.put(PreparedStatementPool.java:129) {conn-10005, pstmt-20000} enter cache
[] 2018-07-05 10:10:21 [61625] [o.m.s.SqlSessionUtils]-[DEBUG] localhost-startStop-1 org.mybatis.spring.SqlSessionUtils.closeSqlSession(SqlSessionUtils.java:168) Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@767f7324]
[] 2018-07-05 10:10:21 [61625] [o.s.j.d.DataSourceUtils]-[DEBUG] localhost-startStop-1 org.springframework.jdbc.datasource.DataSourceUtils.doReleaseConnection(DataSourceUtils.java:329) Returning JDBC Connection to DataSource
[] 2018-07-05 10:10:21 [61625] [o.m.s.SqlSessionUtils]-[DEBUG] localhost-startStop-1 org.mybatis.spring.SqlSessionUtils.getSqlSession(SqlSessionUtils.java:104) Creating a new SqlSession
[] 2018-07-05 10:10:21 [61626] [o.m.s.SqlSessionUtils]-[DEBUG] localhost-startStop-1 org.mybatis.spring.SqlSessionUtils.getSqlSession(SqlSessionUtils.java:140) SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4c582b4c] was not registered for synchronization because synchronization is not active
[] 2018-07-05 10:10:21 [61626] [o.s.j.d.DataSourceUtils]-[DEBUG] localhost-startStop-1 org.springframework.jdbc.datasource.DataSourceUtils.doGetConnection(DataSourceUtils.java:110) Fetching JDBC Connection from DataSource
[] 2018-07-05 10:10:21 [61627] [o.m.s.t.SpringManagedTransaction]-[DEBUG] localhost-startStop-1 org.mybatis.spring.transaction.SpringManagedTransaction.openConnection(SpringManagedTransaction.java:86) JDBC Connection [io.shardingjdbc.core.jdbc.core.connection.ShardingConnection@b551009] will not be managed by Spring
[] 2018-07-05 10:10:21 [61769] [Sharding-JDBC-SQL]-[INFO] localhost-startStop-1 io.shardingjdbc.core.util.SQLLogger.log(SQLLogger.java:59) Logic SQL: select * from t_Unit
[] 2018-07-05 10:10:21 [61770] [Sharding-JDBC-SQL]-[INFO] localhost-startStop-1 io.shardingjdbc.core.util.SQLLogger.log(SQLLogger.java:59) SQLStatement: SelectStatement(super=DQLStatement(super=AbstractSQLStatement(type=DQL, tables=Tables(tables=[Table(name=t_Unit, alias=Optional.absent())]), conditions=Conditions(conditions={}), sqlTokens=[TableToken(beginPosition=14, originalLiterals=t_Unit)], parametersIndex=0)), containStar=true, selectListLastPosition=9, groupByLastPosition=0, items=[StarSelectItem(owner=Optional.absent())], groupByItems=[], orderByItems=[], limit=null, subQueryStatement=null)
[] 2018-07-05 10:10:21 [61771] [Sharding-JDBC-SQL]-[INFO] localhost-startStop-1 io.shardingjdbc.core.util.SQLLogger.log(SQLLogger.java:59) Actual SQL: globalDataSource ::: select * from t_Unit
[] 2018-07-05 10:10:22 [61782] [c.a.d.p.PreparedStatementPool]-[DEBUG] localhost-startStop-1 com.alibaba.druid.pool.PreparedStatementPool.put(PreparedStatementPool.java:129) {conn-210005, pstmt-220000} enter cache
[] 2018-07-05 10:10:22 [61782] [o.m.s.SqlSessionUtils]-[DEBUG] localhost-startStop-1 org.mybatis.spring.SqlSessionUtils.closeSqlSession(SqlSessionUtils.java:168) Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4c582b4c]
[] 2018-07-05 10:10:22 [61783] [o.s.j.d.DataSourceUtils]-[DEBUG] localhost-startStop-1 org.springframework.jdbc.datasource.DataSourceUtils.doReleaseConnection(DataSourceUtils.java:329) Returning JDBC Connection to DataSource
[] 2018-07-05 10:10:22 [61785] [c.y.c.s.ParamexService]-[INFO] localhost-startStop-1 com.yidoo.common.service.ParamexService.afterPropertiesSet(ParamexService.java:102) 强制指定Hint-2提示[{"unitName":"体积","remark1":" ","inputMan":"系统管理员","inputDate":1528166814000,"updateMan":null,"updateDate":1528166814000},{"unitName":"容量","remark1":" ","inputMan":"系统管理员","inputDate":1528166814000,"updateMan":null,"updateDate":1528166814000},{"unitName":"广播","remark1":" ","inputMan":"系统管理员","inputDate":1529984218000,"updateMan":null,"updateDate":1529984218000},{"unitName":"强制指定Hint-1","remark1":" ","inputMan":"系统管理员","inputDate":1530701874000,"updateMan":null,"updateDate":1530701874000},{"unitName":"强制指定Hint-2","remark1":" ","inputMan":"系统管理员","inputDate":1530701874000,"updateMan":null,"updateDate":1530701874000},{"unitName":"强制指定Hint-3","remark1":" ","inputMan":"系统管理员","inputDate":1530701874000,"updateMan":null,"updateDate":1530701874000},{"unitName":"强制指定Hint-4","remark1":" ","inputMan":"系统管理员","inputDate":1530701874000,"updateMan":null,"updateDate":1530701874000},{"unitName":"数量","remark1":" ","inputMan":"系统管理员","inputDate":1528166814000,"updateMan":null,"updateDate":1528166814000},{"unitName":"重量","remark1":" ","inputMan":"系统管理员","inputDate":1528166814000,"updateMan":null,"updateDate":1528166814000},{"unitName":"长度","remark1":" ","inputMan":"系统管理员","inputDate":1528166814000,"updateMan":null,"updateDate":1528166814000},{"unitName":"面积","remark1":" ","inputMan":"系统管理员","inputDate":1528166814000,"updateMan":null,"updateDate":1528166814000}]

 

注意点

一、使用了sharding-jdbc以后,select from TableName会被转换为select from tablename,也就是转小写,这是在代码中处理的,若是不但愿转换(sql标准是大小写不敏感的,主要是mysql linux下lower-case-table-names=1这个特例会区分大小写,并且mysql 8.0不容许数据库初始化和启动的值不一致,5.7以前是能够的,https://bugs.mysql.com/bug.php?id=90695),则须要本身编译sharding-jdbc-core(咱们编译了一版sharding-jdbc-core-2.0.3.zip),并更改getTableTokens中的转小写的代码(这应该算是个bug),2.0.3版本代码位置为:

// io.shardingjdbc.core.rewrite.SQLRewriteEngine.getTableTokens(TableUnit)中对sql语句中的表名作了toLowerCase()致使的,以下:
    private Map<String, String> getTableTokens(final TableUnit tableUnit) { String logicTableName = tableUnit.getLogicTableName().toLowerCase(); Map<String, String> tableTokens = new HashMap<>(); tableTokens.put(logicTableName, tableUnit.getActualTableName()); Optional<BindingTableRule> bindingTableRule = shardingRule.findBindingTableRule(logicTableName); if (bindingTableRule.isPresent()) { tableTokens.putAll(getBindingTableTokens(tableUnit, bindingTableRule.get())); } return tableTokens; }

二、必定要设置context:property-placeholder的ignore-unresolvable属性为true,即<context:property-placeholder location="classpath:property/*.properties" ignore-unresolvable="true" />,不然会报没法解析占位符。

三、咱们在调试的时候遇到个问题,若是eclipse中同时把sharding-jdbc的源代码引入进来,war启动的时候就会报sharding.xsd找不到,而后启动出错。

参考:

各非行内ShardingStrategy的用法能够参考https://www.cnblogs.com/mr-yang-localhost/p/8313360.html

其余:

https://wenku.baidu.com/view/ddeaed18910ef12d2bf9e74a.html

https://www.oschina.net/question/2918182_2280300

https://www.oschina.net/news/88860/sharding-jdbc-1-5-4-released

http://www.infoq.com/cn/news/2017/12/Sharding-JDBC-2

http://cmsblogs.com/?p=2542

http://shardingjdbc.io/document/legacy/2.x/en/00-overview/

https://www.oschina.net/question/2356021_2264290 

https://blog.csdn.net/vkingnew/article/details/80613043

https://www.jianshu.com/p/dd47dd3b1f6b

https://www.zhihu.com/question/64709787

sharding-jdbc不支持SQL

https://blog.csdn.net/Farrell_zeng/article/details/52958189

mycat不支持SQL

https://blog.csdn.net/educast/article/details/50013355

相关文章
相关标签/搜索