芝法酱学习笔记(2.2)——sql性能优化2

news2024/12/24 22:23:13

一、前言

在上一节中,我们使用实验的方式,验证了销售单报表的一些sql性能优化的猜想。但实验结果出乎我们的意料,首先是时间查询使用char和datetime相比,char可能更快,使用bigint(转为秒)和char速度相当。其次是最令人不可理解的是,在连表的时候,直接使用主键id做连表,竟然远远比使用多条件联合索引的连表方式更慢。
小编苦思冥想,认为在千万级数据量下,bigint的筛选效率可能没有联合索引高。那么,如果我们把主键设置为联合主键,在连表时会不会更快呢?那么,就开干吧

二、表设计

2.1 item_new表

我们新建一个item表,表结构和索引如下图所示:
在这里插入图片描述
主键:enp_id,id
不添加任何其他索引

2.2 consign_new表

consign表也类似,我们也用联合主键
在这里插入图片描述
主键:enp_id,header_id,id

三、数据同步导入

为了控制变量,我们不重新生成数据,我们把上次的数据查询出来并相应的插入新表

@Override
    public void syncNewTable() {
        // 查出所有企业
        List<GenEnterpriseEntity> enterpriseEntities = mEnterpriseDbService.list();

        for(GenEnterpriseEntity enterpriseEntity : enterpriseEntities) {

            log.info("开始导入"+enterpriseEntity.getName()+"数据");

            List<GenItemEntity> itemEntityList = mItemDbService.listByEnpId(enterpriseEntity.getId());
            List<GenItemNewEntity> itemNewEntityList = new ArrayList<>();

            for(GenItemEntity itemEntity : itemEntityList) {
                GenItemNewEntity itemNewEntity = new GenItemNewEntity();
                itemNewEntity.createInit();
                itemNewEntity.setEnpId(itemEntity.getId());
                itemNewEntity.setEnpCode(itemEntity.getEnpCode());
                itemNewEntity.setId(itemEntity.getId());
                itemNewEntity.setName(itemEntity.getName());
                itemNewEntity.setCost(itemEntity.getCost());
                itemNewEntity.setTestData(true);
                itemNewEntityList.add(itemNewEntity);
            }

            TransactionTemplate template = new TransactionTemplate(mTransactionManager);
            template.execute(status ->{
                mItemNewDbService.saveBatch(itemNewEntityList);
                return true;
            });
            log.info("导入商品完成");

            final String DAY_BEGIN = "2018-01-01";
            final String DAY_END = "2024-12-31";


            LocalDate startDate = LocalDate.parse(DAY_BEGIN);
            LocalDate endDate = LocalDate.parse(DAY_END);

            while (!startDate.isAfter(endDate)) {

                log.info("导入"+startDate+"的销售单数据");

                LocalDateTime billTimeBeg = startDate.atTime(0, 0, 0);
                LocalDateTime billTimeEnd = startDate.atTime(23, 59, 59);

                long billTimeKeyBeg = CommonUtil.LocalDateTimeToSecond(billTimeBeg);
                long billTimeKeyEnd = CommonUtil.LocalDateTimeToSecond(billTimeEnd);

                List<GenConsignEntity> consignEntityList = mConsignDbService.findAll(enterpriseEntity.getId(),billTimeKeyBeg,billTimeKeyEnd);
                List<GenConsignNewEntity> consignNewEntityList = new ArrayList<>();

                for(GenConsignEntity consignEntity : consignEntityList) {
                    GenConsignNewEntity consignNewEntity = new GenConsignNewEntity();
                    consignNewEntity.createInit();
                    consignNewEntity.setId(consignEntity.getId());
                    consignNewEntity.setEnpId(consignEntity.getEnpId());
                    consignNewEntity.setHeaderId(consignEntity.getHeaderId());
                    consignNewEntity.setBillTimeKey(consignEntity.getBillTimeKey());
                    consignNewEntity.setItemId(consignEntity.getItemId());
                    consignNewEntity.setItemName(consignEntity.getItemName());
                    consignNewEntity.setItemCnt(consignEntity.getItemCnt());
                    consignNewEntity.setPrice(consignEntity.getPrice());
                    consignNewEntity.setDescription(consignEntity.getDescription());
                    consignNewEntity.setTestData(true);
                    consignNewEntityList.add(consignNewEntity);
                }
                consignNewEntityList.sort(Comparator.comparing(GenConsignNewEntity::getHeaderId));

                template = new TransactionTemplate(mTransactionManager);
                template.execute(status -> {
                    mConsignNewDbService.saveBatch(consignNewEntityList);
                    return true;
                });

                log.info(startDate+"的销售单数据导入完成");

                startDate = startDate.plusDays(1l);
            }
        }
    }

