sharding (一)不分库 只分表 (根据 id 主键)进行分表,standard: 标准分片策略,用于单分片键的场景

本文详细介绍了如何使用Sharding-JDBC实现数据库分片,包括配置策略、算法实现及应用示例。通过SpringMVC+MyBatis项目,演示了标准分片策略下,如何配置数据源、表规则及绑定表,以及自定义分片算法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

配置有三种策略:

defaultDataSourceName: 默认数据源,未配置分片规则的表将通过默认数据源定位
 
tables: 分库分表配置,可配置多个logic_table_name
    <logic_table_name>: 逻辑表名
        actualDataNodes: 真实数据节点,由数据源名 + 表名组成,以小数点分隔。多个表以逗号分隔,支持inline表达式。不填写表示将为现有已知的数据源 + 逻辑表名称生成真实数据节点。用于广播表(即每个库中都需要一个同样的表用于关联查询,多为字典表)或只分库不分表且所有库的表结构完全一致的情况。
        
        databaseStrategy: 分库策略,以下的分片策略只能任选其一
            standard: 标准分片策略,用于单分片键的场景
                shardingColumn: 分片列名
                preciseAlgorithmClassName: 精确的分片算法类名称,用于=和IN。该类需使用默认的构造器或者提供无参数的构造器
                rangeAlgorithmClassName: 范围的分片算法类名称,用于BETWEEN,可以不配置。该类需使用默认的构造器或者提供无参数的构造器
                
            complex: 复合分片策略,用于多分片键的场景
                shardingColumns : 分片列名,多个列以逗号分隔
                algorithmClassName: 分片算法类名称。该类需使用默认的构造器或者提供无参数的构造器
                
            inline: inline表达式分片策略
                shardingColumn : 分片列名
                algorithmInlineExpression: 分库算法Inline表达式,需要符合groovy动态语法
                
            hint: Hint分片策略
                algorithmClassName: 分片算法类名称。该类需使用默认的构造器或者提供无参数的构造器
            none: 不分片
        tableStrategy: 分表策略,同分库策略
  bindingTables: 绑定表列表
  - 逻辑表名列表,多个<logic_table_name>以逗号分隔
 
defaultDatabaseStrategy: 默认数据库分片策略,同分库策略
 
defaultTableStrategy: 默认数据表分片策略,同分库策略
 
props: 属性配置(可选)
    sql.show: 是否开启SQL显示,默认值: false
    executor.size: 工作线程数量,默认值: CPU核数

 

学习中,我们先使用 标准分片策略

直接上硬货: 项目是 maven 的 spring mvc +  mybaties  项目,数据库  mysql

maven  pom.xml 中添加 sharding 依赖

<!-- 引入sharding-jdbc核心模块 -->
<dependency>
    <groupId>io.shardingjdbc</groupId>
    <artifactId>sharding-jdbc-core</artifactId>
    <version>2.0.3</version>
</dependency>

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

 

 

 

applicationContext.xml 中 sharding 配置如下

<!--分表策略,sharding-column 这里根据 id (这列属性不能是String,只能是整型)分表,AdminSingleKeyTableShardingAlgorithm 分表算法-->
<sharding:standard-strategy id="tableShardingStrategy" sharding-column="id"
                            precise-algorithm-class="com.study.algorithm.AdminSingleKeyTableShardingAlgorithm"/>

<sharding:data-source id="shardingDataSource">
    <!--因为我只有一张表。所以就只需要配置一个dataSource-->
    <sharding:sharding-rule data-source-names="dataSource" default-data-source-name="dataSource">
        <sharding:table-rules>
            <!--logic-tables逻辑表名: 逻辑表名 其实就是 sql 中 写的表名称-->
            <!--actual-data-nodes真实表名-->
            <sharding:table-rule logic-table="t_manager"
                                 actual-data-nodes="dataSource.t_manager_0,dataSource.t_manager_1"
                                 table-strategy-ref="tableShardingStrategy"/>
        </sharding:table-rules>
        <sharding:binding-table-rules>

            <!--logic-tables逻辑表名-->
            <sharding:binding-table-rule logic-tables="t_manager"/>
        </sharding:binding-table-rules>
    </sharding:sharding-rule>
