什么,你的EasyExcel导出一万条数据就OOM了?

news2024/12/27 17:53:42

前言

前段时间在做一个导出的功能,本以为是平平无奇的一个功能。就用公司内部的一个导出工具类三下五除二就写完了,做法是直接查全量数据,然后直接往Excel里写。一开始没多少数据也没什么问题,但是当数据量逐渐多了起来后,达到一万多条,导出的时候就会报OOM。然后我就换成了阿里开源的EasyExcel,但是导出的时候也不太稳定,偶尔也会OOM。所以应该是数据量太大了,在写入的时候把内存占满了。然后我就放弃了查全量数据一次性写入Excel的做法,采用分页查询,分批次写入Excel的方式,果然不会出现OOM了。

虽然这种方式不会出现OOM,但是每次导出都写一遍重复的代码着实有点麻烦,所以结合自己平时的使用场景,封装了一个EasyExcel的导出工具类,这样只要在分页查询的基础上写少量的代码,就可以实现分批次写入Excel,简化代码的编写并且解决OOM的问题。

实现

@Slf4j
public abstract class EasyExcelExport<T, S> {

    /**
     * EasyExcel导出Excel表格,每个sheet默认最大10万条数据
     *
     * @param fileName  excel文件前缀名
     * @param sheetName 表页名
     */
    public void easyExcelBatchExport(String fileName, String sheetName, HttpServletResponse response) {
        this.easyExcelBatchExport(fileName, sheetName, 100000, response);
    }

    /**
     * 分批次导出excel数据
     *
     * @param fileName  excel文件前缀名
     * @param sheetSize 每个sheet的数据量,默认10万,excel有限制不能大于1048576
     * @param sheetName 表页名
     */
    public void easyExcelBatchExport(String fileName, String sheetName, Integer sheetSize, HttpServletResponse response) {
        fileName = fileName + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) + ".xlsx";
        int currentSheet = 1;   // 当前处于第几个sheet
        int totalLine = 0;      // 总共写入的条数
        int currentBatch = 1;   // 当前写入excel的批次(第几页)
        int lineNum = 1;        // 行号,当前写入的是第几条数据

