PostgreSQL分区表的介绍和实现

本文介绍了如何在SpringBoot项目中结合Mybatis-Plus利用PostgreSQL的声明式分区技术,包括添加依赖、配置数据源、实体类注解、自定义分区策略和注意事项,以优化大型数据表的查询性能和管理效率。
摘要由CSDN通过智能技术生成

什么是分区表

PostgreSQL数据库中分区(Partitioning) 是一种在数据库中管理大量数据的有效方法。通过将表分割成更小的、可管理的部分,可以提高查询性能、简化维护以及优化数据存储。
在这里插入图片描述

分区表的应用场景

PostgreSQL 分区表是将大型的逻辑表分割成小型的物理部分,这样可以提高查询性能,管理数据和维护效率。它的主要目的是为了提高大型表的管理和性能。使用场景:

  • 数据管理:将数据分散到不同的分区中,可以将数据分散到多个磁盘上,提高I/O性能。
  • 性能优化:查询、更新和删除等操作可以直接在对应的分区上执行,不需要扫描全表。
  • 数据维护:分区表可以方便地进行数据的备份、恢复和删除。

分区表的实现

PostgreSQL数据库有各式各样的分区实现方式。官方支持的有声明式分区和继承式分区,而三方插件包括pathman、partman等等。在官方声明式分区实现后,基本只推荐一种分区方式:声明式分区。由于再拓展不同实现的分区表的功能、细节、历史等差别会使篇幅过长,且未来意义不大,本篇主要讨论的是声明式分区,其他方式实现的分区功能只会简单介绍。但是由于历史存量和一些功能差异,了解声明分区、继承分区、pathman还是有必要的。

声明式分区

声明分区也叫原生分区,从PG10版本开始支持,相当于“官方支持”的分区表,也是最为推荐的分区方式。虽然与继承分区不一样,但是其内部也是用继承表实现的。声明分区只支持3种分区方式:range分区、list分区、hash分区

  • 范围分区(Range Partitioning):根据某个列的范围将数据分布到不同的分区中。适用于按照时间、数字范围等进行分区的场景。例如,大型日志表、股票交易表等。
  • 列表分区(List Partitioning):根据某个列的值将数据分布到不同的分区中。适用于按照离散值进行分区的场景。例如,按照地理位置、产品类别等进行分区的表。
  • 哈希分区(Hash Partitioning):根据哈希算法将数据均匀地分布到不同的分区中。适用于需要均匀地将数据分布到多个分区中的场景。例如,将数据分布到多个物理机器上,以提高查询性能和可伸缩性。

基于SpringBoot、Mybatis-Plus代码实现

在Spring Boot项目中,结合MyBatis-Plus和PostgreSQL,可以通过声明式分区实现更灵活和高效的数据管理。以下是关于Spring Boot、MyBatis-Plus和PostgreSQL声明式分区的详细介绍:

使用MyBatis-Plus的声明式分区

添加依赖

在项目的Maven或Gradle配置中添加MyBatis-Plus和PostgreSQL的相关依赖。

<!-- Maven 示例 -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.x.x</version>
</dependency>
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <version>版本号</version>
</dependency>
<!-- Sharding-Jdbc -->
<dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
    <version>4.1.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/druid-spring-boot-starter -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.2.21</version>
</dependency>

配置数据源

在application.properties或application.yml中配置PostgreSQL数据源。

spring:
  shardingsphere:
    datasource:
      names: db0
      db0:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: org.postgresql.Driver
        url: jdbc:postgresql://your-database-url
        username: your-username
        password: your-password
    sharding:
      tables:
        your-table-name:
          actual-data-nodes: iot0.your-table-name_${0..1}
          table-strategy:
            standard:
              sharding-column: partition-column
              precise-algorithm-class-name: your.package.xxxAlgorithm
实体类注解

在需要分区的实体类上添加相关注解

import com.baomidou.mybatisplus.annotation.TableName;

@TableName("my_partitioned_table")
public class MyEntity {
    // Entity fields and methods
}

自定义分区策略实现类

创建一个实现PreciseShardingAlgorithm接口的自定义分区策略类。

@Slf4j
public class BaseTestAlgorithm implements PreciseShardingAlgorithm<Integer> {

    @Override
    public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<Integer> shardingValue) {
        // 获取分片键的值
        Integer value = shardingValue.getValue();
        // 获取逻辑
        String logicTableName = shardingValue.getLogicTableName();
        log.info("分片键的值:{},逻辑表:{}", shardingValue, logicTableName);
        // 根据分表键的值计算分表名称的逻辑,返回实际数据表的名称
        String shardingRule = value % 2 + "";
        for (String each : availableTargetNames) {
            if (each.endsWith(shardingRule)) {
                return each;
            }
        }
        throw new IllegalArgumentException();
    }

}

实例分区策略

PostgreSQL的分区策略实例化配置。

@Configuration
public class ShardingConfig {

    @Bean
    public BaseTestAlgorithm baseTestAlgorithm() {
        return new BaseTestAlgorithm();
    }
}

自动创建表

创建好分表需要的表{0…1},代表有your_table_name_0,your_table_name_1两张表。也可以根据业务做自动创建表。

@Slf4j
public class ShardingAlgorithmTool {
    private static final HashSet<String> tableNameCache = new HashSet<>();
    /**
     * 判断 分表获取的表名是否存在 不存在则自动建表
     *
     * @param logicTableName  逻辑表名(表头)
     * @param resultTableName 真实表名
     * @return 确认存在于数据库中的真实表名
     */
    public static String shardingTablesCheckAndCreatAndReturn(String logicTableName, String resultTableName) {
        synchronized (logicTableName.intern()) {
            // 缓存中有此表 返回
            if (tableNameCache.contains(resultTableName)) {
                return resultTableName;
            }
            // 缓存中无此表 建表 并添加缓存
            List<String> sqlList = selectTableCreateSql(logicTableName);
            for (int i = 0; i < sqlList.size(); i++) {
                sqlList.set(i, sqlList.get(i).replace("CREATE TABLE", "CREATE TABLE IF NOT EXISTS").replace(logicTableName, resultTableName));
            }
            executeSql(sqlList);
            tableNameCache.add(resultTableName);
        }
        return resultTableName;
    }

