【文件增量备份系统】MySQL百万量级数据量分页查询性能优化

news2024/11/17 9:43:29

🎯 导读:本文针对大数据量下的分页查询性能问题进行了深入探讨与优化,最初查询耗时长达12秒,通过避免全表计数及利用缓存保存总数的方式显著提升了浅分页查询速度。面对深分页时依然存在的延迟,采用先查询倒数第N条记录ID,再依据此ID获取后继记录的策略,进一步降低了查询时间。此方案适用于优化大量数据背景下的分页展示性能问题。
🏠️ 项目仓库:数据增量备份系统
📙 项目介绍:【文件增量备份系统】系统功能介绍与开源说明

文章目录

  • 问题说明
  • 原因排查
  • total查询优化
    • 实现步骤
      • 在缓存类中添加一个原子类的备份文件总数属性
      • 实现一个更新缓存值的方法
      • 在项目启动成功之后,调用上面方法记录total值
      • 修改分页查询方法:在分页查询的时候,不要查询总数,总数从缓存中读取
      • 新增文件、删除文件时更新缓存值
    • 测试
  • 深分页问题优化
    • 问题说明
    • 优化实现
    • 测试
    • explain效率比较分析
  • 总结

问题说明

当数据量达到百万级时,查询性能已经非常慢了

在这里插入图片描述
经过查看日志,可以发现查询一次接口,耗时高达 两年半 5 秒,而且查的还是第一页,等查完数据,黄花菜都凉了,受不了一点,为了用户的体验,必须改进

原因排查

原始代码如下,对id进行降序排序是因为id是递增的,id越大,代表文件备份时间越新。对id进行排序是为了把最新备份的文件记录放在最前面

@Override
public PageResponse<BackupFile> pageBackupFileV1(BackupFileRequest request, boolean isOrder) {
    long start = System.currentTimeMillis();
    QueryWrapper<BackupFile> queryWrapper = new QueryWrapper<>();
    if (request.getBackupSourceId() != null) {
        queryWrapper.eq("backup_source_id", request.getBackupSourceId());
    }
    if (request.getBackupTargetId() != null) {
        queryWrapper.eq("backup_target_id", request.getBackupTargetId());
    }
    if (!StringUtils.isEmpty(request.getSourceFilePath())) {
        queryWrapper.like("source_file_path", request.getSourceFilePath());
    }
    if (!StringUtils.isEmpty(request.getTargetFilePath())) {
        queryWrapper.like("target_file_path", request.getTargetFilePath());
    }
    queryWrapper.orderByDesc("id");
    IPage<BackupFile> page = baseMapper.selectPage(new Page(request.getCurrent(), request.getSize()), queryWrapper);
    System.out.println("分页查询时间:" + (System.currentTimeMillis() - start) + "ms");
    return PageUtil.convert(page);
}

在这里插入图片描述

通过查看日志,发现在一次分页查询中,主要做两件事情:

  • 查询数据总条数
SELECT COUNT(*) AS total FROM backup_file
  • 进行真正的分页查询
SELECT id,backup_source_id,backup_target_id,source_file_path,target_file_path,backup_num,file_type,last_backup_time,file_name,file_suffix,file_length,file_length_after_compress,father_id,is_compress,is_contain_file,create_time,update_time FROM backup_file ORDER BY id DESC LIMIT 10

那上面慢的是哪个sql呢,还是说两个都慢,分别对两个sql进行单元测试

【查询数据总条数】

==>  Preparing: SELECT COUNT( * ) AS total FROM backup_file
==> Parameters:
<==    Columns: total
<==        Row: 3458533
<==      Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@521d455c]
时间:4093ms

【进行真正的分页查询】

==>  Preparing: SELECT id,backup_source_id,backup_target_id,source_file_path,target_file_path,backup_num,file_type,last_backup_time,file_name,file_suffix,file_length,file_length_after_compress,father_id,is_compress,is_contain_file,create_time,update_time FROM backup_file ORDER BY id DESC LIMIT 10
==> Parameters:
<==    Columns: id, backup_source_id, backup_target_id, source_file_path, target_file_path, backup_num, file_type, last_backup_time, file_name, file_suffix, file_length, file_length_after_compress, father_id, is_compress, is_contain_file, create_time, update_time
......
<==      Total: 10
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@327ac23]
时间:25ms

