mysql 分库分表、 分区(partition)、sharding-sphere 综合整理

news2024/11/24 2:33:50

引言:

  • 一般情况下,如果单表数据量超过2000w的样子查询速度会很慢,因为内存无法存储其索引,使得之后的 SQL 查询会产生磁盘 IO,从而导致性能下降。
  • 解决方案:mysql 分区分表处理

分库分表:

  • 原因: 当数据量级达到一定规模,当并发过高,数据库连接数不够用时
  • 目的:将一个表拆成N个表,控制每张表的数据范围,保证SQL性能,建议数据量不超过500W

概念:

  • 库垂直拆分:按业务拆分库,不同业务不同数据库
  • 表锤子拆分:大表拆小表
  • 水平拆分:按维度标准,将数据分撒到各个表,离散存储; 或者分区,一个表按维度存储分割在不同的磁盘文件中,表还是一个。
  • 读写分离:数据的查询与数据的入库,单独分离开来
    • 这个需要mysql主从复制与 类似sharding-spere插件整合 实现

Mysql 分区实现:

  • 版本大于 mysql 5.1.3,分区后,表面上还是一张表,但数据散列到多个位置了。
  • 分区类型:
    • RANGE 分区,基于一个给定连续区间的列值,如日期、数值大小
    • LIST 分区,类型给定一个list数组,值包含 在改list 范围,则属于该分区
    • HASH 分区,指定分区数,对表的一个或多个列的Hash(Key)key对应列值为整数 进行计算,然后分配到不同分区。
    • KEY 分区,与hash分区类似,可以指定除BLOB和Text外其他类型的列作为分区键
  • 注意事项:
    • 做分区时,要么不定义主键,要么把分区字段加入到主键中
    • 分区字段不能为NULL

分区常用SQL :

-- 查询表分区
SELECT table_schema,table_name,partition_name,partition_ordinal_position,partition_method,partition_expression
 from information_schema.PARTITIONS
where table_schema = schema()
and table_name = '表名称'

-- 清除表分区,不清除数据
alter table dymc_from_input_info remove partitioning;

-- 定义 表 list 分区 ,表:dymc_from_input_info,字段:template_id
-- 这个业务是,当前表存储数据量很大,数据以模板id导入,可以按模板id分区,所有一个模板id一个分区
ALTER TABLE `dymc_from_input_info` PARTITION BY
LIST(template_id)(
    PARTITION p_1613559290235248642 VALUES IN (1613559290235248642),
    PARTITION p_1613910831752355842 VALUES IN (1613910831752355842),
    PARTITION p_1613910831752355843 VALUES IN (1613910831752355843)
);

-- 添加表list分区,在以有分区再添加list分区
alter table dymc_from_input_info add partition
(
PARTITION p_1 VALUES IN (1)
);

-- 添加 key分区,PARTITIONS 5, 是指定分区数为5
ALTER TABLE `dymc_from_input_info` PARTITION BY KEY(template_id) PARTITIONS 5;

-- 添加 hash分区
ALTER TABLE `dymc_from_input_info` PARTITION BY HASH(template_id) PARTITIONS 3;

-- 查询是否命中分区
EXPLAIN
select * from dymc_from_input_info where template_id=1613559290235248642

分区文件存储:

  • 如果存储引擎是InnoDB,.frm–表结构的文件,.ibd 表数据和索引的文件

分区前:

在这里插入图片描述

分区后:.ibd 文件,分两个分区;生成了2个 #p#分区名.ibd 文件,几个分区几个文件;分开存储

在这里插入图片描述
在这里插入图片描述

代码整合

@ApiModel(value = "mysql表分区Vo")
@Data
public class MysqlPartitionVo implements Serializable {
    private static final long serialVersionUID = -4548301443478563468L;

    @ApiModelProperty(value = "库名称")
    private String tableSchema;

    @ApiModelProperty(value = "表名称")
    private String tableName;

    @ApiModelProperty(value = "分区名称")
    private String partitionName;

    @ApiModelProperty(value = "分区位置")
    private String partitionOrdinalPosition;

    @ApiModelProperty(value = "分区类型")
    private String partitionMethod;

    @ApiModelProperty(value = "分区字段")
    private String partitionExpression;

}
public interface PartitionMapper {

