EasyExcel处理Mysql百万数据的导入导出案例,秒级效率,拿来即用!

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 RDS PostgreSQL,集群系列 2核4GB
简介: 【5月更文挑战第11天】EasyExcel处理Mysql百万数据的导入导出案例,秒级效率,拿来即用!

一、写在开头

今天终于更新新专栏 《EfficientFarm》 的第二篇博文啦,本文主要来记录一下对于EasyExcel的高效应用,包括对MySQL数据库百万级数据量的导入与导出操作,以及性能的优化(争取做到秒级性能!)。


二、如何做技术选型

其实在市面上我们有很多常用的excel操作依赖库,除了EasyExcel之外,还有EasyPOI、JXL、JXLS等等,他们各有千秋,依赖重点不同,我们在做技术选型的时候,要根据自己的需求去做针对性选择,下面我们列举了这几种常见技术的特点对比

技术方案 优点 缺点
EasyExcel 简单易用,API设计友好;
高效处理大量数据;
支持自定义样式和格式化器等功能
不支持老版本 Excel 文件 (如 xls 格式)
POI Apache开源项目,稳定性高,EasyPOI基于它开发的,特点类似,进行了功能增强,这里不单独列举;
支持多种格式(XLS、XLSX等);
可以读写复杂表格(如带有合并单元格或图表的表格)
API使用较为繁琐;对于大数据量可能会存在性能问题
Jxls 具备良好的模板引擎机制,支持通过模板文件生成 Excel 表格;
提供了可视化设计器来快速创建报告模板
性能相对其他两个方案稍弱一些;
模板与代码耦合度较高。

而本文中主要针对的是大数据量的导入与导出,因此,我们果断的选择了EasyExcel技术进行实现。


三、应用场景模拟

假设我们在开发中接到了一个需求要求我们做一个功能:

1、导出商城中所有的用户信息,由于用户规模达到了百万级,导出等待时间不可太长
2、允许通过规定的excel模板进行百万级用户信息的初始化(系统迁移时会发生)。

拿到这个需求后,经过技术选型EasyExcel后,我们在心里有个大概的构想了,大概可以分三个内容 :“模板下载”、“上传数据”、“下载数据”

想好这些后,我们就可以开整了!✊✊✊


四、数据准备

在数据准备阶段,我们应该做如下几点:

1. 在数据库中创建一个用户信息表User;