好家伙,原来慢的是查询总数。那为什么这么慢呢?
原因是 COUNT() 需要遍历整个表中的每一行来计算总行数(涉及大量的磁盘I/O操作,尤其是如果数据分布在多个磁盘块上时),因为行数多,所以慢

total查询优化

既然查询total那么久的话,怎么加速total查询呢,最方便的一个方法就是使用缓存。查询一次total就把它放到缓存中,当新增或修改数据时,再更新缓存

实现步骤

在缓存类中添加一个原子类的备份文件总数属性

import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;

/**
 * 缓存类
 *
 * @Author dam
 * @create 2024/2/19 19:57
 */
public class Cache {
    ......

    /**
     * 所备份文件的总数量
     */
    public static AtomicLong FILE_TOTAL_NUM = new AtomicLong();
}

实现一个更新缓存值的方法

/**
 * 更新缓存中的total值
 */
@Override
public void updateTotalCache() {
    Long total = baseMapper.selectCount(new QueryWrapper<BackupFile>().select("id"));
    FILE_TOTAL_NUM.set(total);
}

在项目启动成功之后,调用上面方法记录total值

import lombok.extern.slf4j.Slf4j;
import org.dam.service.BackupFileService;
import org.dam.service.BackupTaskService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

/**
 * @Author dam
 * @create 2024/1/25 19:29
 */
@Component
@Slf4j
public class BackupTaskInit implements CommandLineRunner {

    @Autowired
    private BackupTaskService backupTaskService;

    @Autowired
    private BackupFileService backupFileService;

    @Override
    public void run(String... args) throws Exception {
        log.info("项目启动成功,执行初始化,将没有完成的备份任务设置为失败状态");
        backupTaskService.updateNotFinishedTask();

        log.info("项目启动成功,更新备份文件总数缓存");
        backupFileService.updateTotalCache();
    }
}

修改分页查询方法:在分页查询的时候,不要查询总数,总数从缓存中读取

@Override
public PageResponse<BackupFile> pageBackupFileV2(BackupFileRequest request, boolean isOrder) {
    long start = System.currentTimeMillis();
    QueryWrapper<BackupFile> queryWrapper = new QueryWrapper<>();
    if (request.getBackupSourceId() != null) {
        queryWrapper.eq("backup_source_id", request.getBackupSourceId());
    }
    if (request.getBackupTargetId() != null) {
        queryWrapper.eq("backup_target_id", request.getBackupTargetId());
    }
    if (!StringUtils.isEmpty(request.getSourceFilePath())) {
        queryWrapper.like("source_file_path", request.getSourceFilePath());
    }
    if (!StringUtils.isEmpty(request.getTargetFilePath())) {
        queryWrapper.like("target_file_path", request.getTargetFilePath());
    }
    queryWrapper.orderByDesc("id");
    Page<BackupFile> page = new Page<>(request.getCurrent(), request.getSize());
    // 关闭总记录数统计
    page.setSearchCount(false);
    IPage<BackupFile> pageResult = baseMapper.selectPage(page, queryWrapper);
    List<BackupFile> backupFileList = pageResult.getRecords();

    PageResponse pageResponse = new PageResponse();
    pageResponse.setRecords(backupFileList);
    pageResponse.setCurrent(request.getCurrent());
    pageResponse.setSize(request.getSize());
    pageResponse.setTotal(Cache.FILE_TOTAL_NUM.get());

    System.out.println("分页查询时间:" + (System.currentTimeMillis() - start) + "ms");
    return pageResponse;
}

新增文件、删除文件时更新缓存值

由于该系统仅为个人使用,对缓存的时效性要求没有那么高,因此我只在备份结束的时候更新缓存值即可

/**
 * 根据备份任务来进行备份
 *
 * @param task                备份任务
 * @param ignoreFileList      忽略文件名列表
 * @param ignoreDirectoryList 忽略目录名列表
 */
private void backUpByTask(Task task, List<String> ignoreFileList, List<String> ignoreDirectoryList) throws IOException {
	......
    // 更新备份文件总数缓存
    backupFileService.updateTotalCache();
}

测试

查询第一页数据仅需要17ms,性能得到了飞一般的提升

在这里插入图片描述

你以为到这里就优化完了吗?不不不,随着分页的深度逐步加深,查询的速度会越来越慢,请继续阅读下面的深分页问题

深分页问题优化

问题说明