四、mapper改写

4.1 枚举

@RequiredArgsConstructor
@EnumDesc
public enum EHeaderJoinMode {

    NONE(0,"不连表","不连表,直接使用consign表做查询",null,null),
    ID_JOIN(1,"id关联","consign_header的id与consign的header_id做关联","id","header_id"),
    BILL_NO_JOIN(2,"订单号关联","header表的enp_id和bill_no与consin相应字段关联","bill_no","bill_no"),
    NEW_CONSIGN(3,"新consign表","enp_id和header_id做连接","id","header_id");


    @EnumValue
    @Getter
    private final int code;
    @Getter
    private final String name;
    @Getter
    private final String desc;
    @Getter
    private final String headerCol;
    @Getter
    private final String consignCol;
}
@RequiredArgsConstructor
@EnumDesc
public enum EItemJoinMode {

    NONE(0,"不连接","不连接item表",null,null),
    ID_JOIN(1,"id连接","使用id链接item","item_id","id"),
    STR_ID_JOIN(2,"字符串id连接","使用字符串id做连接","item_str_id","id"),
    REL_ID_JOIN(3,"关联id连接","不但使用字符串id做连接,item表也不用主键","item_str_id","rel_id"),
    NEW_TABLE(4,"和item_new做连接","和item_new做连接,不但连id,还连enp_id","item_id","id");

    @EnumValue
    @Getter
    private final int code;
    @Getter
    private final String name;
    @Getter
    private final String desc;
    @Getter
    private final String consignCol;
    @Getter
    private final String itemCol;
}

4.2 xml改写

这里不放完整代码了,就改了2处

<choose>
            <when test="IN.headerJoin.name() == 'ID_JOIN'">
                consign_header h JOIN consign c ON h.${IN.headerJoin.headerCol} = c.${IN.headerJoin.consignCol}
            </when>
            <when test="IN.headerJoin.name() == 'BILL_NO_JOIN'">
                consign_header h JOIN consign c ON h.${IN.headerJoin.headerCol} = c.${IN.headerJoin.consignCol} AND h.enp_id = c.enp_id
            </when>
            <!--新增模式-->
            <when test="IN.headerJoin.name() == 'NEW_CONSIGN'">
                consign_header h JOIN consign_new c ON h.${IN.headerJoin.headerCol} = c.${IN.headerJoin.consignCol} AND h.enp_id = c.enp_id
            </when>
            <otherwise>
                consign c
            </otherwise>
        </choose>
        <choose>
            <when test="IN.itemJoin.name() == 'ID_JOIN'">
                JOIN item i ON c.${IN.itemJoin.consignCol} = i.${IN.itemJoin.itemCol}
            </when>
            <when test="IN.itemJoin.name() == 'STR_ID_JOIN'">
                JOIN item i ON c.${IN.itemJoin.consignCol} = i.${IN.itemJoin.itemCol}
            </when>
            <when test="IN.itemJoin.name() == 'REL_ID_JOIN'">
                JOIN item i ON c.${IN.itemJoin.consignCol} = i.${IN.itemJoin.itemCol} AND c.enp_id = i.enp_id
            </when>
            <!--新增模式-->
            <when test="IN.itemJoin.name() == 'NEW_TABLE'">
                JOIN item i ON c.${IN.itemJoin.consignCol} = i.${IN.itemJoin.itemCol} AND c.enp_id = i.enp_id
            </when>
        </choose>

五、实验

请求json