        long startTime = System.currentTimeMillis();
        try {
            response.setCharacterEncoding("utf-8");
            // 告诉浏览器用什么软件可以打开此文件
            response.setHeader("content-Type", "application/vnd.ms-excel");
            // 下载文件的默认名称
            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "utf-8"));

            ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream(), (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]).build();
            WriteSheet sheet = EasyExcel.writerSheet(sheetName).build();

            while (true) {
                // 获取数据,然后currentBatch+1,下次调用就会获取新的数据
                List<S> sourceDataList = getData(currentBatch);
                currentBatch++;

                List<T> exportEntityList = new ArrayList<>();
                if (CollUtil.isNotEmpty(sourceDataList)) {
                    totalLine += sourceDataList.size();
                    log.info("EasyExcel开始写入第{}批数据,当前批次数据大小为{}", currentBatch - 1, sourceDataList.size());
                    for (S sourceData : sourceDataList) {
                        exportEntityList.add(convertSourceData2ExportEntity(sourceData, lineNum));
                        lineNum++;

                        // 当前sheet数据已经到达最大值,将当前数据全写入当前sheet,下一条数据就会写入新sheet
                        if (lineNum > sheetSize) {
                            excelWriter.write(exportEntityList, sheet);
                            exportEntityList.clear();
                            lineNum = 1;
                            currentSheet++;
                            sheet = EasyExcel.writerSheet(sheetName + currentSheet).build();
                        }
                    }

                    // 写入excel
                    excelWriter.write(exportEntityList, sheet);
                } else {
                    // 未获取到数据,结束
                    break;
                }
            }
            excelWriter.finish();
        } catch (Exception e) {
            log.error("EasyExcel导出异常", e);
        }

        log.info("EasyExcel导出数据结束,总数据量为{},耗时{}ms", totalLine, (System.currentTimeMillis() - startTime));
    }

    /**
     * 不分批次导出excel。一次性获取所有数据写入excel,确定数据量不大时可以使用该方法,数据量过大时使用分批次导出,否则会OOM
     *
     * @param fileName  excel文件前缀名
     * @param sheetName 表页名
     */
    public void easyExcelExport(String fileName, String sheetName, HttpServletResponse response) {
        fileName = fileName + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) + ".xlsx";
        int totalLine = 0;      // 总共写入的条数
        int lineNum = 1;        // 行号,当前写入的是第几条数据

        long startTime = System.currentTimeMillis();
        try {
            response.setCharacterEncoding("utf-8");
            // 告诉浏览器用什么软件可以打开此文件
            response.setHeader("content-Type", "application/vnd.ms-excel");
            // 下载文件的默认名称
            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "utf-8"));

            List<S> sourceDataList = getData(1);
            List<T> exportEntityList = new ArrayList<>();
            if (CollUtil.isNotEmpty(sourceDataList)) {
                totalLine += sourceDataList.size();
                log.info("EasyExcel开始写入数据,数据大小为{}", sourceDataList.size());
                for (S sourceData : sourceDataList) {
                    exportEntityList.add(convertSourceData2ExportEntity(sourceData, lineNum));
                    lineNum++;
                }
            }
            response.setCharacterEncoding("utf-8");
            // 告诉浏览器用什么软件可以打开此文件
            response.setHeader("content-Type", "application/vnd.ms-excel");
            // 下载文件的默认名称
            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "utf-8"));
            EasyExcel.write(response.getOutputStream(), (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]).sheet(sheetName).doWrite(exportEntityList);
        } catch (Exception e) {
            log.error("EasyExcel导出异常", e);
        }

        log.info("EasyExcel导出数据结束,总数据量为{},耗时{}ms", totalLine, (System.currentTimeMillis() - startTime));
    }

    /**
     * 将原数据对象转换为需要导出的目标对象
     *
     * @param sourceData 原对象
     * @param lineNum    行号
     */
    public abstract T convertSourceData2ExportEntity(S sourceData, Integer lineNum);

    /**
     * 获取原始数据,通过currentBatch参数分页获取数据。
     *
     * @param currentBatch 获取第几批(页)数据,通过该参数分页查询,每次调用自动递增。不分批次导出时可以忽略该参数
     */
    public abstract List<S> getData(int currentBatch);

}

首先,这是EasyExcelExport是一个抽象类,指定了泛型 TS,T是target目标类,也就是导出时对应的类,S是source原对象所对应的类。

EasyExcelExport里还有两个抽象方法,getData()convertSourceData2ExportEntity() 。这两个方法是需要在平时使用时自己去实现的,getData是数据查询的方法,可以在这里面去实现分页查询的逻辑,currentBatch参数是用来控制分页查询页码的,从1开始,会自动递增。如果确定数据量不大不需要分批次导出的话,那么getData()里只需要进行普通的查询即可,忽略currentBatch参数不用分页查询。还有一个方法是convertSourceData2ExportEntity(),这个是用来将对象S转为对象T的方法,因为从数据库查询或者是从其他地方获取到的对象类型可能是S,而导出时需要的对象类型是T,所以通过该方法进行对象转换。

最核心的是 easyExcelBatchExport() 方法,里面有一个while循环,while循环里首先会去调用getData()方法获取数据,然后将currentBatch加1便于下次获取数据,接下来有个for循环去进行对象的转换并添加到exportEntityList集合中,这个集合中装的是最终写到Excel里的对象。当转换完成后就将当前批次的数据写入Excel中,然后进行下一次循环,当getData()方法未获取到数据时,就结束循环。

同时支持指定每个sheet页的最大行数。在对对象进行转换时有一个判断,当前sheet页的数据是否到达指定值,到达后,直接写入excel,然后新建一个sheet页,这样新的数据就会写入新的sheet页。

使用