在这里插入图片描述
当查看最后一页数据时(数据量有3,459,110条),发现耗时竟然接近 8 秒,性能还是太差了。原因:我们默认的分页是使用offset来实现的,假设有10000条数据,当我们查询最后一页时,即使我们只需要10条数据,数据库也需要先检索出前面的99990条记录并丢弃它们,才能得到我们需要的结果,所以这个过程很慢

==>  Preparing: SELECT id,backup_source_id,backup_target_id,source_file_path,target_file_path,backup_num,file_type,last_backup_time,file_name,file_suffix,file_length,file_length_after_compress,father_id,is_compress,is_contain_file,create_time,update_time FROM backup_file ORDER BY id DESC LIMIT ? OFFSET ?
==> Parameters: 10(Long), 3459100(Long)
......
<==      Total: 10
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3fc32129]
分页查询时间:7712ms

优化实现

首先根据偏移量查询id

<select id="selectIDByOffset" resultType="java.lang.Long">
    select id
    from backup_file
    order by id desc limit #{offset}, 1
</select>

再根据查询到的 id 来取后面 size 条数据

// 将所有sql包裹在一个事务中执行,避免创建两次SqlSession。设置为只读事务,因为这里没有更新操作
@Transactional(readOnly = true)
@Override
public PageResponse<BackupFile> pageBackupFileV3(BackupFileRequest request, boolean isOrder) {
    long start = System.currentTimeMillis();
    request.setOffset((request.getCurrent() - 1) * request.getSize());
    Long idByOffset = baseMapper.selectIDByOffset((request.getCurrent() - 1) * request.getSize());
    QueryWrapper<BackupFile> queryWrapper = new QueryWrapper<>();
    if (request.getBackupSourceId() != null) {
        queryWrapper.eq("backup_source_id", request.getBackupSourceId());
    }
    if (request.getBackupTargetId() != null) {
        queryWrapper.eq("backup_target_id", request.getBackupTargetId());
    }
    if (!StringUtils.isEmpty(request.getSourceFilePath())) {
        queryWrapper.like("source_file_path", request.getSourceFilePath());
    }
    if (!StringUtils.isEmpty(request.getTargetFilePath())) {
        queryWrapper.like("target_file_path", request.getTargetFilePath());
    }
    queryWrapper.orderByDesc("id");
    queryWrapper.le("id", idByOffset);
    queryWrapper.last("LIMIT " + request.getSize());
    List<BackupFile> backupFileList = baseMapper.selectList(queryWrapper);

    PageResponse pageResponse = new PageResponse();
    pageResponse.setRecords(backupFileList);
    pageResponse.setCurrent(request.getCurrent());
    pageResponse.setSize(request.getSize());
    pageResponse.setTotal(Cache.FILE_TOTAL_NUM.get());

    System.out.println("分页查询时间:" + (System.currentTimeMillis() - start) + "ms");
    return pageResponse;
}

有读者可能有疑问。为什么要分两次查询,不直接用一个子查询sql来实现呢?(例如下面的代码)我测试了,发现浅分页的时候,查询的结果没有问题,深分页之后,查出来的数据和直接分页查询的数据对不上,不知道是不是我用了分表,对子查询产生了影响(有知道的大佬求求在评论区教教我,非常感谢)

select f1.id,
       f1.backup_source_id,
       f1.backup_target_id,
       f1.source_file_path,
       f1.target_file_path,
       f1.backup_num,
       f1.file_type,
       f1.last_backup_time,
       f1.file_name,
       f1.file_suffix,
       f1.file_length,
       f1.file_length_after_compress,
       f1.father_id,
       f1.is_compress,
       f1.is_contain_file,
       f1.create_time,
       f1.update_time
from backup_file f1
where (select id
       from backup_file
       order by id desc limit #{request.offset} , 1) >= id
order by f1.id desc
    limit #{request.size}

测试

经过测试,发现最后一页的查询时间为 3.4 s,又把时间减少了一半

Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4cbb45b9]
JDBC Connection [HikariProxyConnection@1178808009 wrapping org.apache.shardingsphere.driver.jdbc.core.connection.ShardingSphereConnection@2c08fcbd] will be managed by Spring
==>  Preparing: select id from backup_file order by id desc limit ?, 1
==> Parameters: 3459100(Long)
<==    Columns: id
<==        Row: 1760179379180195842
<==      Total: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4cbb45b9]
Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4cbb45b9] from current transaction
==>  Preparing: SELECT id,backup_source_id,backup_target_id,source_file_path,target_file_path,backup_num,file_type,last_backup_time,file_name,file_suffix,file_length,file_length_after_compress,father_id,is_compress,is_contain_file,create_time,update_time FROM backup_file WHERE (id <= ?) ORDER BY id DESC LIMIT 10
==> Parameters: 1760179379180195842(Long)
.....
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4cbb45b9]
分页查询时间:3492ms
Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4cbb45b9]
Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4cbb45b9]
Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4cbb45b9]