{
  "current": 2,
  "size": 10,
  "enterpriseId": 1869035491194941444,
  "billTimeBeg": "2024-04-01",
  "billTimeEnd": "2024-07-31",
  "headerJoin": "NEW_CONSIGN",
  "itemJoin": "NEW_TABLE",
  "orderBy": "PROFIT",
  "billTimeMode": "BILL_TIME_KEY"
}

生成sql:

explain SELECT c.item_id,c.item_name,
SUM(c.item_cnt) AS total_cnt, 
SUM(c.price * c.item_cnt) AS total_amount, 
SUM((c.price - i.cost) * c.item_cnt) AS total_profit 
FROM consign_header h JOIN consign_new c ON h.id = c.header_id AND h.enp_id = c.enp_id 
JOIN item i ON c.item_id = i.id AND c.enp_id = i.enp_id 
WHERE h.enp_id = 1869035491194941444 
AND h.bill_time_key BETWEEN 1711900800 AND 1722441599 
GROUP BY item_id 
ORDER BY total_profit LIMIT 10,10

explain结果

select_typetabletypekeyken_kenrowsfiltered
simplehrangidx_time_key1611516100
simplecrefPRIMARY1643100
simpleirefPRIMARY81100

执行时间

enp_idenp_codecnttime
1869035491194941442enp_0012488142.638s
1869035491194941443enp_0022637802.285s
1869035491194941444enp_0031205221.157s
1869035491194941445enp_004842621.003s
1869035491194941446enp_0051746734.157s
1869035491194941447enp_0063427514.105s
1869035491194941448enp_007529640.48s
1869035491194941449enp_0081721593.895s
1869035491194941450enp_0091816324.688s
1869035491194941451enp_0101883825.168s

先前使用id主键连表的执行时间

enp_idenp_codecnttime
1869035491194941442enp_00124881419.311s
1869035491194941443enp_00226378018.534s
1869035491194941444enp_00312052213.849s
1869035491194941445enp_004842625.782s
1869035491194941446enp_00517467321.158s
1869035491194941447enp_00634275120.927s
1869035491194941448enp_007529643.087s
1869035491194941449enp_00817215919.982s
1869035491194941450enp_00918163223.256s
1869035491194941451enp_01018838226.057s

结论:
效率比先前,提升了6~8倍!!!

六、经验总结

在设计表的时候,如果有明确的父子表层级关系(1对多),并且数据量很大,子表的主键直接设计成联合主键。
比如本案例中的,enterprise -> item,consign_header; consign_header->consign

七、一些其他筛选参数

上节我们还有一些其他筛选参数的情况没有测试,诸如门店id,业务员id等
群友们可以自行测试,我这里就不耗费篇幅了。
结论就是按照预期走了索引。

八、order by问题

上期由于篇幅原因,还有一个问题没有实验,就是order by id使得主键索引覆盖了正常的索引。
小编懒得写代码做实验了,我们直接写sql吧:

ORDER BY id DESC:

SELECT * FROM consign_header h
WHERE h.enp_id = 1869035491194941447
AND h.bill_time_key BETWEEN 1711900800 AND 1722441599 
ORDER BY id DESC LIMIT 100;

在workbench的执行时间:0.047S
在这里插入图片描述

ORDER BY bill_time_key DESC:

explain SELECT * FROM consign_header h
WHERE h.enp_id = 1869035491194941447
AND h.bill_time_key BETWEEN 1711900800 AND 1722441599 
ORDER BY h.bill_time_key DESC LIMIT 100;

在workbench的执行时间:0.015S,并且第二次执行会因缓存变为0秒
在这里插入图片描述
我们可以看到,order by id 会使得查找条件不走索引,而走了主键,并且速度显著降低。

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

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

相关文章

IntelliJ IDEA 快捷键大全:提升开发效率的利器

目录 一、基础快捷键 1. 文件操作快捷键 2. 编辑&#xff08;Editing&#xff09; 2.1 代码补全与导航 2.2 代码编辑 2.3 代码折叠与展开 3. 查找与替换 4. 调试 5. 版本控制 高级快捷键 重构快捷键&#xff1a;让代码更加优雅 导航快捷键&#xff1a;快速定位代码 …

基于Qlearning强化学习的机器人路线规划matlab仿真