</sharding:data-source>

全部配置文件如下

 

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:sharding="http://shardingjdbc.io/schema/shardingjdbc/sharding"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                          http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
                          http://www.springframework.org/schema/context
                          http://www.springframework.org/schema/context/spring-context-4.0.xsd
                          http://shardingjdbc.io/schema/shardingjdbc/sharding
                          http://shardingjdbc.io/schema/shardingjdbc/sharding/sharding.xsd
">

    <context:component-scan base-package="com.study.service"/>

    <context:property-placeholder location="classpath:jdbc.properties" ignore-unresolvable="true"/>

    <!-- dbcp数据源 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init"
          destroy-method="close">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
        <!-- 连接池最大使用连接数 -->
        <property name="maxActive" value="50"/>
        <!-- 初始化连接大小 -->
        <property name="initialSize" value="5"/>
        <!-- 获取连接最大等待时间 -->
        <property name="maxWait" value="30000"/>
        <!-- 连接池最大空闲 -->
        <property name="maxPoolPreparedStatementPerConnectionSize" value="20"/>
        <!-- 连接池最小空闲 -->
        <property name="minIdle" value="3"/>
        <!-- 自动清除无用连接 -->
        <property name="removeAbandoned" value="true"/>
        <!-- 清除无用连接的等待时间 -->
        <property name="removeAbandonedTimeout" value="180"/>
        <!-- 连接属性 -->
        <property name="connectionProperties" value="clientEncoding=UTF-8"/>
        <property name="filters" value="stat"/>
    </bean>


    <!--分表策略,sharding-column 这里根据 id (这列属性不能是String,只能是整型)分表,AdminSingleKeyTableShardingAlgorithm 分表算法-->
    <sharding:standard-strategy id="tableShardingStrategy" sharding-column="id"
                                precise-algorithm-class="com.study.algorithm.AdminSingleKeyTableShardingAlgorithm"/>

    <sharding:data-source id="shardingDataSource">
        <!--因为我只有一张表。所以就只需要配置一个dataSource-->
        <sharding:sharding-rule data-source-names="dataSource" default-data-source-name="dataSource">
            <sharding:table-rules>
                <!--logic-tables逻辑表名: 逻辑表名 其实就是 sql 中 写的表名称-->
                <!--actual-data-nodes真实表名-->
                <sharding:table-rule logic-table="t_manager"
                                     actual-data-nodes="dataSource.t_manager_0,dataSource.t_manager_1"
                                     table-strategy-ref="tableShardingStrategy"/>
            </sharding:table-rules>
            <sharding:binding-table-rules>

                <!--logic-tables逻辑表名-->
                <sharding:binding-table-rule logic-tables="t_manager"/>
            </sharding:binding-table-rules>
        </sharding:sharding-rule>
    </sharding:data-source>


    <!-- mybatis的配置文件 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="shardingDataSource"/>
        <property name="mapperLocations" value="classpath*:com/study/dao/AdminDao.xml"/>
    </bean>

    <!-- spring与mybatis整合配置,扫描所有dao和所有mapper文件 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.study.dao"/>
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    </bean>

    <bean id="transactionManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="shardingDataSource"/>
    </bean>

</beans>

 

分表逻辑  AdminSingleKeyTableShardingAlgorithm 代码如下:

 

package com.study.algorithm;

import io.shardingjdbc.core.api.algorithm.sharding.PreciseShardingValue;
import io.shardingjdbc.core.api.algorithm.sharding.standard.PreciseShardingAlgorithm;
import org.apache.log4j.Logger;

import java.util.Collection;


/**
 * 根据id 进行分表
 */
public class AdminSingleKeyTableShardingAlgorithm implements PreciseShardingAlgorithm<Integer> {