explain效率比较分析

通过单元测试,发现时间主要花费在根据偏移量查询id,后面根据偏移 id 来查询数据就很快了。

==> Parameters:
<==    Columns: id
<==        Row: 1760179379180195842
<==      Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@46b4d4e7]
id:1760179379180195842
查id时间:3169ms

Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3387d45e] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@1063860793 wrapping org.apache.shardingsphere.driver.jdbc.core.connection.ShardingSphereConnection@1ef7e4c7] will not be managed by Spring
==>  Preparing: select id, backup_source_id, backup_target_id, source_file_path, target_file_path, backup_num, file_type, last_backup_time, file_name, file_suffix, file_length, file_length_after_compress, father_id, is_compress, is_contain_file, create_time, update_time from backup_file where ? >= id order by id desc limit 10
==> Parameters: 1760179379180195842(Long)
......
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3387d45e]
查数据时间:27ms

【直接分页查询】

explain SELECT id,backup_source_id,backup_target_id,source_file_path,target_file_path,backup_num,file_type,last_backup_time,file_name,file_suffix,file_length,file_length_after_compress,father_id,is_compress,is_contain_file,create_time,update_time FROM backup_file_5 ORDER BY id DESC LIMIT 1000,10

在这里插入图片描述

  • 查询类型(type)为"index",这意味着MySQL正在执行全索引扫描。这通常意味着查询只访问索引树上的数据,而不需要回表获取其他列的信息。
  • possible_keys 列显示为空,表示没有指定任何可能使用的键。然而,key 列显示 PRIMARY,说明实际上使用了主键作为索引。
  • key_len 列值为8,表明在主键上使用了完整的索引长度。对于一个整数类型的主键来说,这通常是正确的。
  • ref 列显示为 NULL,这是因为在这个查询中没有涉及与其他表的关联操作。
  • rows 列显示预计需要读取的行数为10,010。这表明查询将遍历大约10,010个索引项来找到满足条件的数据。
  • Extra 列显示 “Backward index scan”,表示MySQL正在进行反向索引扫描。这通常发生在查询从高到低排序时,或者当查询优化器认为这样做更有效率时。

【根据偏移量查询id】

explain select id from backup_file_5 order by id desc limit 1000,1

在这里插入图片描述
从分析来看,很多指标和【直接分页查询】是一样的,区别是extra值为"Backward index scan; Using index" 表明正在进行反向索引扫描,并且只使用索引,无需回表查询原始数据

【根据偏移 id 来查询数据】

explain select id, backup_source_id, backup_target_id, source_file_path, target_file_path, backup_num, file_type, last_backup_time, file_name, file_suffix, file_length, file_length_after_compress, father_id, is_compress, is_contain_file, create_time, update_time from backup_file_5 where 7373278992159211536 >= id order by id desc limit 10

在这里插入图片描述

  • 查询类型(type)为"range",这意味着MySQL正在执行范围扫描。比全表扫描或全索引扫描要好
  • possible_keys 列显示为空,表示没有指定任何可能使用的键
  • key 列显示 PRIMARY,说明实际上使用了主键作为索引
  • key_len 列值为8,表明在主键上使用了完整的索引长度。对于一个整数类型的主键来说,这通常是正确的
  • ref 列显示为 NULL,这是因为在这个查询中没有涉及与其他表的关联操作
  • rows 列显示预计需要读取的行数为124。这表明查询将遍历大约124个索引项来找到满足条件的数据
  • Extra 列显示 “Using where; Backward index scan”,表示MySQL正在进行反向索引扫描,并应用了WHERE子句中的条件

【总结】

  • 根据偏移量查询id:相对于直接分页查询,只使用 id 来查询,数据量更小,且无需回表操作查询其他字段,消耗的时间和资源少
  • 根据偏移 id 来查询数据:只需要范围扫描,效率更高