    /**
     * 查询mysql 对应表-分区列表
     * @param tableName 表名称
     * @return List<MysqlPartitionVo>
     */
    List<MysqlPartitionVo> getPartitionByTable(@Param("tableName") String tableName);

    /**
     * 删除对应表分区,保留分区
     * @param tableName 表名称
     * @return Boolean
     */
    void removePartitionByTable(@Param("tableName") String tableName);

}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >

<mapper namespace="com.mapper.PartitionMapper">

    <update id="removePartitionByTable">
        alter table ${tableName} remove partitioning;
    </update>


    <select id="getPartitionByTable" resultType="com.vo.mysql.MysqlPartitionVo">
        SELECT table_schema,table_name,partition_name,partition_ordinal_position,partition_method,partition_expression
        from information_schema.PARTITIONS
        where table_schema = schema()
        and table_name = #{tableName}
    </select>

</mapper>
/**
 * @author xiaoshu
 * @description mysql 分区处理
 * @date 2023年01月15日 18:53
 */
public interface PartitionService {

    /**
     *  初始化分区 表:dymc_from_input_info
     */
    void checkPartition();

    /**
     * 查询该模板是否存在分区,没有则创建
     * @param templateId 模板id
     * @return Boolean
     */
    Boolean checkExistTemplateId(Long templateId);
}
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.constant.CommonConstants;
import com.mapper.DymcFromInputInfoMapper;
import com.mapper.PartitionMapper;
import com.service.PartitionService;
import com.vo.mysql.MysqlPartitionVo;
import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author xiaoshu
 * @description
 * @date 2023年01月15日 18:54
 */
@Slf4j
@Service
public class PartitionServiceImpl implements PartitionService {

    @Value("${spring.datasource.url}")
    private String url;
    @Value("${spring.datasource.username}")
    private String username;
    @Value("${spring.datasource.password}")
    private String password;

    @Resource
    private Redisson redisson;

    @Resource
    private PartitionMapper partitionMapper;

    @Resource
    private DymcFromInputInfoMapper dymcFromInputInfoMapper;

    private static final String tableName="dymc_from_input_info";

    //初始化定义分区sql
    private static final String sql =" ALTER TABLE `%s` PARTITION BY\n" +
            "LIST(%s)(\n" +
            " %s "+
            "); ";

    //添加分区sql
    private static final String add_sql =" ALTER TABLE `%s` add partition( %s )";

    private static final String partitionSql="PARTITION %s VALUES IN (%s)";