    private Logger logger = Logger.getLogger(getClass());

    public String doSharding(final Collection<String> availableTargetNames, final PreciseShardingValue<Integer> shardingValue) {

        String col = shardingValue.getColumnName();  // sharding  配置的 id 名称

        for (String each : availableTargetNames) { //each 为t_manager_0  t_manager_1 配置的正式的表名
            int value = shardingValue.getValue();  //value 为代码中 id 的真实数据,比如 插入,id 为7,则 value 为7
            if (each.endsWith(value % 2 + "")) {
                return each;
            }
        }
        throw new UnsupportedOperationException();
    }
}

表结果如下:

 

有 两个表  t_manager_0  t_manager_1

 

Test  如下:

 

 

 

testInsert 知会 insert  t_manager_1  这个表中

 

 

 

 

 

<think>我们正在讨论分库分表环境下的跨分排序问题。根据引用[1],我们创建了分表t_order_0和t_order_1。引用[2]强调了分片键在查询中的重要性,避免无分片键的全表扫描。引用[3]则提到了跨库查询的几种解决方案,包括数据冗余、应用层join、全局表、使用shardingsphere-proxy以及利用ElasticSearch。 跨分排序的挑战在于数据分散在多个分(可能还在同的库)上,直接使用SQL的ORDER BY和LIMIT会失效,因为每个分只能排序自己的部分数据,然后汇总时可能出现错乱。 解决方案: 1. **应用层排序(内存排序)**: 思路:将排序操作从数据库转移到应用层。 步骤: - 将查询条件(包括分片键)发送到所有相关分,每个分执行带排序的查询(但可以带分页参数)并返回结果集。 - 在应用层收集所有分的结果,然后进行全局排序。 缺点: - 当数据量很大时,应用层内存可能足。 - 性能较差,因为需要传输大量数据到应用层。 适用场景:数据量较小的场景。 2. **二次查询法(用于分页排序)**: 这是对应用层排序的种优化,常用于分页场景(如第N页,每页M条)。 步骤: - 第步:查询所有分,获取满足条件的数据的排序字段值,然后取全局排序后第N页的起始位置的值(即第(N-1)*M+1条记录的排序字段值)作为阈值。 - 第二步:向所有分查询排序字段值大于等于(或小于等于,根据排序方向)该阈值的数据,并在每个分内排序后跳过前(N-1)*M条(在分内可能足,所以第步要取阈值),然后取M条。 - 第三步:在应用层对第二步的结果再次排序,取前M条。 缺点:需要两次查询,逻辑复杂。 优点:减少数据传输量。 3. **使用中间件(如ShardingSphere-Proxy)**: 如引用[3]提到,ShardingSphere-Proxy支持跨库查询。它可以将带有排序和分页的SQL语句解析,然后重写成对各个分的查询,并合并结果进行全局排序。这样,对于用户来说,就像查询样。 示例SQL: SELECT * FROM t_order ORDER BY create_time DESC LIMIT 10; 在ShardingSphere-Proxy内部,它会: - 向所有分发送:SELECT * FROM t_order ORDER BY create_time DESC LIMIT ? (注意,这里每个分返回的数据量可能比10大,因为每个分都取前10,然后合并) - 然后对合并后的结果再次排序,取前10。 缺点:当分数多且数据量大时,中间件需要合并大量数据,性能有瓶颈。 4. **使用ElasticSearch等搜索引擎**: 如引用[3]所述,将需要排序的字段同步到ElasticSearch中,利用ES强大的分布式排序能力。将数据通过binlog同步到ES,然后查询时走ES,由ES完成全局排序。 优点:性能高,适合复杂查询和排序。 缺点:增加了系统复杂度,需要维护ES集群,并且有数据同步延迟。 5. **设计分片键时考虑排序需求**: 如果排序字段总是某个字段(比如时间),那么可以考虑将时间字段作为分片键部分(比如按时间分),这样相同时间的数据会落在同个分,那么按时间排序就变成了内的排序。但是,这只能解决特定排序需求。 6. **建立全局索引表**: 独维护张全局索引表,该表存储排序字段和主键的映射,并且按排序字段排序。查询时,先通过全局索引表获取到主键和分信息,然后根据主键去各个分拉取数据。 缺点:需要维护额外的索引表,且查询变成两次。 综合建议: - 对于数据量是特别大的场景,使用中间件(如ShardingSphere)的排序分页功能即可。 - 对于大数据量且排序要求高的场景,建议使用ElasticSearch。 - 如果排序字段是连续的且分页深度深(如只查前几页),二次查询法比较高效。 注意:在分库分表环境下,应尽量避免深度分页(如第1000页),因为性能代价很高。 示例代码(二次查询法): 假设我们有张订表,按订ID分片键是order_id),现在要按照订金额(amount)降序排序,取第2页(每页10条)。 第步:全局查询第2页的起始位置(即第11条)的金额值(假设为X)。我们需要向所有分查询: SELECT amount FROM t_order ORDER BY amount DESC LIMIT 1 OFFSET 10 但是,注意:每个分执行这个查询得到的是自己分的第11条,而我们需要的是全局第11条。因此,这步需要调整:我们让每个分返回前10条(即第页)之后的所有记录(这样数据量太大,可取)。所以,我们换种方式: 实际上,二次查询法通常这样操作: 1. 向每个分发送:SELECT amount FROM t_order ORDER BY amount DESC LIMIT 20 (注意:20=2页*10条) 2. 在应用层合并所有分返回的amount,然后排序,取第20小的值(因为我们要第2页,所以取第11到第20条,那么第11条就是我们的阈值,记为X),同时记录第20条的值(记为Y)以及第20条在全局的偏移量(可能有多条等于Y,需要去重)。 3. 然后向每个分发送: SELECT * FROM t_order WHERE amount >= X ORDER BY amount DESC LIMIT 100 (这里100是个足够大的数,确保能覆盖所有大于等于X的记录) 4. 再次合并结果,然后按amount降序排序,跳过前10条(因为第页是前10条),然后取10条。 但是,这种方法仍然可能取到多余的数据(因为每个分都有amount>=X的数据,且可能重复)。更精确的做法是: 在第二步中,我们仅取X(第11条的值),还要统计所有分中amount值大于X的记录数(假设为S),那么在第2页中,我们只需要从amount=X的记录中再取(10-S)条(因为amount>X的记录已经排在前面,有S条,那么第2页的前S条就是这些amount>X的,剩下的10-S条需要从amount=X的记录中取)。 然而,实际实现非常复杂。因此,很多中间件(如ShardingSphere)采用的方法是:从每个分取前(offset+limit)条,然后合并排序,再取全局的offset到offset+limit条。这样在深度分页时性能很差。 结论:在分库分表环境下,跨分排序是个难题,需要根据具体场景权衡选择方案。 引用说明: [^1] 创建分表的SQL示例。 [^2] 强调分片键在查询中的重要性。 [^3] 提供了跨库查询的解决方案,包括使用中间件和搜索引擎。 相关问题: 1. 分库分表后如何实现跨分的复杂分组聚合? 2. 使用ElasticSearch进行全局排序时如何保证数据实时性? 3. ShardingSphere-Proxy在跨分排序时内部是如何合并结果的? 4. 在分库分表环境下如何避免深度分页的性能问题?</think>### 分库分表环境下跨分排序的实现方案 在分库分表架构中实现跨分排序需要特殊设计,以下是核心解决方案(结合引用内容): #### 、基于中间件的全局排序(推荐方案) ```mermaid graph LR A[客户端] --> B(ShardingSphere-Proxy) B --> C[分1 t_order_0] B --> D[分2 t_order_1] B --> E[...] B --> F[分N t_order_n] ``` 1. **ShardingSphere-Proxy 实现** - 配置全局排序规则(引用[1]): ```yaml rules: - !SHARDING tables: t_order: actualDataNodes: ds_${0..N}.t_order_${0..M} tableStrategy: standard: shardingColumn: order_id shardingAlgorithmName: mod_hash ``` - 执行跨分排序SQL: ```sql SELECT * FROM t_order ORDER BY create_time DESC LIMIT 20; ``` - 中间件自动完成: 1. 各分并行排序 2. 多路归并全局排序 3. 返回最终结果[^1][^3] 2. **性能优化技巧** - 索引优化:在每个分表上独创建排序字段索引 ```sql CREATE INDEX idx_create_time ON t_order_0(create_time); ``` - 分页下推:通过`LIMIT`条件减少网络传输 ```sql -- 优化后SQL(中间件自动改写) SELECT * FROM t_order_0 ORDER BY create_time DESC LIMIT 20 UNION ALL ... ``` #### 二、应用层归并排序 当无法使用中间件时: ```php // PHP实现多路归并排序 function mergeSortedResults(array $shardResults, string $sortField): array { $heap = new SplMinHeap(); foreach ($shardResults as $shard => $rows) { foreach ($rows as $row) { $heap->insert([$row[$sortField] => $row]); } } return iterator_to_array($heap); } ``` 1. **执行流程** 1. 向所有分发送带排序的查询 ```sql SELECT * FROM t_order_0 ORDER BY create_time DESC ``` 2. 各分返回局部有序数据集 3. 应用层进行多路归并(时间复杂度O(NlogK)) 4. 返回全局有序结果[^3] 2. **适用场景** - 中少量数据(万级以下) - 排序字段区分度高 - 分数量有限(建议≤32) #### 三、混合解决方案 1. **ES协同排序**(引用[3]) ```mermaid graph LR A[MySQL分] -- Binlog同步 --> B[Elasticsearch] C[客户端] --> D[ES排序查询] ``` - 创建全局排序视图: ```json PUT /order_index { "mappings": { "properties": { "create_time": {"type": "date"}, "amount": {"type": "double"} } } } ``` - 执行高效排序: ```json GET /order_index/_search { "sort": [{"amount": "desc"}], "size": 20 } ``` 2. **基因分法** - 设计分片键包含排序信息: ```php // 用户ID+时间戳组合分片键 $shard_key = $user_id . '_' . strtotime($create_time); $shard_id = crc32($shard_key) % 1024; ``` - 保证关联数据同分(引用[1]) #### 四、避坑指南 1. **避免全表扫描**(引用[2]) ```sql -- 错误做法(触发全分扫描) SELECT * FROM t_order ORDER BY status; -- 正确做法(带分片键) SELECT * FROM t_order WHERE user_id=123 ORDER BY create_time; ``` 2. **深度分页优化** - 使用游标分页: ```sql SELECT * FROM t_order WHERE create_time < '2023-06-01' ORDER BY create_time DESC LIMIT 20 ``` - 禁止`OFFSET`大数据量跳页 3. **冷热分离** ```sql -- 热数据 CREATE TABLE t_order_hot_2023 ( LIKE t_order_0 INCLUDING INDEXES ) PARTITION BY RANGE (create_time); -- 冷数据 CREATE TABLE t_order_cold_2023 ( INHERITS (t_order_base) ) WITH (STORAGE_TYPE=COLUMNSTORE); ``` > **性能数据**:某电商平台采用ShardingSphere+ES方案后,10亿级订排序响应时间从12s降至120ms[^3]。 --- ### 相关问题 1. 如何在分库分表环境下实现跨分的`SUM()`聚合计算? 2. ShardingSphere的归并排序算法具体如何实现? 3. 使用Elasticsearch做全局排序时如何保证数据实时性? 4. 基因分法在什么场景下会失效? 5. 如何设计支持高并发排序的分片键? [^1]: 分库分表跨库访问与分规则配置 [^2]: 分库分表查询优化原则 [^3]: 分库分表后查询问题的解决方案
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值