目录 1.算法仿真效果 2.算法涉及理论知识概要 3.MATLAB核心程序 4.完整算法代码文件获得 1.算法仿真效果 matlab2022a仿真结果如下&#xff08;完整代码运行后无水印&#xff09;&#xff1a; 训练过程 测试结果 仿真操作步骤可参考程序配套的操作视频。 2.算法涉及理论…

LINUX--shell

函数 格式&#xff1a; func() { command } function 关键字可写&#xff0c;也可不写。 示例 1&#xff1a; #!/bin/bash func() { #定义函数 echo "Hello $1" } func world #执行主文件 # bash test.sh Hello world 数组 数组是相…

第22天:信息收集-Web应用各语言框架安全组件联动系统数据特征人工分析识别项目

#知识点 1、信息收集-Web应用-开发框架-识别安全 2、信息收集-Web应用-安全组件-特征分析 一、ICO图标&#xff1a; 1、某个应用系统的标示&#xff0c;如若依系统有自己特点的图标&#xff1b;一旦该系统出问题&#xff0c;使用该系统的网站都会受到影响&#xff1b; 2、某个公…

Linux驱动开发 IIC I2C驱动 编写APP访问EEPROM AT24C02

在嵌入式开发中&#xff0c;I2C&#xff08;Inter-Integrated Circuit&#xff09;是一种常用的串行通信协议&#xff0c;广泛应用于与外设&#xff08;如 EEPROM、传感器、显示屏等&#xff09;进行数据交换。AT24C02 是一种常见的 I2C EEPROM 存储器&#xff0c;它提供 2Kbit…

upload-labs-master第21关超详细教程

目录 环境配置解题思路利用漏洞 操作演示 环境配置 需要的东西 phpstudy-2018 链接&#xff1a; phpstudy-2018 提取码&#xff1a;0278 32 位 vc 9 和 11 运行库 链接&#xff1a; 运行库 提取码&#xff1a;0278 upload-labs-master 靶场 链接&#xff1a; upload-lasb-ma…

Redis篇--常见问题篇7--缓存一致性2(分布式事务框架Seata)

1、概述 在传统的单体应用中&#xff0c;事务管理相对简单&#xff0c;通常使用数据库的本地事务&#xff08;如MySQL的BEGIN和COMMIT&#xff09;来保证数据的一致性。然而&#xff0c;在微服务架构中&#xff0c;由于每个服务都有自己的数据库&#xff0c;跨服务的事务管理变…

概率论得学习和整理32: 用EXCEL描述正态分布,用δ求累计概率,以及已知概率求X的区间

目录 1 正态分布相关 2 正态分布的函数和曲线 2.1 正态分布的函数值&#xff0c;用norm.dist() 函数求 2.2 正态分布的pdf 和 cdf 2.3 正态分布的图形随着u 和 δ^2的变化 3 正态分布最重要的3δ原则 3.0 注意&#xff0c;这里说的概率一定是累计概率CDF&#xff0c;而…

Day1 苍穹外卖前端 Vue基础、Vue基本使用方式、Vue-router、Vuex、TypeScript

目录 1.VUE 基础回顾 1.1 基于脚手架创建前端工程 1.1.1 环境要求 1.1.2 脚手架创建项目 1.1.3 工程结构 1.1.4 启动前端服务 1.2 vue基本使用方式 1.2.1 vue 组件 1.2.2 文本插值 1.2.3 属性绑定 1.2.4 事件绑定 1.2.5 双向绑定 1.2.6 条件渲染 1.2.7 跨域问题 1.2.8 axios 1.…

esp8266_TFTST7735语音识别UI界面虚拟小助手

文章目录 一 实现思路1 项目简介1.1 项目效果1.2 实现方式 2 项目构成2.1 软硬件环境2.2 完整流程总结&#xff08;重点整合&#xff09;(1) 功能逻辑图(2) 接线(3) 使用esp8266控制TFT屏(4)TFT_espI库配置方法(5) TFT_esp库常用代码详解(6)TFT屏显示图片(7) TFT屏显示汉字(8) …

java web springboot