那么如何使用这个工具类呢。很简单,只要new出EasyExcelExport的对象,然后实现一下 convertSourceData2ExportEntity() 方法和 getData() 方法即可,然后再根据需要去调用不同的导出方法即可。导出方法有指定和不指定sheet数据页大小的分批写入方法 easyExcelBatchExport() 和不分批次直接一次性写入的 easyExcelExport() 方法。

下面通过一个小案例展示一下。假设现在有个导出用户列表的需求,数据库User表对应的是UserPO类:

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserPO {

    private Long id;

    /**
     * 用户编号
     */
    private String code;

    /**
     * 姓名
     */
    private String name;

    /**
     * 手机号码
     */
    private String phone;

    /**
     * 性别。1-男,2-女
     */
    private Integer sex;

}

导出对应的类是UserExportEntity:

@Data
public class UserExportEntity {

    @ColumnWidth(10)
    @ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER, verticalAlignment = VerticalAlignmentEnum.CENTER)
    @ExcelProperty(index = 0, value = "序号")
    private Integer line;

    @ColumnWidth(35)
    @ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER, verticalAlignment = VerticalAlignmentEnum.CENTER)
    @ExcelProperty(index = 1, value = "用户编号")
    private String code;

    @ColumnWidth(35)
    @ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER, verticalAlignment = VerticalAlignmentEnum.CENTER)
    @ExcelProperty(index = 2, value = "姓名")
    private String name;

    @ColumnWidth(35)
    @ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER, verticalAlignment = VerticalAlignmentEnum.CENTER)
    @ExcelProperty(index = 3, value = "手机号码")
    private String phone;

    @ColumnWidth(10)
    @ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER, verticalAlignment = VerticalAlignmentEnum.CENTER)
    @ExcelProperty(index = 4, value = "性别")
    private String sexStr;

    @ColumnWidth(10)
    @ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER, verticalAlignment = VerticalAlignmentEnum.CENTER)
    @ExcelProperty(index = 5, value = "fieldA")
    private String fieldA;

    @ColumnWidth(10)
    @ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER, verticalAlignment = VerticalAlignmentEnum.CENTER)
    @ExcelProperty(index = 6, value = "fieldB")
    private String fieldB;

    @ColumnWidth(10)
    @ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER, verticalAlignment = VerticalAlignmentEnum.CENTER)
    @ExcelProperty(index = 7, value = "fieldC")
    private String fieldC;

    @ColumnWidth(10)
    @ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER, verticalAlignment = VerticalAlignmentEnum.CENTER)
    @ExcelProperty(index = 8, value = "fieldD")
    private String fieldD;

    @ColumnWidth(10)
    @ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER, verticalAlignment = VerticalAlignmentEnum.CENTER)
    @ExcelProperty(index = 9, value = "fieldE")
    private String fieldE;

    @ColumnWidth(10)
    @ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER, verticalAlignment = VerticalAlignmentEnum.CENTER)
    @ExcelProperty(index = 10, value = "fieldF")
    private String fieldF;

    @ColumnWidth(10)
    @ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER, verticalAlignment = VerticalAlignmentEnum.CENTER)
    @ExcelProperty(index = 11, value = "fieldG")
    private String fieldG;

    @ColumnWidth(10)
    @ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER, verticalAlignment = VerticalAlignmentEnum.CENTER)
    @ExcelProperty(index = 12, value = "fieldH")
    private String fieldH;

    @ColumnWidth(10)
    @ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER, verticalAlignment = VerticalAlignmentEnum.CENTER)
    @ExcelProperty(index = 13, value = "fieldI")
    private String fieldI;

}