总结

  • 查询效率有了比较大的提升
    • 查询第一页,查询时间从5秒下降到ms级别,性能有巨大提升
    • 查询最后一页数据,直接分页查询耗时12.5 秒,改进查询下降到3.4 s,性能提升 3.6 倍
  • 随着数据量的进一步提升,达到千万级,现在的实现方案在查询深分页时性能肯定会非常差,还需要进一步的优化。
  • 其他常用的效率优化逻辑
    • 冷热数据分离:将不常使用的数据迁移到其他数据库中
    • 使用游标分页:记录上一页的最后一条数据id,这样查下一页就很快了,缺点是只能上下页,无法随意切换页

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

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

相关文章

从信号量开始的里牛渴死生活

讲讲信号量 POSIX信号量 这个曾经在进程间通信提过一嘴但是没怎么细说&#xff0c;POSIX信号量和SystemV信号量都可用于同步达到无冲突的访问共享资源的目的&#xff0c;POSIX还可以用于线程间同步 初始化 #include <semaphore.h> int sem_init(sem_t *sem, int psh…

C++ 9.27

作业&#xff1a; 将之前实现的顺序表、栈、队列都更改成模板类 Stack #include <iostream> using namespace std; template <typename T> class Stack { private: T* arr; // 存储栈元素的数组 int top; // 栈顶索引 int capacity; // 栈的…

工程师 - Windows下使用WSL来访问本地的Linux文件系统

Access Linux filesystems in Windows and WSL 2 从 Windows Insiders 预览版构建 20211 开始&#xff0c;WSL 2 将提供一项新功能&#xff1a;wsl --mount。这一新参数允许在 WSL 2 中连接并挂载物理磁盘&#xff0c;从而使您能够访问 Windows 本身不支持的文件系统&#xff0…

【中医智慧解糖忧】血糖高?中医调理有妙招,自然平衡血糖不是梦!

在快节奏的现代生活中&#xff0c;高血糖已成为困扰许多人的健康难题。面对这一挑战&#xff0c;许多人第一时间想到的是西医的药物治疗&#xff0c;却往往忽略了中医这一博大精深的宝库。事实上&#xff0c;中医以其独特的理论体系和丰富的实践经验&#xff0c;在调理血糖方面…

千亿数据-异地容灾-查询打印——MySQL大数据优化

1. 数据备份策略 - 定期全量备份&#xff1a;制定周期性的全量数据备份计划&#xff0c;确保数据的完整性。 - 增量备份&#xff1a;在全量备份之间进行增量备份&#xff0c;减少备份时间和存储空间。 2. 数据存储 - 分布式存储&#xff1a;利用分布式存储系统来存…

极致cms使用多语言插件,如何修改默认主站语言为英文

jizhicms使用多语言插件&#xff0c;如何修改默认主站语言为英文 第一步&#xff1a;首先安装多语言插件 第二步配置多语言 第三步&#xff1a;极致cms默认的语言版本是中文的&#xff0c;需要修成英文要分三个步骤 1、修改前台&#xff0c;当url中不带语言的时候&#xff0c…

systemd使用入门

systemd负责管理整个操作系统&#xff0c;功能非常强大。我们在实际工作中&#xff0c;常常使用systemd来管理我们的服务。 &#xff08;1&#xff09;想要开机之后自动启动&#xff0c;从而不需要每次开机之后都手动启动进程&#xff0c;可以通过systemd来实现。 &#xff0…

酒水速送小程序开发制作方案

在餐饮娱乐领域&#xff0c;即时酒水配送服务逐渐成为市场新宠。开发一款集在线选购、快速配送、于一体的酒水配送小程序&#xff0c;以满足用户在家中、聚会场所或商业活动中即时获取各类酒水的需求&#xff0c;提升用户体验&#xff0c;拓宽酒水销售渠道。 目标用户 年轻消费…

JavaScript基础第一天

一、JavaScript简介 JavaScript&#xff08;简称“JS”&#xff09; 是一种具有函数优先的轻量级&#xff0c;解释型或即时编译型的编程语言。虽然它是作为开发Web页面的脚本语言而出名的&#xff0c;但是它也被用到了很多非浏览器环境中&#xff0c;JavaScript 基于原型编程、…

如何使用Kimi编写商品管理设计文档:包含流程图和用例图