    @Override
    public void checkPartition() {
        RLock redissonLock = redisson.getLock(CommonConstants.REDIS_LOCK+"partitionTash");
        try {
            redissonLock.lock();
            List<Long> templateIds = dymcFromInputInfoMapper.getTemplateIds();
            if (CollectionUtil.isEmpty(templateIds)){
                log.info("表单列详情没有数据,不建立分区");
            }else {
                int requiredSize = templateIds.size();
                log.info("需要分区数量:"+ requiredSize);
                //分区列表
                List<MysqlPartitionVo> partitionByTable = partitionMapper.getPartitionByTable(tableName);
                if (CollectionUtil.isNotEmpty(partitionByTable)){
                    //查询表对应分区数量
                    List<MysqlPartitionVo> partitionVos = partitionByTable.stream().filter(e -> StrUtil.isNotEmpty(e.getPartitionName())).collect(Collectors.toList());
                    int actualSize = partitionVos.size();
                    log.info("实际分区数量:"+ actualSize);
                    //分区为空
                    if (CollectionUtil.isEmpty(partitionVos)){
                        //需要分区数量 > 实际分区数量
                        log.info("初始化分区");
                        //拼接分区sql
                        StringBuilder partitionSql = getPartitionSql(templateIds);
                        initPartition(partitionSql);
                    }else {
                        //分区不为空
                        if (requiredSize>actualSize){
                            //添加分区
                            Map<String, String> templateMap = partitionByTable.stream().collect(Collectors.toMap(MysqlPartitionVo::getPartitionName, MysqlPartitionVo::getPartitionOrdinalPosition));
                            templateIds.forEach(e->{
                                String partitionName="p_" + e;
                                String existFlag = templateMap.get(partitionName);
                                //不存在分区,模板id
                                List<Long> unPartitionTemplate = new LinkedList<>();
                                if (StrUtil.isEmpty(existFlag)){
                                    unPartitionTemplate.add(e);
                                }
                                if (CollectionUtil.isNotEmpty(unPartitionTemplate)){
                                    log.info("添加分区数量:"+unPartitionTemplate.size());
                                    //拼接分区sql
                                    StringBuilder partitionSql = getPartitionSql(unPartitionTemplate);
                                    addPartition(partitionSql);
                                }
                            });
                        }
                    }
                }
                //清空表分区
                //partitionMapper.removePartitionByTable(tableName);
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            redissonLock.unlock();
        }
    }

    @Override
    public Boolean checkExistTemplateId(Long templateId) {
        //分区列表
        try {
            List<MysqlPartitionVo> partitionByTable = partitionMapper.getPartitionByTable(tableName);
            if (CollectionUtil.isNotEmpty(partitionByTable)){
                //查询表对应分区数量
                List<MysqlPartitionVo> partitionVos = partitionByTable.stream().filter(e -> StrUtil.isNotEmpty(e.getPartitionName())).collect(Collectors.toList());
                //分区不为空
                if (CollectionUtil.isNotEmpty(partitionVos)){
                    log.info("当前分区数量:"+partitionVos.size());
                    //已有分区map
                    Map<String, String> templatePartitionMap = partitionByTable.stream().collect(Collectors.toMap(MysqlPartitionVo::getPartitionName, MysqlPartitionVo::getPartitionOrdinalPosition));
                    String partitionName = templatePartitionMap.get(String.valueOf(templateId));
                    //如果不存在分区
                    if (StrUtil.isEmpty(partitionName)){
                        partitionName="p_"+templateId;
                        log.info("该分区不存在:"+partitionName);
                        StringBuilder partitionSql = getPartitionSql(Collections.singletonList(templateId));
                        //添加分区
                        addPartition(partitionSql);
                    }
                }else {
                    //分区为空
                    String partitionName = "p_"+templateId;
                    log.info("该分区不存在:"+partitionName);
                    StringBuilder partitionSql = getPartitionSql(Collections.singletonList(templateId));
                    //初始化分区
                    initPartition(partitionSql);
                }
                return Boolean.TRUE;
            }
        }catch (Exception e){
            return Boolean.FALSE;
        }
        return Boolean.FALSE;
    }


    /**
     * 拼接分区sql
     * @param templateIds 待添加分区模板id列表
     * @return partitionSql
     */
    private synchronized StringBuilder getPartitionSql(List<Long> templateIds) {
        List<String> partitionSqls = new LinkedList<>();
        //拼接分区sql
        for (Long templateId : templateIds) {
            String partitionName = String.format(partitionSql, "p_" + templateId, templateId);
            partitionSqls.add(partitionName);
        }
        StringBuilder partitionSql= new StringBuilder();
        for (int i = 0; i < partitionSqls.size(); i++) {
            if (i!=partitionSqls.size()-1){
                partitionSql.append(partitionSqls.get(i)).append(",");
            }else {
                partitionSql.append(partitionSqls.get(i));
            }
        }
        return partitionSql;
    }

    /**
     * 定义表分区
     * @param partitionSql 分区sql
     */
    private synchronized void initPartition(StringBuilder partitionSql) {
        Connection connection;
        Statement statement;
        String executeSql = String.format(sql, tableName, "template_id", partitionSql);
        try {
            connection = DriverManager.getConnection(url, username, password);
            statement = connection.createStatement();
            statement.execute(executeSql);
            log.info("分区添加成功");
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    /**
     * 添加表分区
     * @param partitionSql 分区sql
     */
    private synchronized void addPartition(StringBuilder partitionSql) {
        Connection connection;
        Statement statement;
        String executeSql = String.format(add_sql, tableName,partitionSql);
        try {
            connection = DriverManager.getConnection(url, username, password);
            statement = connection.createStatement();
            statement.execute(executeSql);
            log.info("分区添加成功");
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

}

sharding-sphere 概念

  • Apache ShardingSphere 设计哲学为 Database Plus,旨在构建异构数据库上层的标准和生态
  • ShardingSphere-JDBC 定位为轻量级 Java 框架,在 Java 的 JDBC 层提供的额外服务
  • ShardingSphere-Proxy 定位为透明化的数据库代理端,通过实现数据库二进制协议,对异构语言提供支持
  • 官网: https://shardingsphere.incubator.apache.org/index_zh.html

流程图:

在这里插入图片描述

sharding-shpere 实现读写分离:

  • 实现读写分离,先需要配置 mysql的 主从复制 ,再通过 sharding-shpere 实现读写分离

mysql主从复制配置:

  • 主从复制(也称 AB 复制)允许将来自一个MySQL数据库服务器(主服务器)的数据复制到一个或多个MySQL数据库服务器(从服务器)。
  • 流程概念: 开启数据库二进制日志功能,数据操作记录( data changes ) -》 会存入 binary log 二进制文件中,通过TCP连接,上报到从库的 中继日志文件中 (Relay log),重新执行sql语句,实现数据同步。
    在这里插入图片描述

mysql 主从配置 以5.7.10为例:

  1. 修改主数据库配置文件,/etc/my.cnf ; 开启二进制日志功能,然后重启服务器
[mysqld]
#
# Remove leading # and set to the amount of RAM for the most important data
# cache in MySQL. Start at 70% of total RAM for dedicated server, else 10%.
# innodb_buffer_pool_size = 128M
#
# Remove leading # to turn on a very important data integrity option: logging
# changes to the binary log between backups.
# log_bin
#
# Remove leading # to set options mainly useful for reporting servers.
# The server defaults are faster for transactions and fast SELECTs.
# Adjust sizes as needed, experiment to find the optimal values.
# join_buffer_size = 128M
# sort_buffer_size = 2M
# read_rnd_buffer_size = 2M

## 同一局域网内注意要唯一
server-id=100  
## 开启二进制日志功能,可以随便取(关键)
log-bin=mysql-bin
## 复制过滤:不需要备份的数据库,不输出(mysql库一般不同步)
binlog-ignore-db=mysql
## 为每个session 分配的内存,在事务过程中用来存储二进制日志的缓存
binlog_cache_size=1M
## 主从复制的格式(mixed,statement,row,默认格式是statement)
binlog_format=mixed

validate_password_policy=0
validate_password_length=0

datadir=/var/lib/mysql
socket=/var/lib/mysql/mysql.sock

# Disabling symbolic-links is recommended to prevent assorted security risks
symbolic-links=0

log-error=/var/log/mysqld.log
pid-file=/var/run/mysqld/mysqld.pid
  1. 确定主从服务之间ping通,防火墙以放开
  2. 确保远程可访问
  3. mysql -uroot -p ,登录到数据库; show master status; - 查看二进制文件状态
    在这里插入图片描述
-- 修改远程访问 ,'%' 可为从服务器ip
use mysql;
update user set host = '%' where user = 'root';
FLUSH PRIVILEGES;
  1. 从mysql同意修改数据配置,确保 server-id 不一致
  2. 从服务器执行sql 绑定,主服务器。 master_log_filemaster_log_posshow master status;命令查看
change master to master_host='master服务器ip', master_user='root', 
master_password='master密码', master_port=3306, master_log_file='mysql-bin.000002',master_log_pos=2079;
  1. 查询同步状态 show slave status\G;
    在这里插入图片描述
    问题处理:
--建议处理:
stop slave;
--删除多余库
set global sql_slave_skip_counter=1;
start slave;
show slave status\G ;

代码整合:

<!-- <sharding-sphere.version>4.0.0-RC1</sharding-sphere.version> -->

		<!--依赖sharding-->
        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
            <version>${sharding-sphere.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>sharding-core-common</artifactId>
            <version>${sharding-sphere.version}</version>
        </dependency>

读写分离配置:

spring:
  #读写分离配置
  shardingsphere:
    # 参数配置,显示sql
    props:
      sql:
        show: true
    # 配置数据源
    datasource:
      # 给每个数据源取别名,下面的ds1,ds2,ds3任意取名字
      names: master,slave
      # 给master-ds1每个数据源配置数据库连接信息
      master:
        # 配置druid数据源
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://192.168.1.21:3306/sharding-test?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT
        username: root
        password: 123456
        maxPoolSize: 100
        minPoolSize: 5
      # 配置slave
      slave:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3306/sharding-test?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT
        username: root
        password: 730730
        maxPoolSize: 100
        minPoolSize: 5
    # 配置默认数据源ds1
    sharding:
      # 默认数据源,主要用于写,注意一定要配置读写分离 ,注意:如果不配置,那么就会把三个节点都当做从slave节点,新增,修改和删除会出错。
      default-data-source-name: master
    # 配置数据源的读写分离,但是数据库一定要做主从复制
    masterslave:
      # 配置主从名称,可以任意取名字
      name: ms
      # 配置主库master,负责数据的写入
      master-data-source-name: master
      # 配置从库slave节点
      slave-data-source-names: slave
      # 配置slave节点的负载均衡均衡策略,采用轮询机制
      load-balance-algorithm-type: round_robin

整合成功:

在这里插入图片描述

查询走从库

在这里插入图片描述

写入走主库

在这里插入图片描述

配置数据分片:

  • 分表概念:如对users进行分表处理,则 users逻辑表,实际不存在;
    在这里插入图片描述
  • 按照yml 配置的规则,最后命中 users_1、或者users_2表

yml配置

  • 表达式说明:ds$->{0..1} ,ds 代码上面命名的数据库别名,分片表达式的值命中,则对应 $->{0..N}范围的值
spring:
  #数据分片配置
  shardingsphere:
    # 参数配置,显示sql
    props:
      sql:
        show: true
    # 配置数据源
    datasource:
      # 给每个数据源取别名,下面的ds1,ds2,ds3任意取名字
      names: ds0,ds1
      # 给master-ds1每个数据源配置数据库连接信息
      ds0:
        # 配置druid数据源
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://192.168.1.21:3306/sharding-test?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT
        username: root
        password: 123456
        maxPoolSize: 100
        minPoolSize: 5
      # 配置ds2-slave
      ds1:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3306/sharding-test?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT
        username: root
        password: 730730
        maxPoolSize: 100
        minPoolSize: 5
    # 配置默认数据源ds1
    sharding:
      # 默认数据源,主要用于写,注意一定要配置读写分离 ,注意:如果不配置,那么就会把三个节点都当做从slave节点,新增,修改和删除会出错。
      default-data-source-name: ds0
      # 配置分表的规则
      tables:
        # users 逻辑表名
        users:
          # 数据节点:数据源$->{0..N}.逻辑表名$->{0..N}
          actual-data-nodes: ds$->{0..1}.users$->{0..1}
          # 拆分库策略,也就是什么样子的数据放入放到哪个数据库中。
          database-strategy:
            inline:
              sharding-column: age    # 分片字段(分片键)
              algorithm-expression: ds$->{age % 2} # 分片算法表达式
          # 拆分表策略,也就是什么样子的数据放入放到哪个数据表中。
          table-strategy:
            inline:
              sharding-column: age    # 分片字段(分片键)
              algorithm-expression: users$->{age % 2} # 分片算法表达式

提交数据 age=20时,命中 ds0
在这里插入图片描述
提交数据 age=21时,命中 ds1在这里插入图片描述

标准分片,如按日期分;则需要 implements PreciseShardingAlgorithm

spring:
  #读写分离配置
  shardingsphere:
    # 参数配置,显示sql
    props:
      sql:
        show: true
    # 配置数据源
    datasource:
      # 给每个数据源取别名,下面的ds1,ds2,ds3任意取名字
      names: ds0,ds1
      # 给master-ds1每个数据源配置数据库连接信息
      ds0:
        # 配置druid数据源
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://192.168.1.21:3306/sharding-test?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT
        username: root
        password: 123456
        maxPoolSize: 100
        minPoolSize: 5
      # 配置ds2-slave
      ds1:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3306/sharding-test?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT
        username: root
        password: 730730
        maxPoolSize: 100
        minPoolSize: 5
    # 配置默认数据源ds1
    sharding:
      # 默认数据源,主要用于写,注意一定要配置读写分离 ,注意:如果不配置,那么就会把三个节点都当做从slave节点,新增,修改和删除会出错。
      default-data-source-name: ds0
      # 配置分表的规则
      tables:
        # users 逻辑表名
        users:
          # 数据节点:数据源$->{0..N}.逻辑表名$->{0..N}
          actual-data-nodes: ds$->{0..1}.users$->{0..1}
          # 拆分库策略,也就是什么样子的数据放入放到哪个数据库中。
          database-strategy:
            standard:
              sharding-column: birthday    # 分片字段(分片键)
              preciseAlgorithmClassName: com.config.BirthdayAlgorithm
          # 拆分表策略,也就是什么样子的数据放入放到哪个数据表中。
          table-strategy:
            inline:
              sharding-column: age    # 分片字段(分片键)
              algorithm-expression: users$->{age % 2} # 分片算法表达式

编写具体规则:

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/168574.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【匠心打造】从0打造uniapp 可视化拖拽设计 c_o 第六篇

1、这个版本的变化是左侧增加了布局设计和包资源管理器 包资源管理器&#xff1a;eclipse的特称&#xff0c;左侧的项目管理。和hbuildx左侧类似 项目的整体设计结构如下: v1.0 普通模式&#xff1a;支持新建前端项目&#xff0c;拖拽&#xff0c;且生成前端项目&#xff08…

基于“遥感+”蓝碳储量估算、红树林信息提取实践技术应用与科研论文写作

目录 “遥感”助推蓝碳生态系统碳储量调查简介 第一章 高光谱遥感数据介绍及预处理 第二章 光谱特征分析与参量提取 第三章 高光谱遥感数据分类与制图 第四章 GEE数据处理介绍 第五章 碳储量时空变化与预测 大气温室气体浓度不断增加&#xff0c;导致气候变暖加剧&#x…

DFS的树上应用

目录 一、前言 二、树上的DFS 1、树的重心 2、树的重心例题 3、树的直径 4、树的直径例题 &#xff08;1&#xff09;做两次DFS 三、拓扑排序与DFS 1、用DFS解拓扑排序 2、欧拉路与DFS 3、用DFS输出一个欧拉回路 一、前言 本文主要讲了树上的DFS、树的重心、树的直…

538. 把二叉搜索树转换为累加树

538. 把二叉搜索树转换为累加树 难度中等 给出二叉 搜索 树的根节点&#xff0c;该树的节点值各不相同&#xff0c;请你将其转换为累加树&#xff08;Greater Sum Tree&#xff09;&#xff0c;使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。 提醒一下&am…

关于浮点数使用的两个注意事项(C/C++)

目录 一.回顾浮点数的存储与读取 二.浮点数使用的第一个注意事项 三.浮点数使用的第二个注意事项 附&#xff1a; 观察内存中的FLT_MAX和FLT_MIN 一.回顾浮点数的存储与读取 http://t.csdn.cn/oVwte 浮点数的存入与读取流程总览&#xff1a; 二.浮点数使用的第一个注意事…

理解实现八大排序

目录 一、初步认识 二、直接插入排序 三、希尔排序 四、直接选择排序 五、堆排序 六、冒泡排序 七、快速排序 7.1 Hoare版本 7.2 挖坑法 7.3 前后指针法 7.4 非递归 7.5 优化方法 7.5.1 三数取中 7.5.2 小区间优化 八、归并排序 九、计数排序 一、初步认识 排…

Netty入门

二. Netty 入门 1. 概述 1.1 Netty 是什么&#xff1f; Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients.Netty 是一个异步的、基于事件驱动的网络应用框架&…

简单开发网站+HTML标签

目录 一、学习路线 二、快速开发网站 1、简单demo 2、浏览器能识别的标签 ① 编码② Title ③ 标题 ④ div和span ⑤ 超链接 ⑥ 图片⑦ 列表 ⑧ 表格 ⑨ input系列 ⑩ 下拉框 ⑪ 多行文本 三、网络请求 四、案例 1、用户注册 2、用户登录 五、小结 1、学习标签的总…

网易互客CRM 微盟系统 管易系统 金蝶系统对接集成整体解决方案

前言&#xff1a;大部分的企业都可能只用一套系统组织架构复杂&#xff0c;业务流程繁琐&#xff0c;内部同时有CRM系统、OMS系统、ERP系统......且各个系统都需要独立登陆&#xff0c;造成IT部门数据监管困难&#xff01;如何在同一套中台系统上关联多管理系统呢&#xff1f;系…

【GD32F427开发板试用】-03-定时器1 的不算坑的坑和时钟设置

本篇文章来自极术社区与兆易创新组织的GD32F427开发板评测活动&#xff0c;更多开发板试用活动请关注极术社区网站。作者&#xff1a;申小林 先说一下我使用定时器1 的时候吧&#xff0c;最开始我以为定时器1是挂在APB1上的&#xff0c;随意按照惯性思维&#xff0c;定时器的时…

vue3使用svg图标多种方式

方式1使用在线链接访问 在iconfont找到自己的项目的图标选择Symbol获取在线链接 2&#xff1a;在vue3项目中找到public的index.html进行script进行引入 打开浏览器看&#xff1a;这样就会自动注入到body下 在项目直接使用 //控制图标的大小<svg style"width: 10px; …

SAP ABAP 函数组组件缺失检查

有没有遇到如下几个场景 场景1 开发1&#xff0c;新建函数组1&#xff0c;创建函数1 开发2&#xff0c;在函数组1里&#xff0c;创建函数2 两者都传Q测试&#xff0c;开发2的先QAT完后发布生产&#xff0c;请求dump&#xff0c;找不到函数2 场景2 函数组1已传生产 开发1&#x…

编解码-性能优化-SIMD

文章目录前言MMXSSEAVX使用内置函数使用SSE/AVX命名规则SSE/AVX操作类别实战汇编使用优化前代码详解优化后代码详解引用文章#mermaid-svg-cWLDz5Rki1i4TgZ1 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#fff;}#mermaid-svg-cWLDz5Rki…

【JavaGuide面试总结】MySQL篇·中

【JavaGuide面试总结】MySQL篇中1.MySQL 的隔离级别是基于锁实现的吗&#xff1f;2.表级锁和行级锁了解吗&#xff1f;有什么区别&#xff1f;3.共享锁和排他锁简单说说4.意向锁有什么作用&#xff1f;5.InnoDB 有哪几类行锁&#xff1f;6.当前读和快照读有什么区别&#xff1f…

Go语言循环语句

Go语言循环语句 资料参考至菜鸟教程。 在不少实际问题中有许多具有规律性的重复操作&#xff0c;因此在程序中就需要重复执行某些语句。 以下为大多编程语言循环程序的流程图&#xff1a; Go语言提供了以下几种类型循环处理语句&#xff1a; 循环类型描述for循环重复执行语句块…

Base64

概述 Base64是一种基于64个字符的编码算法,经过Base64编码后的数据会比原始数据略长,为原来的4/3倍。经Base64编码后的字符串的字符数是以4为单位的整数倍。 编码表 即64个字符分别是: 字符个数A-Z26a-z260-910+1/1=用于补位 在电子邮件中,每行为76个字符,每行末需添加一…

【青训营】Go的依赖管理

Go的依赖管理 本节内容来自于&#xff1a;字节跳动青年训练营第五届 后端组 1.什么是依赖 实际开发的工程需要使用许多第三方库&#xff0c;这能够使得我们站在巨人的肩膀上&#xff0c;使用第三方库中封装好的函数&#xff0c;可以大大方便我们的程序开发&#xff0c;第三方…

Microsoft Teams上的编程教育

内容提示&#xff1a;Microsoft Teams上的 MakeCode Arcade 使用形式&#xff1a;Microsoft Teams中的 “作业” 服务 应用场景&#xff1a;编程教育 社团活动 个人经验&#xff1a;在校期间&#xff0c;每周学校都会有社团活动&#xff0c;学生们根据自己的兴趣爱好来选择社…

struct 结构体的内存对齐

&#x1f499;作者&#xff1a;阿润菜菜 &#x1f4d6;专栏&#xff1a;一起学习C言 本文目录 在内存中观察 结构体内存对齐的规则&#xff1a; 为什么存在内存对齐&#xff1f; 编程中我们该如何设计结构体&#xff1f; 修改默认对齐数 相关笔试题 在内存中观察 首先…

el-date-picker 目前只能通过点击input输入框触发日期选择器,怎么通过其他方式触发日期选择器同时把input输入框去掉,如点击按钮

依然是该模块由于后端接口数据传输限制 在前面文章里做了些许改动。 需求左右切换 可以快速找到年份&#xff0c;于是添加了element选择年份的日期组件。 图中隐藏了el-data-picker日期组件&#xff0c;手动添加样式展示时间栏选择的数据进行 0 回显。 点击时间时&#xff0c;…