先测试一下不分批次导出,导出123456条数据。

    @GetMapping("/testExport")
    public void testExport(HttpServletResponse response) {
        new EasyExcelExport<UserExportEntity, UserPO>() {
            @Override
            public UserExportEntity convertSourceData2ExportEntity(UserPO sourceData, Integer lineNum) {
                UserExportEntity entity = new UserExportEntity();
                entity.setLine(lineNum);
                entity.setCode(sourceData.getCode());
                entity.setName(sourceData.getName());
                entity.setPhone(sourceData.getPhone());
                entity.setSexStr(Objects.equals(sourceData.getSex(), 1) ? "男" : Objects.equals(sourceData.getSex(), 2) ? "女" : StrUtil.EMPTY);
                return entity;
            }

            @Override
            public List<UserPO> getData(int currentBatch) {
                List<UserPO> userPOList = new ArrayList<>();
                // 模拟查询数据库,假设每次查询会查出123456条数据
                for (int i = 0; i < 123456; i++) {
                    userPOList.add(UserPO.builder()
                            .code("USER_" + RandomUtil.randomString("1234567890", 6))
                            .name(RandomUtil.randomString("qwertyuiopasdfghjklzxcvbnm", 10))
                            .phone("138" + RandomUtil.randomString("1234567890", 8))
                            .sex(RandomUtil.randomInt(1, 3))
                            .build());
                }
                log.info("userPOList-->{}", JSONUtil.toJsonStr(userPOList));
                return userPOList;
            }
        }.easyExcelExport("测试不分批次导出", "测试不分批次导出", response);
    }

为了更清晰地看到效果,我将内存大小限制为128M。

调用一下测试接口,可以看到,导出十几万条数据时发生了OOM。

再来看看分批次导出的效果,模拟一下分页查询,假设有200页数据,每页8888条,一共是170多万条数据。

    @GetMapping("/testBatchExport")
    public void testBatchExport(HttpServletResponse response) {
        new EasyExcelExport<UserExportEntity, UserPO>() {
            @Override
            public UserExportEntity convertSourceData2ExportEntity(UserPO sourceData, Integer lineNum) {
                UserExportEntity entity = new UserExportEntity();
                entity.setLine(lineNum);
                entity.setCode(sourceData.getCode());
                entity.setName(sourceData.getName());
                entity.setPhone(sourceData.getPhone());
                entity.setSexStr(Objects.equals(sourceData.getSex(), 1) ? "男" : Objects.equals(sourceData.getSex(), 2) ? "女" : StrUtil.EMPTY);
                return entity;
            }

            @Override
            public List<UserPO> getData(int currentBatch) {
                // 模拟分页查询,假设数据库中有200页数据
                if (currentBatch <= 200) {
                    List<UserPO> userPOList = new ArrayList<>();
                    // 模拟查询数据库,假设每次查询会查出8888条数据
                    for (int i = 0; i < 8888; i++) {
                        userPOList.add(UserPO.builder()
                                .code("USER_" + RandomUtil.randomString("1234567890", 6))
                                .name(RandomUtil.randomString("qwertyuiopasdfghjklzxcvbnm", 10))
                                .phone("138" + RandomUtil.randomString("1234567890", 8))
                                .sex(RandomUtil.randomInt(1, 3))
                                .build());
                    }
                    return userPOList;
                } else {
                    return new ArrayList<>();
                }
            }
        }.easyExcelBatchExport("测试分批次导出", "测试分批次导出", response);
    }

通过分批次写入Excel的方式,成功导出了170多万条数据,相较于不分批次导出,效果显而易见。而且通过调用工具类的方式,进一步简化了导出时代码的编写。

OK!本次的分享就到这里!

如果您觉得文章还不错,请给我来个点赞收藏关注

学习更多编程知识,微信扫描下方二维码关注公众号『 R o b o d 』:

code

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

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

相关文章

【OpenCV • c++】图像平滑处理(2) —— 方框滤波 | 盒滤波 | 源码分析

文章目录 前言一、方框滤波代码演示 二、源码分析 前言 前文我们了解了什么是图像平滑处理、图像滤波、邻域算子与线性邻域滤波、以及如何使用方框滤波&#xff0c;本文我们来分析一下方框滤波的源码。 一、方框滤波 void boxFilter(InputArray src, OutputArray dst, int ddep…

面试常见 | 项目上没有亮点,如何包装?

