范文健康探索娱乐情感热点
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

一文快速入门分库分表中间件ShardingJDBC

  作为 Sharding-JDBC  分库分表实战系列的开篇文章,我们在前文中回顾了一下分库分表的基础知识,对分库分表的拆分方式有了一定的了解,下边我们介绍一下  Sharding-JDBC 框架和快速的搭建一个分库分表案例,为讲解后续功能点准备好环境。 一、Sharding-JDBC 简介
  Sharding-JDBC  最早是当当网内部使用的一款分库分表框架,到2017年的时候才开始对外开源,这几年在大量社区贡献者的不断迭代下,功能也逐渐完善,现已更名为 ShardingSphere ,2020年4⽉16⽇正式成为 Apache  软件基⾦会的顶级项⽬。
  随着版本的不断更迭  ShardingSphere  的核心功能也变得多元化起来。从最开始 Sharding-JDBC 1.0 版本只有数据分片,到 Sharding-JDBC 2.0 版本开始支持数据库治理(注册中心、配置中心等等),再到 Sharding-JDBC 3.0版本又加分布式事务 (支持 Atomikos 、Narayana 、Bitronix 、Seata ),如今已经迭代到了 Sharding-JDBC 4.0 版本。
  在这里插入图片描述
  现在的 ShardingSphere 不单单是指某个框架而是一个生态圈,这个生态圈  Sharding-JDBC 、Sharding-Proxy  和 Sharding-Sidecar  这三款开源的分布式数据库中间件解决方案所构成。
  ShardingSphere  的前身就是 Sharding-JDBC ,所以它是整个框架中最为经典、成熟的组件,我们先从 Sharding-JDBC  框架入手学习分库分表。二、核心概念
  在开始  Sharding-JDBC 分库分表具体实战之前,我们有必要先了解分库分表的一些核心概念。分片
  一般我们在提到分库分表的时候,大多是以水平切分模式(水平分库、分表)为基础来说的,数据分片将原本一张数据量较大的表  t_order  拆分生成数个表结构完全一致的小数据量表 t_order_0 、t_order_1 、···、t_order_n ,每张表只存储原大表中的一部分数据,当执行一条SQL 时会通过 分库策略 、分片策略  将数据分散到不同的数据库、表内。
  在这里插入图片描述 数据节点
  数据节点是分库分表中一个不可再分的最小数据单元(表),它由数据源名称和数据表组成,例如上图中  order_db_1.t_order_0 、order_db_2.t_order_1  就表示一个数据节点。逻辑表
  逻辑表是指一组具有相同逻辑和数据结构表的总称。比如我们将订单表 t_order  拆分成 t_order_0  ··· t_order_9  等 10张表。此时我们会发现分库分表以后数据库中已不在有 t_order 这张表,取而代之的是 t_order_n ,但我们在代码中写 SQL  依然按 t_order  来写。此时 t_order  就是这些拆分表的逻辑表 。真实表
  真实表也就是上边提到的  t_order_n  数据库中真实存在的物理表。分片键
  用于分片的数据库字段。我们将  t_order  表分片以后,当执行一条SQL时,通过对字段 order_id  取模的方式来决定,这条数据该在哪个数据库中的哪个表中执行,此时 order_id  字段就是 t_order  表的分片健。
  在这里插入图片描述
  这样以来同一个订单的相关数据就会存在同一个数据库表中,大幅提升数据检索的性能,不仅如此  sharding-jdbc  还支持根据多个字段作为分片健进行分片。分片算法
  上边我们提到可以用分片健取模的规则分片,但这只是比较简单的一种,在实际开发中我们还希望用  >= 、<= 、> 、< 、BETWEEN  和 IN  等条件作为分片规则,自定义分片逻辑,这时就需要用到分片策略与分片算法。
  从执行 SQL 的角度来看,分库分表可以看作是一种路由机制,把 SQL 语句路由到我们期望的数据库或数据表中并获取数据,分片算法可以理解成一种路由规则。
  咱们先捋一下它们之间的关系,分片策略只是抽象出的概念,它是由分片算法和分片健组合而成,分片算法做具体的数据分片逻辑。
  分库、分表的分片策略配置是相对独立的,可以各自使用不同的策略与算法,每种策略中可以是多个分片算法的组合,每个分片算法可以对多个分片健做逻辑判断。
  分片算法和分片策略的关系
  注意  :sharding-jdbc 并没有直接提供分片算法的实现,需要开发者根据业务自行实现。
  sharding-jdbc  提供了4种分片算法:1、精确分片算法
  精确分片算法(PreciseShardingAlgorithm)用于单个字段作为分片键,SQL中有  =  与 IN  等条件的分片,需要在标准分片策略(StandardShardingStrategy  )下使用。2、范围分片算法
  范围分片算法(RangeShardingAlgorithm)用于单个字段作为分片键,SQL中有  BETWEEN AND 、> 、< 、>= 、<=  等条件的分片,需要在标准分片策略(StandardShardingStrategy  )下使用。3、复合分片算法
  复合分片算法(ComplexKeysShardingAlgorithm)用于多个字段作为分片键的分片操作,同时获取到多个分片健的值,根据多个字段处理业务逻辑。需要在复合分片策略( ComplexShardingStrategy  )下使用。4、Hint分片算法
  Hint分片算法(HintShardingAlgorithm)稍有不同,上边的算法中我们都是解析 SQL  语句提取分片键,并设置分片策略进行分片。但有些时候我们并没有使用任何的分片键和分片策略,可还想将 SQL 路由到目标数据库和表,就需要通过手动干预指定SQL的目标数据库和表信息,这也叫强制路由。分片策略
  上边讲分片算法的时候已经说过,分片策略是一种抽象的概念,实际分片操作的是由分片算法和分片健来完成的。 1、标准分片策略
  标准分片策略适用于单分片键,此策略支持  PreciseShardingAlgorithm  和 RangeShardingAlgorithm  两个分片算法。
  其中  PreciseShardingAlgorithm  是必选的,用于处理 =  和 IN  的分片。RangeShardingAlgorithm  是可选的,用于处理BETWEEN AND , > , < ,>= ,<=  条件分片,如果不配置RangeShardingAlgorithm ,SQL中的条件等将按照全库路由处理。2、复合分片策略
  复合分片策略,同样支持对 SQL语句中的  = ,> , < , >= , <= ,IN 和 BETWEEN AND 的分片操作。不同的是它支持多分片键,具体分配片细节完全由应用开发者实现。3、行表达式分片策略
  行表达式分片策略,支持对 SQL语句中的  =  和 IN  的分片操作,但只支持单分片键。这种策略通常用于简单的分片,不需要自定义分片算法,可以直接在配置文件中接着写规则。
  t_order_$->{t_order_id % 4}  代表 t_order  对其字段 t_order_id 取模,拆分成4张表,而表名分别是t_order_0  到 t_order_3 。4、Hint分片策略
  Hint分片策略,对应上边的Hint分片算法,通过指定分片健而非从  SQL 中提取分片健的方式进行分片的策略。分布式主键
  数据分⽚后,不同数据节点⽣成全局唯⼀主键是⾮常棘⼿的问题,同⼀个逻辑表( t_order )内的不同真实表(t_order_n )之间的⾃增键由于⽆法互相感知而产⽣重复主键。
  尽管可通过设置⾃增主键  初始值  和 步⻓  的⽅式避免ID碰撞,但这样会使维护成本加大,乏完整性和可扩展性。如果后去需要增加分片表的数量,要逐一修改分片表的步长,运维成本非常高,所以不建议这种方式。
  实现分布式主键⽣成器的方式很多,可以参考我之前写的《9种分布式ID生成方式》。
  为了让上手更加简单,ApacheShardingSphere 内置了 UUID 、SNOWFLAKE  两种分布式主键⽣成器,默认使⽤雪花算法(snowflake )⽣成64bit的⻓整型数据。不仅如此它还抽离出分布式主键⽣成器的接口,⽅便我们实现⾃定义的⾃增主键⽣成算法。广播表
  广播表:存在于所有的分片数据源中的表,表结构和表中的数据在每个数据库中均完全一致。一般是为字典表或者配置表  t_config ,某个表一旦被配置为广播表,只要修改某个数据库的广播表,所有数据源中广播表的数据都会跟着同步。绑定表
  绑定表:那些分片规则一致的主表和子表。比如: t_order  订单表和 t_order_item  订单服务项目表,都是按 order_id  字段分片,因此两张表互为绑定表关系。
  那绑定表存在的意义是啥呢?
  通常在我们的业务中都会使用  t_order  和 t_order_item  等表进行多表联合查询,但由于分库分表以后这些表被拆分成N多个子表。如果不配置绑定表关系,会出现笛卡尔积关联查询,将产生如下四条SQL 。SELECT * FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id=i.order_id  SELECT * FROM t_order_0 o JOIN t_order_item_1 i ON o.order_id=i.order_id  SELECT * FROM t_order_1 o JOIN t_order_item_0 i ON o.order_id=i.order_id  SELECT * FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id=i.order_id
  笛卡尔积查询
  而配置绑定表关系后再进行关联查询时,只要对应表分片规则一致产生的数据就会落到同一个库中,那么只需  t_order_0  和 t_order_item_0  表关联即可。SELECT * FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id=i.order_id  SELECT * FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id=i.order_id
  绑定表关系
  注意  :在关联查询时   t_order   它作为整个联合查询的主表。所有相关的路由计算都只使用主表的策略, t_order_item   表的分片相关的计算也会使用  t_order   的条件,所以要保证绑定表之间的分片键要完全相同。 三、和JDBC的猫腻
  从名字上不难看出, Sharding-JDBC  和 JDBC 有很大关系,我们知道 JDBC 是一种 Java  语言访问关系型数据库的规范,其设计初衷就是要提供一套用于各种数据库的统一标准,不同厂家共同遵守这套标准,并提供各自的实现方案供应用程序调用。
  在这里插入图片描述
  但其实对于开发人员而言,我们只关心如何调用 JDBC API 来访问数据库,只要正确使用  DataSource 、Connection 、Statement  、ResultSet  等 API 接口,直接操作数据库即可。所以如果想在 JDBC 层面实现数据分片就必须对现有的 API 进行功能拓展,而 Sharding-JDBC 正是基于这种思想,重写了 JDBC 规范并完全兼容了 JDBC 规范。
  JDBC流程
  对原有的  DataSource 、Connection  等接口扩展成 ShardingDataSource 、ShardingConnection ,而对外暴露的分片操作接口与 JDBC 规范中所提供的接口完全一致,只要你熟悉 JDBC 就可以轻松应用 Sharding-JDBC 来实现分库分表。
  在这里插入图片描述
  因此它适用于任何基于  JDBC  的 ORM  框架,如:JPA , Hibernate ,Mybatis ,Spring JDBC Template  或直接使用的 JDBC。完美兼容任何第三方的数据库连接池,如:DBCP , C3P0 , BoneCP ,Druid , HikariCP  等,几乎对主流关系型数据库都支持。
  那  Sharding-JDBC  又是如何拓展这些接口的呢?想知道答案我们就的从源码入手了,下边我们以 JDBC API 中的 DataSource  为例看看它是如何被重写扩展的。
  数据源  DataSource  接口的核心作用就是获取数据库连接对象 Connection ,我们看其内部提供了两个获取数据库连接的方法 ,并且继承了 CommonDataSource  和 Wrapper  两个接口。 public interface DataSource  extends CommonDataSource, Wrapper {    /**    * 

Attempts to establish a connection with the data source that * this {@code DataSource} object represents. * @return a connection to the data source */ Connection getConnection() throws SQLException; /** *

Attempts to establish a connection with the data source that * this {@code DataSource} object represents. * @param username the database user on whose behalf the connection is * being made * @param password the user"s password */ Connection getConnection(String username, String password) throws SQLException; }   其中 CommonDataSource 是定义数据源的根接口这很好理解,而 Wrapper 接口则是拓展 JDBC 分片功能的关键。   由于数据库厂商的不同,他们可能会各自提供一些超越标准 JDBC API 的扩展功能,但这些功能非 JDBC 标准并不能直接使用,而 Wrapper 接口的作用就是把一个由第三方供应商提供的、非 JDBC 标准的接口包装成标准接口,也就是适配器模式 。   既然讲到了适配器模式就多啰嗦几句,也方便后边的理解。   适配器模式个种比较常用的设计模式,它的作用是将某个类的接口转换成客户端期望的另一个接口,使原本因接口不匹配(或者不兼容)而无法在一起工作的两个类能够在一起工作。比如用耳机听音乐,我有个圆头的耳机,可手机插孔却是扁口的,如果我想要使用耳机听音乐就必须借助一个转接头才可以,这个转接头就起到了适配作用。举个栗子:假如我们 Target 接口中有 hello() 和 word() 两个方法。 public interface Target { void hello(); void world(); }   可由于接口版本迭代 Target 接口的 word() 方法可能会被废弃掉或不被支持,Adaptee 类的 greet() 方法将代替hello() 方法。public class Adaptee { public void greet(){ } public void world(){ } }   但此时旧版本仍然有大量 word() 方法被使用中,解决此事最好的办法就是创建一个适配器Adapter ,这样就适配了 Target 类,解决了接口升级带来的兼容性问题。public class Adapter extends Adaptee implements Target { @Override public void world() { } @Override public void hello() { super.greet(); } @Override public void greet() { } }   而 Sharding-JDBC 提供的正是非 JDBC 标准的接口,所以它也提供了类似的实现方案,也使用到了 Wrapper 接口做数据分片功能的适配。除了 DataSource 之外,Connection、Statement、ResultSet 等核心对象也都继承了这个接口。   下面我们通过 ShardingDataSource 类源码简单看下实现过程,下图是继承关系流程图。   ShardingDataSource实现流程   ShardingDataSource 类它在原 DataSource 基础上做了功能拓展,初始化时注册了分片SQL路由包装器、SQL重写上下文和结果集处理引擎,还对数据源类型做了校验,因为它要同时支持多个不同类型的数据源。到这好像也没看出如何适配,那接着向上看 ShardingDataSource 的继承类 AbstractDataSourceAdapter 。@Getter public class ShardingDataSource extends AbstractDataSourceAdapter { private final ShardingRuntimeContext runtimeContext; /** * 注册路由、SQl重写上下文、结果集处理引擎 */ static { NewInstanceServiceLoader.register(RouteDecorator.class); NewInstanceServiceLoader.register(SQLRewriteContextDecorator.class); NewInstanceServiceLoader.register(ResultProcessEngine.class); } /** * 初始化时校验数据源类型 并根据数据源 map、分片规则、数据库类型得到一个分片上下文,用来获取数据库连接 */ public ShardingDataSource(final Map dataSourceMap, final ShardingRule shardingRule, final Properties props) throws SQLException { super(dataSourceMap); checkDataSourceType(dataSourceMap); runtimeContext = new ShardingRuntimeContext(dataSourceMap, shardingRule, props, getDatabaseType()); } private void checkDataSourceType(final Map dataSourceMap) { for (DataSource each : dataSourceMap.values()) { Preconditions.checkArgument(!(each instanceof MasterSlaveDataSource), "Initialized data sources can not be master-slave data sources."); } } /** * 数据库连接 */ @Override public final ShardingConnection getConnection() { return new ShardingConnection(getDataSourceMap(), runtimeContext, TransactionTypeHolder.get()); } }   AbstractDataSourceAdapter 抽象类内部主要获取不同类型的数据源对应的数据库连接对象,实现 AutoCloseable 接口是为在使用完资源后可以自动将这些资源关闭(调用 close 方法),那再看看继承类 AbstractUnsupportedOperationDataSource 。@Getter public abstract class AbstractDataSourceAdapter extends AbstractUnsupportedOperationDataSource implements AutoCloseable { private final Map dataSourceMap; private final DatabaseType databaseType; public AbstractDataSourceAdapter(final Map dataSourceMap) throws SQLException { this.dataSourceMap = dataSourceMap; databaseType = createDatabaseType(); } public AbstractDataSourceAdapter(final DataSource dataSource) throws SQLException { dataSourceMap = new HashMap<>(1, 1); dataSourceMap.put("unique", dataSource); databaseType = createDatabaseType(); } private DatabaseType createDatabaseType() throws SQLException { DatabaseType result = null; for (DataSource each : dataSourceMap.values()) { DatabaseType databaseType = createDatabaseType(each); Preconditions.checkState(null == result || result == databaseType, String.format("Database type inconsistent with "%s" and "%s"", result, databaseType)); result = databaseType; } return result; } /** * 不同数据源类型获取数据库连接 */ private DatabaseType createDatabaseType(final DataSource dataSource) throws SQLException { if (dataSource instanceof AbstractDataSourceAdapter) { return ((AbstractDataSourceAdapter) dataSource).databaseType; } try (Connection connection = dataSource.getConnection()) { return DatabaseTypes.getDatabaseTypeByURL(connection.getMetaData().getURL()); } } @Override public final Connection getConnection(final String username, final String password) throws SQLException { return getConnection(); } @Override public final void close() throws Exception { close(dataSourceMap.keySet()); } }   AbstractUnsupportedOperationDataSource 实现DataSource 接口并继承了 WrapperAdapter 类,它内部并没有什么具体方法只起到桥接的作用,但看着是不是和我们前边讲适配器模式的例子方式有点相似。public abstract class AbstractUnsupportedOperationDataSource extends WrapperAdapter implements DataSource { @Override public final int getLoginTimeout() throws SQLException { throw new SQLFeatureNotSupportedException("unsupported getLoginTimeout()"); } @Override public final void setLoginTimeout(final int seconds) throws SQLException { throw new SQLFeatureNotSupportedException("unsupported setLoginTimeout(int seconds)"); } }   WrapperAdapter 是一个包装器的适配类,实现了 JDBC 中的 Wrapper 接口,其中有两个核心方法 recordMethodInvocation 用于添加需要执行的方法和参数,而 replayMethodsInvocation 则将添加的这些方法和参数通过反射执行。仔细看不难发现两个方法中都用到了 JdbcMethodInvocation 类。public abstract class WrapperAdapter implements Wrapper { private final Collection jdbcMethodInvocations = new ArrayList<>(); /** * 添加要执行的方法 */ @SneakyThrows public final void recordMethodInvocation(final Class<?> targetClass, final String methodName, final Class<?>[] argumentTypes, final Object[] arguments) { jdbcMethodInvocations.add(new JdbcMethodInvocation(targetClass.getMethod(methodName, argumentTypes), arguments)); } /** * 通过反射执行 上边添加的方法 */ public final void replayMethodsInvocation(final Object target) { for (JdbcMethodInvocation each : jdbcMethodInvocations) { each.invoke(target); } } }   JdbcMethodInvocation 类主要应用反射通过传入的 method 方法和 arguments 参数执行对应的方法,这样就可以通过 JDBC API 调用非 JDBC 方法了。@RequiredArgsConstructor public class JdbcMethodInvocation { @Getter private final Method method; @Getter private final Object[] arguments; /** * Invoke JDBC method. * * @param target target object */ @SneakyThrows public void invoke(final Object target) { method.invoke(target, arguments); } }   那 Sharding-JDBC 拓展 JDBC API 接口后,在新增的分片功能里又做了哪些事情呢?   一张表经过分库分表后被拆分成多个子表,并分散到不同的数据库中,在不修改原业务 SQL 的前提下, Sharding-JDBC 就必须对 SQL进行一些改造才能正常执行。   大致的执行流程: SQL 解析 -> 执⾏器优化 -> SQL 路由 -> SQL 改写 -> SQL 执⾏ -> 结果归并 六步组成,一起瞅瞅每个步骤做了点什么。   在这里插入图片描述 SQL 解析   SQL解析过程分为词法解析和语法解析两步,比如下边这条查询用户订单的SQL,先用词法解析将SQL拆解成不可再分的原子单元。在根据不同数据库方言所提供的字典,将这些单元归类为关键字,表达式,变量或者操作符等类型。 SELECT order_no,price FROM t_order_ where user_id = 10086 and order_status > 0   接着语法解析会将拆分后的SQL转换为抽象语法树,通过对抽象语法树遍历,提炼出分片所需的上下文,上下文包含查询字段信息( Field )、表信息(Table )、查询条件(Condition )、排序信息(Order By )、分组信息(Group By )以及分页信息(Limit )等,并标记出 SQL中有可能需要改写的位置。   抽象语法树 执⾏器优化   执⾏器优化对SQL分片条件进行优化,处理像关键字 OR 这种影响性能的坏味道。SQL 路由   SQL 路由通过解析分片上下文,匹配到用户配置的分片策略,并生成路由路径。简单点理解就是可以根据我们配置的分片策略计算出 SQL该在哪个库的哪个表中执行,而SQL路由又根据有无分片健区分出 分片路由 和 广播路由 。   官方路由图谱   有分⽚键的路由叫分片路由,细分为直接路由、标准路由和笛卡尔积路由这3种类型。 标准路由   标准路由是最推荐也是最为常⽤的分⽚⽅式,它的适⽤范围是不包含关联查询或仅包含绑定表之间关联查询的SQL。   当 SQL分片健的运算符为 = 时,路由结果将落⼊单库(表),当分⽚运算符是BETWEEN 或IN 等范围时,路由结果则不⼀定落⼊唯⼀的库(表),因此⼀条逻辑SQL最终可能被拆分为多条⽤于执⾏的真实SQL。SELECT * FROM t_order where t_order_id in (1,2)   SQL路由处理后 SELECT * FROM t_order_0 where t_order_id in (1,2) SELECT * FROM t_order_1 where t_order_id in (1,2) 直接路由   直接路由是通过使用 HintAPI 直接将 SQL路由到指定⾄库表的一种分⽚方式,而且直接路由可以⽤于分⽚键不在SQL中的场景,还可以执⾏包括⼦查询、⾃定义函数等复杂情况的任意SQL。   比如根据 t_order_id 字段为条件查询订单,此时希望在不修改SQL的前提下,加上 user_id 作为分片条件就可以使用直接路由。笛卡尔积路由   笛卡尔路由是由⾮绑定表之间的关联查询产生的,查询性能较低尽量避免走此路由模式。   无分⽚键的路由又叫做广播路由,可以划分为全库表路由、全库路由、 全实例路由、单播路由和阻断路由这 5种类型。 全库表路由   全库表路由针对的是数据库 DQL 和 DML ,以及 DDL 等操作,当我们执行一条逻辑表 t_order SQL时,在所有分片库中对应的真实表 t_order_0 ··· t_order_n 内逐一执行。全库路由   全库路由主要是对数据库层面的操作,比如数据库 SET 类型的数据库管理命令,以及 TCL 这样的事务控制语句。   对逻辑库设置 autocommit 属性后,所有对应的真实库中都执行该命令。SET autocommit=0; 全实例路由   全实例路由是针对数据库实例的 DCL 操作(设置或更改数据库用户或角色权限),比如:创建一个用户 order ,这个命令将在所有的真实库实例中执行,以此确保 order 用户可以正常访问每一个数据库实例。 CREATE USER order@127.0.0.1 identified BY "程序员内点事"; 单播路由   单播路由用来获取某一真实表信息,比如获得表的描述信息: DESCRIBE t_order;   t_order 的真实表是 t_order_0 ···· t_order_n ,他们的描述结构相完全同,我们只需在任意的真实表执行一次就可以。阻断路由   ⽤来屏蔽SQL对数据库的操作,例如: USE order_db;   这个命令不会在真实数据库中执⾏,因为 ShardingSphere 采⽤的是逻辑 Schema(数据库的组织和结构) ⽅式,所以无需将切换数据库的命令发送⾄真实数据库中。SQL 改写   将基于逻辑表开发的SQL改写成可以在真实数据库中可以正确执行的语句。比如查询 t_order 订单表,我们实际开发中 SQL是按逻辑表 t_order 写的。SELECT * FROM t_order   但分库分表以后真实数据库中 t_order 表就不存在了,而是被拆分成多个子表 t_order_n 分散在不同的数据库内,还按原SQL执行显然是行不通的,这时需要将分表配置中的逻辑表名称改写为路由之后所获取的真实表名称。SELECT * FROM t_order_n SQL执⾏   将路由和改写后的真实 SQL 安全且高效发送到底层数据源执行。但这个过程并不是简单的将 SQL 通过JDBC 直接发送至数据源执行,而是平衡数据源连接创建以及内存占用所产生的消耗,它会自动化的平衡资源控制与执行效率。 结果归并   将从各个数据节点获取的多数据结果集,合并成一个大的结果集并正确的返回至请求客户端,称为结果归并。而我们SQL中的排序、分组、分页和聚合等语法,均是在归并后的结果集上进行操作的。 四、快速实践   下面我们结合 Springboot + mybatisplus 快速搭建一个分库分表案例。1、准备工作   先做准备工作,创建两个数据库 ds-0 、ds-1 ,两个库中分别建表 t_order_0 、t_order_1 、t_order_2 、t_order_item_0 、t_order_item_1 、t_order_item_2 ,t_config ,方便后边验证广播表、绑定表的场景。   表结构如下:   t_order_0 订单表CREATE TABLE `t_order_0` ( `order_id` bigint(200) NOT NULL, `order_no` varchar(100) DEFAULT NULL, `create_name` varchar(50) DEFAULT NULL, `price` decimal(10,2) DEFAULT NULL, PRIMARY KEY (`order_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;   t_order_0 与 t_order_item_0 互为关联表CREATE TABLE `t_order_item_0` ( `item_id` bigint(100) NOT NULL, `order_no` varchar(200) NOT NULL, `item_name` varchar(50) DEFAULT NULL, `price` decimal(10,2) DEFAULT NULL, PRIMARY KEY (`item_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;   广播表 t_config `id` bigint(30) NOT NULL, `remark` varchar(50) CHARACTER SET utf8 DEFAULT NULL, `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `last_modify_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1;   ShardingSphere 提供了4种分片配置方式:Java 代码配置 Yaml 、properties 配置 Spring 命名空间配置 Spring Boot配置   为让代码看上去更简洁和直观,后边统一使用 properties 配置的方式,引入 shardingsphere 对应的 sharding-jdbc-spring-boot-starter 和 sharding-core-common 包,版本统一用的 4.0.0-RC1。2、分片配置 org.apache.shardingsphere sharding-jdbc-spring-boot-starter 4.0.0-RC1 org.apache.shardingsphere sharding-core-common 4.0.0-RC1   准备工作做完( mybatis 搭建就不赘述了),接下来我们逐一解读分片配置信息。   我们首先定义两个数据源 ds-0 、ds-1 ,并分别加上数据源的基础信息。# 定义两个全局数据源 spring.shardingsphere.datasource.names=ds-0,ds-1 # 配置数据源 ds-0 spring.shardingsphere.datasource.ds-0.type=com.alibaba.druid.pool.DruidDataSource spring.shardingsphere.datasource.ds-0.driverClassName=com.mysql.jdbc.Driver spring.shardingsphere.datasource.ds-0.url=jdbc:mysql://127.0.0.1:3306/ds-0?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT spring.shardingsphere.datasource.ds-0.username=root spring.shardingsphere.datasource.ds-0.password=root # 配置数据源 ds-1 spring.shardingsphere.datasource.ds-1.type=com.alibaba.druid.pool.DruidDataSource spring.shardingsphere.datasource.ds-1.driverClassName=com.mysql.jdbc.Driver spring.shardingsphere.datasource.ds-1.url=jdbc:mysql://127.0.0.1:3306/ds-1?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT spring.shardingsphere.datasource.ds-1.username=root spring.shardingsphere.datasource.ds-1.password=root   配置完数据源接下来为表添加分库和分表策略,使用 sharding-jdbc 做分库分表需要我们为每一个表单独设置分片规则。# 配置分片表 t_order # 指定真实数据节点 spring.shardingsphere.sharding.tables.t_order.actual-data-nodes=ds-$->{0..1}.t_order_$->{0..2}   actual-data-nodes 属性指定分片的真实数据节点,$ 是一个占位符,{0..1}表示实际拆分的数据库表数量。   ds-$->{0..1}.t_order_$->{0..2} 表达式相当于 6个数据节点ds-0.t_order_0 ds-0.t_order_1 ds-0.t_order_2 ds-1.t_order_0 ds-1.t_order_1 ds-1.t_order_2 ### 分库策略 # 分库分片健 spring.shardingsphere.sharding.tables.t_order.database-strategy.inline.sharding-column=order_id # 分库分片算法 spring.shardingsphere.sharding.tables.t_order.database-strategy.inline.algorithm-expression=ds-$->{order_id % 2}   为表设置分库策略,上边讲了 sharding-jdbc 它提供了四种分片策略,为快速搭建我们先以最简单的行内表达式分片策略来实现,在下一篇会介绍四种分片策略的详细用法和使用场景。   database-strategy.inline.sharding-column 属性中 database-strategy 为分库策略,inline 为具体的分片策略,sharding-column 代表分片健。   database-strategy.inline.algorithm-expression 是当前策略下具体的分片算法,ds-$->{order_id % 2} 表达式意思是 对 order_id 字段进行取模分库,2 代表分片库的个数,不同的策略对应不同的算法,这里也可以是我们自定义的分片算法类。 # 分表策略 # 分表分片健 spring.shardingsphere.sharding.tables.t_order.table-strategy.inline.sharding-column=order_id # 分表算法 spring.shardingsphere.sharding.tables.t_order.table-strategy.inline.algorithm-expression=t_order_$->{order_id % 3} # 自增主键字段 spring.shardingsphere.sharding.tables.t_order.key-generator.column=order_id # 自增主键ID 生成方案 spring.shardingsphere.sharding.tables.t_order.key-generator.type=SNOWFLAKE   分表策略 和 分库策略 的配置比较相似,不同的是分表可以通过 key-generator.column 和 key-generator.type 设置自增主键以及指定自增主键的生成方案,目前内置了SNOWFLAKE 和 UUID 两种方式,还能自定义的主键生成算法类,后续会详细的讲解。# 绑定表关系 spring.shardingsphere.sharding.binding-tables= t_order,t_order_item   必须按相同分片健进行分片的表才能互为成绑定表,在联合查询时就能避免出现笛卡尔积查询。 # 配置广播表 spring.shardingsphere.sharding.broadcast-tables=t_config   广播表,开启 SQL解析日志,能清晰的看到 SQL分片解析的过程 # 是否开启 SQL解析日志 spring.shardingsphere.props.sql.show=true 3、验证分片   分片配置完以后我们无需在修改业务代码了,直接执行业务逻辑的增、删、改、查即可,接下来验证一下分片的效果。   我们同时向 t_order 、t_order_item 表插入 5条订单记录,并不给定主键 order_id ,item_id 字段值。public String insertOrder() { for (int i = 0; i < 4; i++) { TOrder order = new TOrder(); order.setOrderNo("A000" + i); order.setCreateName("订单 " + i); order.setPrice(new BigDecimal("" + i)); orderRepository.insert(order); TOrderItem orderItem = new TOrderItem(); orderItem.setOrderId(order.getOrderId()); orderItem.setOrderNo("A000" + i); orderItem.setItemName("服务项目" + i); orderItem.setPrice(new BigDecimal("" + i)); orderItemRepository.insert(orderItem); } return "success"; }   看到订单记录被成功分散到了不同的库表中, order_id 字段也自动生成了主键ID,基础的分片功能就完成了。   基础分片   那向广播表 t_config 中插入一条数据会是什么效果呢? public String config() { TConfig tConfig = new TConfig(); tConfig.setRemark("我是广播表"); tConfig.setCreateTime(new Date()); tConfig.setLastModifyTime(new Date()); configRepository.insert(tConfig); return "success"; }   发现所有库中 t_config 表都执行了这条SQL,广播表和 MQ广播订阅的模式很相似,所有订阅的客户端都会收到同一条消息。   广播表   简单SQL操作验证没问通,接下来在试试复杂一点的联合查询,前边我们已经把 t_order 、t_order_item 表设为绑定表,直接联表查询执行一下。   关联查询   通过控制台日志发现,逻辑表SQL 经过解析以后,只对 t_order_0 和 t_order_item_0 表进行了关联产生一条SQL。   绑定表SQL   那如果不互为绑定表又会是什么情况呢?去掉 spring.shardingsphere.sharding.binding-tables 试一下。   发现控制台解析出了 3条真实表SQL,而去掉 order_id 作为查询条件再次执行后,结果解析出了 9条SQL,进行了笛卡尔积查询。所以相比之下绑定表的优点就不言而喻了。   笛卡尔积查询 五、总结   以上对分库分表中间件 sharding-jdbc 的基础概念做了简单梳理,快速的搭建了一个分库分表案例,但这只是实践分库分表的第一步,下一篇我们会详细的介绍四种分片策略的具体用法和使用场景(必知必会),后边将陆续讲解自定义分布式主键、分布式数据库事务、分布式服务治理,数据脱敏等。   案例 GitHub 地址:https://github.com/chengxy-nds/Springboot-Notebook/tree/master/springboot-sharding-jdbc


达拉然大劫案获好评,新冒险模式重新定义卡牌PVE距离5月17日炉石传说新的大冒险模式达拉然大劫案发布已经过去了一周,对于大部分炉石玩家都可以说是心满意足嗨到飞起的一周。无论是传统的PVP对战玩家还是喜欢冒险模式和AI对抗的佛系玩新橙卡登场,阴谋双生跟班加持!来看新版本怎么战!为巨龙年的第一个拓展包,在经历了整整四个视频预告之后,千呼万唤始出来。近日,炉石传说官方终于正式公布了全新版本暗影崛起,并披露了部分卡牌。据悉,全新版本将于4月10日上线,而对应的天际探索神仙打架,完美世界重温曾经御风翱翔的感动游戏作为能够实现玩家美好想象的奇妙载体,一直受到我们的热爱。身为普通人,也许此生不能拥有诸多奇妙的经历。但是依靠游戏的力量,我们可以最大程度地获得各式各样的体验。在赛车游戏中我们可新冒险达拉然大劫案上线,主播们为通关各显神通。5月17日,令人期待已久的炉石传说新冒险模式达拉然大劫案正式上市。暴雪为了满足玩家们好人做久了,偶尔过一把坏人瘾儿的需求和兴趣,推出了扮演恶棍的爪牙挑战达拉然的全新剧情冒险。虽然本昔日小主播如今举办个人演唱会一起看冯提莫的华丽蜕变2019年8月3日,知名主播冯提莫将举行首场个人演唱会,冯提莫也将成为主播转型歌手开办线下演唱会在重庆大礼堂首位举办个唱的三项第一人。据悉,此次演唱会还将与提莫的首张实体专辑同步发雷神驾临炉石传说,玩家新皮肤帅炸!我也要过一把皇帝瘾对于所有玩过魔兽世界的中国玩家,熊猫人之谜这部资料片一定不陌生,作为一部以中国文化作为背景制作的资料片,深受广大国内玩家的喜爱。无论是新种族熊猫人新职业武僧还是以兵马俑为原型制作的守望先锋又双叕搞事情,玩家长假打几折?暴雪霸霸骨折每年的十一黄金周,都是各大游戏厂商集中发放福利的高峰期。本来中国游戏市场的份额就足够巨大,七天长假又更是网友一年一度娱乐和消费的盛会。许多厂商会在长假前后打出各种各样折扣的组合拳,LPLS11全球总决赛出征服爆丑无比,玩家吐槽赖皮蛇9月5日,随着LPLS11资格赛最后一轮比赛的结束,咱们LPL的四个种子EDGFPXRNGLNG也是一起出席了出征仪式。出征仪式虽然激动人心,但是当选手们的出场亮相出征服之后,网友伊泽瑞尔为什么是神?伊泽瑞尔为什么是神?在谈论这个问题之前,我想先说说其他ad相较于伊泽瑞尔究竟差在了哪里。首先是犯下傲慢之罪的德莱文两刀秒了软辅,露出不屑的笑就开始闹够了没有嘲讽对面,这种傲慢的ad界姜维卖500灵宝?狗都不买9月9日晚,三国杀OL发布公告,9。109。16界姜维占星秘宝张昌蒲武将皮肤教师节全新上线,张妈终于上架商城,而界姜维也终于可以买到了。张妈没什么好说的,她强就强在她队友比较强,她一年外观全买居然要3。5万?剑网3的第十二年外观全统计这段时间以来,总有大伙问我玩剑网3究竟花不花钱。我只能回答你如果你能忍住不买外观,那么玩剑网3不花钱,每个月的月卡充下来也就60,点卡可能会更少或者更多,但是也不会超过100块。而
16支战队集结街头篮球SFSA总决赛日期公布经过了数个月线下赛以及线上赛的激烈争夺,街头篮球SFSA全国联赛地区赛已经圆满落下了帷幕,16支总决赛战队已经出炉,接下来将会进行最终对决,总决赛日程也已经确定,让我们一起了解一下GameHollywood携StallionRace赛马王RobotTacticsX亮相Gamescom2021年以来,COVID19持续影响着人们的生活,而全球最大的游戏展会Gamescom以纯数字的形式举办。它是大厂大作轮番上阵的讲台,也是独立开发者发声的舞台。中国老牌出海发行G柔情似水佳期如梦英魂之刃口袋版涂山红红新皮肤上线8月27日,英魂之刃口袋版狐妖小红娘联动新英雄涂山红红全新皮肤情梦回还隆重登场!作为与东方月初的情侣皮肤,她将在万众瞩目下抢先一步上线!另外,自然之灵新皮肤暗黑小魔星9月1日正式上十万现金大奖!燃烧意志杯跨服争霸赛将至各位船长好呀!燃烧意志杯跨服争霸赛开赛在即,十万现金大奖专属称号争霸赛霸主(称号属性攻击60速度25暴击伤害3)凡是能够在9月18日2400天梯赛结算时进入前十名的船长,均可获得该国风武侠武林志2五大必打卡场景曝光,古城剑冢暗藏杀机由独立开发团队江湖工作室打造的武侠单机游戏武林志2已于8月12日在Steam平台开启了抢先体验(EarlyAccess),本作采用开放世界设定,力图为玩家带来一个真实自由的江湖世界神迹X勇夺无双杯夏季赛冠军,MSPL秋季赛8支战队大名单揭晓为梦一搏,突破自我!8月29日,由电魂主办,奥义电竞承办的2019梦三国2无双杯全民选拔赛(夏季赛)落下帷幕。当晚总决赛上,由多名前职业选手重组的神迹X展现王者风范,以30的比分干剑侠热血回归!剑侠世界3iOS测试9月即将来袭二十五载真江湖,如今再续剑侠情。由西山居原班人马倾力打造的新一代剑侠情缘手游剑侠世界3将于9月开启iOS不付费删档测试!经典剑侠情缘IP再度传承,倾情打造最真的江湖世界,带你回归最从爆火的巴啦啦变身挑战,看迷你世界的IP创世伴着迷你世界虚拟偶像花小楼的全新热单你可不可以跟我看一看彩虹,众多UP主在抖音带巴啦啦变身挑战话题,纷纷挑战自己最擅长的变身方式,Cosplay变身小魔仙。迷你世界的IP联动和破圈剑侠聚首,再续前缘!剑侠世界iOS测试定档9月17日二十五载风雨兼程,犹不改剑侠江湖梦。新一代剑侠情缘手游剑侠世界3将于9月17日开启iOS测试!剑侠情缘IP系列当中那些道不尽的武侠故事诉不完的江湖情怀,如今将要再添上浓墨重彩的一笔为了守护世界街头篮球超能战队美少女卢比ampampamp玛琳为爱而战上周发布了街头篮球国庆超特宣传片水与火之歌之后大家一定对卢比玛琳很感兴趣,那么一起来了解下她的故事吧在我们生活的这个世界上有着一群可以运用神秘力量的人类,他们是一群可以熟练的使用水武侠开放世界武林志2发布更新路线图,侠恶机制首曝近期,Steam武侠开放世界游戏武林志2更新了多篇研发日记,并曝光了游戏未来的更新路线图。武林志2力图呈现一个真实自由的江湖世界,冲破固有路线,让正邪善恶产生多元碰撞,带来更加丰富