-- 如果存在表先删除
drop table if exists `user`;
--建表语句
CREATE TABLE `user` (
  `id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
  `name` varchar(100) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '员工姓名',
  `phone_num` varchar(20) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '联系方式',
  `address` varchar(200) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '住址',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

2. 准备一个用户信息导入的初始化模板;

image.png

3. 模拟创造百万数据量在User表中;

这一点其实有2种方案,第一种就是在创造好的模板文件xlsx中,手工造出100万的数据,xlsx单个sheet页最大可创建104万行数据,刚刚好满足,如果用xls单个sheet还不可以,这种肯定相对麻烦,并且100万的数据有几十M,打开就已经很慢了;

另外一种方案,可以通过存储过程向MySQL中加入100w条数据,不过性能也不好,毕竟数据量太大,自己斟酌吧,sql贴出来(性能不好的电脑,不建议这么干,容易把软件跑崩):

DELIMITER //
drop procedure IF EXISTS InsertTestData;
CREATE PROCEDURE InsertTestData()
BEGIN
    DECLARE counter INT DEFAULT 1;

    WHILE counter < 1000000 DO
            INSERT INTO user (id, name, phone_num, address) VALUES
        (counter, CONCAT('name_', counter), CONCAT('phone_', counter), CONCAT('add_',counter)) ; 
            SET counter = counter + 1;
        END WHILE;
END //
DELIMITER;

-- 调用存储过程插入数据
CALL InsertTestData();

五、SpringBoot中配置EasyExcel

5.1 pom.xml中引入依赖

本次代码中一共用到了如下这些依赖,很多小伙伴本地若已经引入了,可以忽略!

<!--lombok依赖-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>
<!--MyBatis Plus依赖-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.0</version>
</dependency>
<!--easyexcel-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>3.3.4</version>
</dependency>
<!-- hutool -->
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.25</version>
</dependency>

5.2 创建实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
@ColumnWidth(25)
public class User {
   
    /**
     * 主键
     *
     * @mbg.generated
     */
    @ExcelProperty("id")
    private Integer id;

    /**
     * 员工姓名
     *
     * @mbg.generated
     */
    @ExcelProperty("姓名")
    private String name;

    /**
     * 联系方式
     *
     * @mbg.generated
     */
    @ExcelProperty("联系方式")
    private String phoneNum;

    /**
     * 住址
     *
     * @mbg.generated
     */
    @ExcelProperty("联系地址")
    private String address;

}

【注解说明】

  • @ExcelProperty:声明列名。
  • @ColumnWidth:设置列宽。也可以直接作用在类上。统一每一列的宽度

5.3 创建数据关系映射

UserMapper 文件

//*注:这里面继承了mybatis-plus的BaseMapper接口,供后面进行分页查询使用。*
public interface UserMapper extends BaseMapper<User> {
    int deleteByPrimaryKey(Integer id);

    int insertAll(User record);

    void insertSelective(@Param("list") List<User> list);

    User selectByPrimaryKey(Integer id);

    int updateByPrimaryKeySelective(User record);

    int updateByPrimaryKey(User record);
    Integer countNum();
}

UserMapper .xml文件

<?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="org.javaboy.vhr.mapper.UserMapper">
  <resultMap id="BaseResultMap" type="org.javaboy.vhr.pojo.User">
    <id column="id" jdbcType="INTEGER" property="id" />
    <result column="name" jdbcType="VARCHAR" property="name" />
    <result column="phone_num" jdbcType="VARCHAR" property="phoneNum" />
    <result column="address" jdbcType="VARCHAR" property="address" />
  </resultMap>
  <sql id="Base_Column_List">
    id, name, phone_num, address
  </sql>
  <select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
    select 
    <include refid="Base_Column_List" />
    from user
    where id = #{id,jdbcType=INTEGER}
  </select>
  <select id="countNum" resultType="java.lang.Integer">
    select count(*) from user
  </select>
  <delete id="deleteByPrimaryKey" parameterType="java.lang.Integer">
    delete from user
    where id = #{id,jdbcType=INTEGER}
  </delete>
  <insert id="insertAll" keyColumn="id" keyProperty="id" parameterType="org.javaboy.vhr.pojo.User" useGeneratedKeys="true">
    insert into user (name, phone_num, address
      )
    values (#{name,jdbcType=VARCHAR}, #{phoneNum,jdbcType=VARCHAR}, #{address,jdbcType=VARCHAR}
      )
  </insert>
  <insert id="insertSelective" parameterType="org.javaboy.vhr.pojo.User">
    insert into user
    (id,name, phone_num, address
      )
    values
    <foreach collection="list" item="item" separator=",">
        (#{item.id},#{item.name},#{item.phoneNum},#{item.address})
    </foreach>
  </insert>
  <update id="updateByPrimaryKeySelective" parameterType="org.javaboy.vhr.pojo.User">
    update user
    <set>
      <if test="name != null">
        name = #{name,jdbcType=VARCHAR},
      </if>
      <if test="phoneNum != null">
        phone_num = #{phoneNum,jdbcType=VARCHAR},
      </if>
      <if test="address != null">
        address = #{address,jdbcType=VARCHAR},
      </if>
    </set>
    where id = #{id,jdbcType=INTEGER}
  </update>
  <update id="updateByPrimaryKey" parameterType="org.javaboy.vhr.pojo.User">
    update user
    set name = #{name,jdbcType=VARCHAR},
      phone_num = #{phoneNum,jdbcType=VARCHAR},
      address = #{address,jdbcType=VARCHAR}
    where id = #{id,jdbcType=INTEGER}
  </update>
</mapper>

六、前端设计

前端页面采用Vue框架实现,咱们就按照上文中构想的那三点来设计就行,可以简单点实现,如果想要更加炫酷的前端样式,比如导入的文件格式校验,数据量提示等等,可以自行网上学习哈。

<template>
    <el-card>
        <div>
            <!--导入数据-->
            <el-upload
                    :show-file-list="false"
                    :before-upload="beforeUpload"
                    :on-success="onSuccess"
                    :on-error="onError"
                    :disabled="importDataDisabled"
                    style="display: inline-flex;margin-right: 8px"
                    action="/employee/excel/import">
                <!--导入数据-->
                <el-button :disabled="importDataDisabled" type="success" :icon="importDataBtnIcon">
                    {
   {
   importDataBtnText}}
                </el-button>
            </el-upload>
            <el-button type="success" @click="exportEasyExcel" icon="el-icon-download">
                导出数据
            </el-button>
            <el-button type="success" @click="exportExcelTemplate" icon="el-icon-download">
                导出模板
            </el-button>
        </div>
    </el-card>

</template>

<script>
    import {
   Message} from 'element-ui';
    export default {
   
        name: "Export",
        data() {
   
            return {
   
                importDataBtnText: '导入数据',
                importDataBtnIcon: 'el-icon-upload2',
                importDataDisabled: false,
            }
        },
        methods: {
   
            onError(res) {
   
                this.importDataBtnText = '导入数据';
                this.importDataBtnIcon = 'el-icon-upload2';
                this.importDataDisabled = false;
                console.log(res);
            },
            onSuccess(res) {
   
                this.importDataBtnText = '导入数据';
                this.importDataBtnIcon = 'el-icon-upload2';
                this.importDataDisabled = false;
                console.log(res.msg);
                if (res.msg == '文件导入成功'){
   
                    Message.success("文件导入完成")
                }
               // this.initEmps();

            },
            beforeUpload() {
   
                this.importDataBtnText = '正在导入';
                this.importDataBtnIcon = 'el-icon-loading';
                this.importDataDisabled = true;
            },
            exportEasyExcel() {
   
                window.open('/employee/excel/easyexcelexport', '_parent');
            },
            exportExcelTemplate(){
   
                window.open('/employee/excel/exporttemplate', '_parent');
            }
        }
    }

</script>

<style scoped>

</style>

效果如下:
image.png


七、导入导出实现

7.1 模板下载

1️⃣ 将准备好的用户信息模板.xlsx文件放入resource对应路径下。

image.png

2️⃣ 构建一个控制器类,用以接收导出模板、导入数据、导出数据的请求。

@RestController
@RequestMapping("/employee/excel")
@AllArgsConstructor
@Slf4j
public class EasyExcellController {
   
/**
     * 下载用户信息模板
     * @param response
     */
    @RequestMapping("/exporttemplate")
    public void downloadTemplate(HttpServletResponse response){
   
        try {
   
            //设置文件名
            InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("template/用户信息模板.xlsx");
            //设置头文件,注意文件名若为中文,使用encode进行处理
            response.setHeader("Content-disposition", "attachment;fileName=" +  java.net.URLEncoder.encode("用户信息模板.xlsx", "UTF-8"));
            //设置文件传输类型与编码
            response.setContentType("application/vnd.ms-excel;charset=UTF-8");
            OutputStream outputStream = response.getOutputStream();
            byte[] bytes = new byte[2048];
            int len;
            while((len = inputStream.read(bytes)) != -1){
   
                outputStream.write(bytes,0,len);
            }
            outputStream.flush();
            outputStream.close();
            inputStream.close();
        } catch (Exception e) {
   
            e.printStackTrace();
        }
    }

}

这部分代码中需要注意的是,如果你的模板是中文名字,需要加上java.net.URLEncoder.encode("用户信息模板.xlsx", "UTF-8")解决乱码问题。

7.2 导入数据

1️⃣ 在EasyExcellController类中增加导入数据的请求处理方法;

    @Autowired
    EasyExcelServiceImpl easyExcel;

 /**
 * 导入百万excel文件
  * @param file
  * @return
  */
 @RequestMapping("/import")
 public RespBean easyExcelImport(MultipartFile file){
   
     if(file.isEmpty()){
   
         return RespBean.error("文件不可为空");
     }
     easyExcel.easyExcelImport(file);
     return RespBean.ok("文件导入成功");
 }

代码中的RespBean是自己定义的一个响应工具类。

public class RespBean {
   
    private Integer status;
    private String msg;
    private Object obj;

    public static RespBean build() {
   
        return new RespBean();
    }

    public static RespBean ok(String msg) {
   
        return new RespBean(200, msg, null);
    }

    public static RespBean ok(String msg, Object obj) {
   
        return new RespBean(200, msg, obj);
    }

    public static RespBean error(String msg) {
   
        return new RespBean(500, msg, null);
    }

    public static RespBean error(String msg, Object obj) {
   
        return new RespBean(500, msg, obj);
    }

    private RespBean() {
   
    }

    private RespBean(Integer status, String msg, Object obj) {
   
        this.status = status;
        this.msg = msg;
        this.obj = obj;
    }

    public Integer getStatus() {
   
        return status;
    }

    public RespBean setStatus(Integer status) {
   
        this.status = status;
        return this;
    }

    public String getMsg() {
   
        return msg;
    }

    public RespBean setMsg(String msg) {
   
        this.msg = msg;
        return this;
    }

    public Object getObj() {
   
        return obj;
    }

    public RespBean setObj(Object obj) {
   
        this.obj = obj;
        return this;
    }
}

2️⃣ 在控制器中引入的easyExcel.easyExcelImport(file)方法中进行导入逻辑的实现。

@Service
@Slf4j
@AllArgsConstructor
public class EasyExcelServiceImpl implements EasyExcelService {
   

    private final ApplicationContext applicationContext;
    /**
     * excle文件导入实现
     * @param file
     */
    @Override
    public void easyExcelImport(MultipartFile file) {
   
        try {
   
            long beginTime = System.currentTimeMillis();
            //加载文件读取监听器
            EasyExcelImportHandler listener = applicationContext.getBean(EasyExcelImportHandler.class);
            //easyexcel的read方法进行数据读取
            EasyExcel.read(file.getInputStream(), User.class,listener).sheet().doRead();
            log.info("读取文件耗时:{}秒",(System.currentTimeMillis() - beginTime)/1000);
        } catch (IOException e) {
   
            log.error("导入异常", e.getMessage(), e);
        }
    }
}

这部分代码的核心是文件读取监听器:EasyExcelImportHandler。

3️⃣ 构建文件读取监听器

@Slf4j
@Service
public class EasyExcelImportHandler implements ReadListener<User> {
   
    /*成功数据*/
    private final CopyOnWriteArrayList<User> successList = new CopyOnWriteArrayList<>();
    /*单次处理条数*/
    private final static int BATCH_COUNT = 20000;
    @Resource
    private ThreadPoolExecutor threadPoolExecutor;
    @Resource
    private UserMapper userMapper;



    @Override
    public void invoke(User user, AnalysisContext analysisContext) {
   
        if(StringUtils.isNotBlank(user.getName())){
   
            successList.add(user);
            return;
        }
        if(successList.size() >= BATCH_COUNT){
   
            log.info("读取数据:{}", successList.size());
            saveData();
        }

    }

    /**
     * 采用多线程读取数据
     */
    private void saveData() {
   
        List<List<User>> lists = ListUtil.split(successList, 20000);
        CountDownLatch countDownLatch = new CountDownLatch(lists.size());
        for (List<User> list : lists) {
   
            threadPoolExecutor.execute(()->{
   
                try {
   
                    userMapper.insertSelective(list.stream().map(o -> {
   
                        User user = new User();
                        user.setName(o.getName());
                        user.setId(o.getId());
                        user.setPhoneNum(o.getPhoneNum());
                        user.setAddress(o.getAddress());
                        return user;
                    }).collect(Collectors.toList()));
                } catch (Exception e) {
   
                    log.error("启动线程失败,e:{}", e.getMessage(), e);
                } finally {
   
                    //执行完一个线程减1,直到执行完
                    countDownLatch.countDown();
                }
            });
        }
        // 等待所有线程执行完
        try {
   
            countDownLatch.await();
        } catch (Exception e) {
   
            log.error("等待所有线程执行完异常,e:{}", e.getMessage(), e);
        }
        // 提前将不再使用的集合清空,释放资源
        successList.clear();
        lists.clear();
    }

    /**
     * 所有数据读取完成之后调用
     * @param analysisContext
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
   
        //读取剩余数据
        if(CollectionUtils.isNotEmpty(successList)){
   
            log.info("读取数据:{}条",successList.size());
            saveData();
        }
    }
}

在这部分代码中我们需要注意两个问题,第一个是多线程,第二个是EasyExcel提供的ReadListener监听器。

第一个,由于我们在代码里采用了多线程导入,因此我们需要配置一个合理的线程池,以提高导入效率。

@Configuration
public class EasyExcelThreadPoolExecutor {
   

    @Bean(name = "threadPoolExecutor")
    public ThreadPoolExecutor easyExcelStudentImportThreadPool() {
   
        // 系统可用处理器的虚拟机数量
        int processors = Runtime.getRuntime().availableProcessors();
        return new ThreadPoolExecutor(processors + 1,
                processors * 2 + 1,
                10 * 60,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(1000000));
    }
}

第二个,对于ReadListener,我们需要搞清楚它提供的方法的作用。

  • invoke():读取表格内容,每一条数据解析都会来调用;
  • doAfterAllAnalysed():所有数据解析完成了调用;
  • invokeHead() :读取标题,里面实现在读完标题后会回调,本篇文章中未使用到;
  • onException():转换异常 获取其他异常下会调用本接口。抛出异常则停止读取。如果这里不抛出异常则 继续读取下一行,本篇文章中未使用到。

4️⃣ 导入100万数据量耗时测试

在做导入测试前,由于100万数据量的excel文件很大,所以我们要在application.yml文件中进行最大可上传文件的配置:

spring:
 servlet:
    multipart:
      max-file-size: 128MB        # 设置单个文件最大大小为10MB
      max-request-size: 128MB    # 设置多个文件大小为100MB

对100万数据进行多次导入测试,所损耗时间大概在500秒左右,8分多钟,这对于我们来说肯定无法接受,所以我们在后面针对这种导入进行彻底优化!
image.png

7.3 导出数据

1️⃣ 在EasyExcellController类中增加导出数据的请求处理方法;

/**
     * 导出百万excel文件
     * @param response
     */
    @RequestMapping("/easyexcelexport")
    public void easyExcelExport(HttpServletResponse response){
   
        try {
   
            //设置内容类型
            response.setContentType("text/csv");
            //设置响应编码
            response.setCharacterEncoding("utf-8");
            //设置文件名的编码格式,防止文件名乱码
            String fileName = URLEncoder.encode("用户信息", "UTF-8");
            //固定写法,设置响应头
            response.setHeader("Content-disposition", "attachment;filename="+ fileName + ".xlsx");
            Integer total = userMapper.countNum();
            if (total == 0) {
   
                log.info("查询无数据");
                return;
            }
            //指定用哪个class进行写出
            ExcelWriter build = EasyExcel.write(response.getOutputStream(), User.class).build();
            //设置一个sheet页存储所有导出数据
            WriteSheet writeSheet = EasyExcel.writerSheet("sheet").build();
            long pageSize = 10000;
            long pages = total / pageSize;
            long startTime = System.currentTimeMillis();
            //数据量只有一页时直接写出
            if(pages < 1){
   
                List<User> users = userMapper.selectList(null);
                build.write(users, writeSheet);
            }
            //大数据量时,进行分页查询写入
            for (int i = 0; i <= pages; i++) {
   
                Page<User> page = new Page<>();
                page.setCurrent(i + 1);
                page.setSize(pageSize);
                Page<User> userPage = userMapper.selectPage(page, null);
                build.write(userPage.getRecords(), writeSheet);
            }
            build.finish();
            log.info("导出耗时/ms:"+(System.currentTimeMillis()-startTime)+",导出数据总条数:"+total);
        } catch (Exception e) {
   
            log.error("easyExcel导出失败,e:{}",e.getMessage(),e);
        }
    }

由于数据量比较大,我们在这里采用分页查询,写入到一个sheet中,如果导出到xls格式的文件中,需要写入到多个sheet中,这种可能会慢一点。

且在Mybatis-Plus中使用分页的话,需要增加一个分页插件的配置

@Configuration
public class MybatisPlusPageConfig {
   
    /**
     * 新版分页插件配置
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
   
        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
        mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return mybatisPlusInterceptor;
    }
}

2️⃣ 百万数据量导出测试

经过多次测试发现,100万数据量平均导出耗时在40秒左右,在可以接受的范围内!

image.png

八、总结

以上就是SpringBoot项目下,通过阿里开源的EasyExcel技术进行百万级数据的导入与导出,不过针对百万数据量的导入,时间在分钟级别,这很明显不够优秀,但考虑到本文的篇幅已经很长了,我们在下一篇文章针对导入进行性能优化,敬请期待!

九、结尾彩蛋

如果本篇博客对您有一定的帮助,大家记得留言+点赞+收藏呀。原创不易,转载请联系Build哥!

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
28天前
|
安全 关系型数据库 MySQL
如何将数据从MySQL同步到其他系统
【10月更文挑战第17天】如何将数据从MySQL同步到其他系统
149 0
|
9天前
|
存储 Oracle 关系型数据库
【赵渝强老师】MySQL InnoDB的数据文件与重做日志文件
本文介绍了MySQL InnoDB存储引擎中的数据文件和重做日志文件。数据文件包括`.ibd`和`ibdata`文件,用于存放InnoDB数据和索引。重做日志文件(redo log)确保数据的可靠性和事务的持久性,其大小和路径可由相关参数配置。文章还提供了视频讲解和示例代码。
115 11
【赵渝强老师】MySQL InnoDB的数据文件与重做日志文件
|
9天前
|
缓存 NoSQL 关系型数据库
Redis和Mysql如何保证数据⼀致?
在项目中,为了解决Redis与Mysql的数据一致性问题,我们采用了多种策略:对于低一致性要求的数据,不做特别处理;时效性数据通过设置缓存过期时间来减少不一致风险;高一致性但时效性要求不高的数据,利用MQ异步同步确保最终一致性;而对一致性和时效性都有高要求的数据,则采用分布式事务(如Seata TCC模式)来保障。
43 14
|
12天前
|
SQL 前端开发 关系型数据库
SpringBoot使用mysql查询昨天、今天、过去一周、过去半年、过去一年数据
SpringBoot使用mysql查询昨天、今天、过去一周、过去半年、过去一年数据
44 9
|
24天前
|
SQL Java 关系型数据库
java连接mysql查询数据(基础版,无框架)
【10月更文挑战第12天】该示例展示了如何使用Java通过JDBC连接MySQL数据库并查询数据。首先在项目中引入`mysql-connector-java`依赖,然后通过`JdbcUtil`类中的`main`方法实现数据库连接、执行SQL查询及结果处理,最后关闭相关资源。
|
20天前
|
SQL 关系型数据库 MySQL
定时任务频繁插入数据导致锁表问题 -> 查询mysql进程
定时任务频繁插入数据导致锁表问题 -> 查询mysql进程
39 1
|
22天前
|
SQL 关系型数据库 MySQL
mysql数据误删后的数据回滚
【11月更文挑战第1天】本文介绍了四种恢复误删数据的方法:1. 使用事务回滚,通过 `pymysql` 库在 Python 中实现;2. 使用备份恢复,通过 `mysqldump` 命令备份和恢复数据;3. 使用二进制日志恢复,通过 `mysqlbinlog` 工具恢复特定位置的事件;4. 使用延迟复制从副本恢复,通过停止和重启从库复制来恢复数据。每种方法都有详细的步骤和示例代码。
|
24天前
|
关系型数据库 MySQL 数据库
一个 MySQL 数据库死锁的案例和解决方案
本文介绍了一个 MySQL 数据库死锁的案例和解决方案。
42 3
|
27天前
|
存储 关系型数据库 MySQL
基于案例分析 MySQL 权限认证中的具体优先原则
【10月更文挑战第26天】本文通过具体案例分析了MySQL权限认证中的优先原则,包括全局权限、数据库级别权限和表级别权限的设置与优先级。全局权限优先于数据库级别权限,后者又优先于表级别权限。在权限冲突时,更严格的权限将被优先执行,确保数据库的安全性与资源合理分配。
|
1月前
|
存储 关系型数据库 MySQL
面试官:MySQL一次到底插入多少条数据合适啊?
本文探讨了数据库插入操作的基础知识、批量插入的优势与挑战,以及如何确定合适的插入数据量。通过面试对话的形式,详细解析了单条插入与批量插入的区别,磁盘I/O、内存使用、事务大小和锁策略等关键因素。最后,结合MyBatis框架,提供了实际应用中的批量插入策略和优化建议。希望读者不仅能掌握技术细节,还能理解背后的原理,从而更好地优化数据库性能。