如何使用Kimi编写商品管理设计文档&#xff1a;包含流程图和用例图 在软件开发项目中&#xff0c;商品管理系统是一个常见的需求。它涉及到商品的添加、编辑、删除、查询以及库存管理等功能。编写商品管理设计文档是确保项目顺利进行的关键步骤。以下是如何使用Kimi编写商品管…

青动CRM-E售后V2.0.4

CRM售后管理系统&#xff0c;旨在助力企业销售售后全流程精细化、数字化管理&#xff0c;主要功能&#xff1a;客户、合同、工单、任务、报价、产品、库存、出纳、收费&#xff0c;适用于&#xff1a;服装鞋帽、化妆品、机械机电、家具装潢、建材行业、快销品、母婴用品、办公用…

Vue 自定义组件实现 v-model 的几种方式

前言 在 Vue 中&#xff0c;v-model 是一个常用的指令&#xff0c;用于实现表单元素和组件之间的双向绑定。当我们使用原生的表单元素时&#xff0c;直接使用 v-model 是很方便的&#xff0c;但是对于自定义组件来说&#xff0c;要实现类似的双向绑定功能就需要一些额外的处理…

【JavaScript】搭建一个具有记忆的简洁个人待办网页

1. HTML 结构 文档类型声明&#xff1a;<!DOCTYPE html>这告诉浏览器这是一个 HTML5 文档。HTML 标签&#xff1a;<html lang"zh-CN">表示整个页面的内容&#xff0c;lang"zh-CN" 表示内容使用简体中文。头部信息&#xff1a;<head><…

RabbitMQ 队列之战:Classic 和 Quorum 的性能洞察

RabbitMQ 是一个功能强大且广泛使用的消息代理&#xff0c;它通过处理消息的传输、存储和交付来促进分布式应用程序之间的通信。作为消息代理&#xff0c;RabbitMQ 充当生产者&#xff08;发送消息的应用程序&#xff09;和使用者&#xff08;接收消息的应用程序&#xff09;之…

速查!2024 CSP-J/S第一轮认证成绩查询及晋级分数线

CSP-J/S 2024第一轮认证成绩已于9月27日13:30开放查询&#xff0c;认证者可登录NOI报名系统&#xff0c;在对应活动内查询个人成绩&#xff0c;CSP-J/S 2024第一轮J组得分为89分及以上的选手、S组得分为56分及以上的选手&#xff0c;可以直接晋级第二轮。 Scratch实验室根据NOI…

大模型微调方法(非常详细),收藏这一篇就够了!

引言 众所周知&#xff0c;大语言模型(LLM)正在飞速发展&#xff0c;各行业都有了自己的大模型。其中&#xff0c;大模型微调技术在此过程中起到了非常关键的作用&#xff0c;它提升了模型的生成效率和适应性&#xff0c;使其能够在多样化的应用场景中发挥更大的价值。 那么&…

数据特征工程:如何计算块熵?| 基于SQL实现

目录 0 信息量定义 1 块熵定义 2 问题描述 ​3 数据准备 4 问题分析 5 小结 0 信息量定义 信息量是指从N 个相等可能事件中选出一个事件所需要的信息度量或含量,也就是在辩识N 个事件中特定的一个事件的过程中所需要提问是或否的最少次数。 在一个系统中,等可能事件的数…

【技术文章】PostgreSQL分区表

引言 PostgreSQL作为一款高度可扩展的企业级关系型数据库管理系统&#xff0c;其内置的分区表功能在处理大规模数据场景中扮演着重要角色。本文将深入探讨PostgreSQL分区表的实现逻辑、详细实验过程&#xff0c;并辅以分区表相关的视图查询、分区表维护及优化案例&#xff0c;…

OpenStack Yoga版安装笔记(十四)启动一个实例

1、官方文档 OpenStack Installation Guidehttps://docs.openstack.org/install-guide/ 本次安装是在Ubuntu 22.04上进行&#xff0c;基本按照OpenStack Installation Guide顺序执行&#xff0c;主要内容包括&#xff1a; 环境安装 &#xff08;已完成&#xff09;OpenStack…

树脂法提纯牛胆汁

牛胆汁中除了含有胆酸外&#xff0c;还含有胆红素、胆固醇、卵磷脂、钠、钾、钙、镁、铁、锌等多种成分。这些成分对人体健康具有一定的益处&#xff0c;如胆红素具有抗氧化作用&#xff0c;卵磷脂有助于降低胆固醇&#xff0c;钠、钾、钙等矿物质对于维持人体正常生理功能也是…