很多技术人在公司用的老技术&#xff0c;而且很多都是搬业务代码且做枯燥乏味的CRUD&#xff0c;在面试提交简历或做自我介绍的时候并不突出&#xff0c;这种情况&#xff0c;如何破局&#xff1f; 首先不管你做的啥项目&#xff0c;全世界不可能只有你自己在做&#xff0c;比…

Android Handler用法

Android Handler用法 为什么要设计Handler机制&#xff1f;Handler的用法1、创建Handler2、Handler通信2.1 sendMessage 方式2.2 post 方式 Handler常用方法1、延时执行2、周期执行 HandlerThread用法主线程-创建Handler子线程-创建Handler FAQMessage是如何创建主线程中Looper…

Agent AI智能体的未来

未来社会中的智能使者&#xff1a;Agent AI智能体的可能性与挑战 随着科技的迅速进步&#xff0c;人工智能已深入我们生活的各个领域&#xff0c;而Agent AI智能体作为与人工智能紧密相关的一个分支&#xff0c;其未来发展无疑是值得期待的。Agent AI智能体&#xff0c;或称为…

JAVA Coding 规范

Coding 规范 文章目录 Coding 规范一.文件规范1.1 声明1.2 缩进1.3 空行1.4 空格1.5 对齐1.6 小括号1.7 花括号1.8 代码长度 二.命名规范2.1 命名总则2.2 命名空间2.3 类与接口2.4 方法命名2.5 属性命名2.6 常量命名2.7 变量命名 三.语句规范3.1 语句总则3.2 循环语句3.3 Switc…

【note3】linux驱动基础,

文章目录 1.互斥锁和自旋锁选择&#xff1a;自旋锁&#xff08;开销少&#xff09;的自旋时间和被锁住的代码执行时间成正比关系2.linux错误码&#xff1a;64位错误指针指向内核空间最后一页&#xff0c;对于 1.互斥锁和自旋锁选择&#xff1a;自旋锁&#xff08;开销少&#x…

QT:核心控件-QWidget

文章目录 控件enableobjectNamegeometrysetWindowTitleopacitycursorFonttooltipstyleSheet 控件 什么是控件&#xff1f; 如上所示&#xff0c;就是控件&#xff0c;而本篇要做的就是对于这些控件挑选一些比较有用的常用的进行讲解分析 在QT的右侧&#xff0c;会有对应的空间…

【面试经典 150 | 分治】将有序数组转换为二叉搜索树

文章目录 写在前面Tag题目来源解题思路方法一&#xff1a;中序遍历递归建树 写在最后 写在前面 本专栏专注于分析与讲解【面试经典150】算法&#xff0c;两到三天更新一篇文章&#xff0c;欢迎催更…… 专栏内容以分析题目为主&#xff0c;并附带一些对于本题涉及到的数据结构等…

如何有效地合并和分类多个文件目录?

在现代办公环境中&#xff0c;文件管理和组织是至关重要的。随着科技的发展&#xff0c;我们不再仅仅依赖于纸质文件&#xff0c;而是更多地使用电子设备来存储和管理信息。这种转变带来了一些新的挑战&#xff0c;其中之一就是如何有效地合并和分类多个目录文件。一&#xff0…

Sortable 拖拽行实现el-table表格顺序号完整例子,vue 实现表格拖拽行顺序号完整例子

npm install sortable<template><vxe-modalref"modalRef"v-model"showModal"title"详情"width"70vw"height"60vh"class"his"transfer><el-table ref"tableRef" :data"tableData&q…

树莓派5用docker运行Ollama3

书接上回&#xff0c;树莓派5使用1panel安装 Ollama 点击终端就可以进入容器 输入以下代码 ollama run llama3Llama3 是市场推崇的版本。您的 树莓派5上必须至少有 4.7GB 的可用空间&#xff0c;因此用树莓派玩机器学习就必须配置大容量的固态硬盘。用1panel部署网络下载速度…