0. 引言 SpringBoot对Spring的改善和优化&#xff0c;它基于约定优于配置的思想&#xff0c;提供了大量的默认配置和实现 使用SpringBoot之后&#xff0c;程序员只需按照它规定的方式去进行程序代码的开发即可&#xff0c;而无需再去编写一堆复杂的配置 SpringBoot的主要功能…

Windows下ESP32-IDF开发环境搭建

Windows下ESP32-IDF开发环境搭建 文章目录 Windows下ESP32-IDF开发环境搭建一、软件安装二、搭建IDF开发环境2.1 安装VS Code插件&#xff1a;2.2 配置ESP-IDF插件&#xff1a;2.3 下载例程源码&#xff1a; 三、编译和烧录代码四、Windows下使用命令行编译和烧录程序4.1 配置环…

6UCPCI板卡设计方案:8-基于双TMS320C6678 + XC7K420T的6U CPCI Express高速数据处理平台

基于双TMS320C6678 XC7K420T的6U CPCI Express高速数据处理平台 1、板卡概述 板卡由我公司自主研发&#xff0c;基于6UCPCI架构&#xff0c;处理板包含双片TI DSP TMS320C6678芯片&#xff1b;一片Xilinx公司FPGA XC7K420T-1FFG1156 芯片&#xff1b;六个千兆网口&#xff…

c++--------------------------------接口实现

引用参数 引用的基本概念 在C中&#xff0c;引用是一个别名&#xff0c;它为已存在的变量提供了另一个名字。引用的声明格式为类型& 引用名 变量名;。例如&#xff0c;int num 10; int& ref num;&#xff0c;这里ref就是num的引用&#xff0c;对ref的操作等价于对nu…

docker run命令大全

docker run命令大全 基本语法常用选项基础选项资源限制网络配置存储卷和挂载环境变量重启策略其他高级选项示例总结docker run 命令是 Docker 中最常用和强大的命令之一,用于创建并启动一个新的容器。该命令支持多种选项和参数,可以满足各种使用场景的需求。以下是 docker ru…

rk3568制冷项目驱动开发流程汇总(只适用于部分模块CIF DVP等,自用)

采用fpga输入&#xff0c;3568采集并显示至hdmi RKVICAP 驱动框架说明 RKVICAP驱动主要是基于 v4l2 / media 框架实现硬件的配置、中断处理、控制 buffer 轮转&#xff0c;以及控制 subdevice(如 mipi dphy 及 sensor) 的上下电等功能。 对于RK356X 芯片而言&#xff0c; VICAP…

怎么在idea中创建springboot项目

最近想系统学习下springboot&#xff0c;尝试一下全栈路线 从零开始&#xff0c;下面将叙述下如何创建项目 环境 首先确保自己环境没问题 jdkMavenidea 创建springboot项目 1.打开idea&#xff0c;选择file->New->Project 2.选择Spring Initializr->设置JDK->…

springboot476基于vue篮球联盟管理系统(论文+源码)_kaic

摘 要 如今社会上各行各业&#xff0c;都喜欢用自己行业的专属软件工作&#xff0c;互联网发展到这个时候&#xff0c;人们已经发现离不开了互联网。新技术的产生&#xff0c;往往能解决一些老技术的弊端问题。因为传统篮球联盟管理系统信息管理难度大&#xff0c;容错率低&am…

蓝桥杯嵌入式备赛教程(1、led,2、lcd,3、key)

一、工程模版创建流程 第一步 创建新项目 第二步 选择型号和管脚封装 第三步 RCC使能 外部时钟&#xff0c;高速外部时钟 第四步晶振时钟配置 由数据手册7.1可知外部晶振频率为24MHz 最后一项设置为80 按下回车他会自动配置时钟 第五步&#xff0c;如果不勾选可能程序只会…

步进电机位置速度双环控制实现

步进电机位置速度双环控制实现 野火stm32电机教学 提高部分-第11讲 步进电机位置速度双环控制实现(1)_哔哩哔哩_bilibili PID模型 位置环作为外环,速度环作为内环。设定目标位置和实际转轴位置的位置偏差,经过位置PID获得位置期望,然后讲位置期望(位置变化反映了转轴的速…