    /**
     * 缓存重载方法
     */
    public static void tableNameCacheReload() {
        // 读取数据库中所有表名
        List<String> tableNameList = getAllTableNameBySchema();
        // 删除旧的缓存(如果存在)
        ShardingAlgorithmTool.tableNameCache.clear();
        // 写入新的缓存
        ShardingAlgorithmTool.tableNameCache.addAll(tableNameList);
    }


    private static void executeSql(List<String> sqlList) {
        Environment env = SpringUtil.getApplicationContext().getEnvironment();
        try (Connection conn = DriverManager.getConnection(Objects.requireNonNull(env.getProperty("spring.datasource.hikari.jdbc-url")), env.getProperty("spring.datasource.hikari.username"), env.getProperty("spring.datasource.hikari.password"))) {
            try (Statement st = conn.createStatement()) {
                conn.setAutoCommit(false);
                for (String sql : sqlList) {
                    st.execute(sql);
                }
                conn.commit();
            } catch (Exception ex) {
                conn.rollback();
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
    
    private static List<String> selectTableCreateSql(String tableName) {
        List<String> res = new ArrayList<>();
        if (tableName.equals("hss_history")) {
            res.add("CREATE TABLE `hss_history` (\n" +
                    "  `id` bigint unsigned NOT NULL,\n" +
                    "  `type_id` bigint unsigned NOT NULL DEFAULT '0' COMMENT '设备类型id',\n" +
                    "  `equipment_id` bigint unsigned NOT NULL COMMENT '设备id',\n" +
                    "  `data` json DEFAULT NULL COMMENT '原始数据',\n" +
                    "  `parse_data` json DEFAULT NULL COMMENT '解析数据',\n" +
                    "  `parse_time` bigint NOT NULL DEFAULT '0' COMMENT '解析时间',\n" +
                    "  `create_time` bigint NOT NULL DEFAULT '0',\n" +
                    "  PRIMARY KEY (`id`) USING BTREE) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='设备历史数据'");
            res.add("ALTER TABLE `hss_history` ADD INDEX n1 ( `create_time`, `equipment_id` ) USING BTREE");
            res.add("ALTER TABLE `hss_history` ADD INDEX n2 ( `equipment_id` ) USING BTREE");
            res.add("ALTER TABLE `hss_history` ADD INDEX n3 ( `parse_time` ) USING BTREE");
        }
        return res;
    }

    public static List<String> getAllTableNameBySchema() {
        List<String> res = new ArrayList<>();
        Environment env = SpringUtil.getApplicationContext().getEnvironment();
        try (Connection connection = DriverManager.getConnection(env.getProperty("spring.datasource.hikari.jdbc-url"), env.getProperty("spring.datasource.hikari.username"), env.getProperty("spring.datasource.hikari.password"));
             Statement st = connection.createStatement()) {
            try (ResultSet rs = st.executeQuery("show TABLES like 'hss_history%'")) {
                while (rs.next()) {
                    res.add(rs.getString(1));
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return res;
    }

    public static HashSet<String> cacheTableNames() {
        return tableNameCache;
    }
}

注意事项

  • 分区列必须是分区表的一部分,且必须是可比较类型(例如,整数、日期等)。
  • 分区列必须包含非空值,否则会导致分区操作失败。
  • 分区表的主键必须包含分区列,以便在执行查询时可以正确路由到相应的分区。
  • 分区表的索引必须也包含分区列,以便在执行查询时可以正确路由到相应的分区。
  • 分区表的触发器必须确保数据插入到正确的分区中,以避免数据插入到错误的分区中。

参考地址:
SharDingJDBC-5.1.0按月水平分表+读写分离,自动创表、自动刷新节点表

postgre数据库中分区的应用场景

PostgreSQL 支持分区表,它允许将表数据根据特定的条件拆分成多个子表,从而提高查询性能和管理数据。下面是一些关于 PostgreSQL 分区表的基本信息: 1. 分区表定义:在创建表时,可以使用 PARTITION BY 子句指定分区键。常见的分区键类型包括范围(range)、列表(list)和哈希(hash)。 2. 范围分区(Range partitioning):根据某个列的值范围进行分区,例如按时间范围、按数值范围等。可以使用 CREATE TABLE 语句的 PARTITION OF 子句定义每个分区。 3. 列表分区(List partitioning):根据某个列的值列表进行分区,例如按地区、按部门等。也可以使用 CREATE TABLE 语句的 PARTITION OF 子句定义每个分区。 4. 哈希分区(Hash partitioning):根据某个列的哈希值进行分区,通常用于数据平均分布的场景。使用 CREATE TABLE 语句的 PARTITION OF 子句定义每个分区。 5. 分区表管理:分区表可以通过 ALTER TABLE 添加或删除分区。还可以使用 EXCHANGE PARTITION 子句将数据从非分区表或已有分区中交换进入分区表。 6. 查询优化:PostgreSQL 的查询优化器会在执行查询时自动识别并只查询相关分区,从而提高查询性能。同时,可以通过查询约束来进一步减少查询的分区范围。 需要注意的是,分区表数据库中的使用需要根据具体的业务需求和数据特点来决定,同时需要合理设计和规划分区键,以及考虑数据维护和查询优化等方面的因素。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值