4G远程温湿度传感器在农业中的应用—福建蜂窝物联网科技有限公司

解决方案 农业四情监测预警解决方案 农业四情指的是田间的虫情、作物的苗情、气候的灾情和土壤墒情。“四情”监测预警系统的组成包括管式土壤墒情监测站、虫情测报灯、气象站、农情监测摄像机&#xff0c;可实时监测基地状况,可以提高监测的效率和准确性&#xff0c;为农业生…

“Unite“ > MacOS下很不错的网站转应用App的工具

前言 前不久在浏览mac论坛&#xff0c;无意了解到一款非常好的工具&#xff0c;可以将网站转换为app&#xff0c;考虑到我们现在的主要应用都从本地客户端转成web形式使用&#xff0c;但基于本能的使用习惯&#xff0c;还是希望有个快捷的访问信息&#xff0c;这个应用非常适合…

202012青少年软件编程(Python)等级考试试卷(一级)

第 1 题 【单选题】 运行下方代码段&#xff0c;输出是6&#xff0c;则输入的可能是&#xff08; &#xff09;。 a eval(input())print(a)A :8%2 B :8/2 C :3*2 D :3**2 正确答案:C 试题解析: 第 2 题 【单选题】 关于Python变量&#xff0c;下列叙述正确的是&#x…

Offer必备算法33_DFS爆搜深搜回溯剪枝_八道力扣题详解(由易到难)

目录 ①力扣784. 字母大小写全排列 解析代码1_path是全局变量 解析代码2_path是函数参数 ②力扣526. 优美的排列 解析代码 ③力扣51. N 皇后 解析代码 ④力扣36. 有效的数独 解析代码 ⑤力扣37. 解数独 解析代码 ⑥力扣79. 单词搜索 解析代码 ⑦力扣1219. 黄金矿…

[嵌入式系统-58]:RT-Thread-内核:线程间通信,邮箱mailbox、消息队列MsgQueue、信号Signal

目录 线程间通信 1. 邮箱 1.1 邮箱的工作机制 1.2 邮箱控制块 1.3 邮箱的管理方式 &#xff08;1&#xff09;创建和删除邮箱 &#xff08;2&#xff09;初始化和脱离邮箱 &#xff08;3&#xff09;发送邮件 &#xff08;4&#xff09;等待方式发送邮件 &#xff08…

excel 和 text 文件的读写操作

excel 和 text 文件的读写操作 1. text 文件读写包 open语句 在文件存在的时候&#xff0c;即打开文件&#xff08;此时操作会覆盖文件&#xff0c;实际就是删除文件重后重新创建&#xff09;&#xff1b;在文件不存在的时候&#xff0c;即创建文件。 import sys print(sys.…

商务谈判技巧与口才书籍有哪些类型

商务谈判技巧与口才书籍有哪些类型&#xff08;3篇&#xff09; 商务谈判技巧与口才书籍的类型丰富多样&#xff0c;以下从三个角度进行介绍&#xff1a; **篇&#xff1a;基础理论与策略类书籍 这类书籍通常深入剖析谈判的本质&#xff0c;系统介绍谈判的原理、技巧和策略。…

S-Clustr+Nets3e 僵尸网络偷拍照片插件

项目地址:https://github.com/MartinxMax/S-Clustr-Ring 更新内容 本次将Nets3e(https://github.com/MartinxMax/Nets3e/tree/Nets3e_V1.1.4)插件成功嵌入,意味着你可以指定任意节点下的主机进行拍照 一些嵌入式设备与工业PLC设备与个人PC计算机回连控制 核心服务端搭建 最好…

MySQL-----多表查询(二)

目录 一.子查询概述&#xff1a; 二&#xff1a;标量子查询&#xff1a; 三&#xff1a;列子查询&#xff1a; 四&#xff1a;行子查询&#xff1a; 五&#xff1a;表子查询&#xff1a; 六&#xff1a;练习部分&#xff1a; 写在之前&#xff1a;本文承接上文MySQL-----多…