You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何优化含重复子查询与多分组的MySQL查询

问题描述

我用两个完全相同的子查询(a和b)生成子查询n,再对n做聚合得到最终结果,但查询响应时间太长。试过合并a与b、a与n都没成功,求优化思路。

原查询SQL:

select n.businessLiaision,
       n.channel,
       n.name,
       n.dt,
       n.ifNew,
       count(n.productRowId),
       sum(n.totalQty),
       sum(n.totalAmount)
from ( select a.productRowId,
              a.name,
              a.rowId,
              a.dt,
              a.businessLiaision,
              a.channel,
              a.ct,
              a.totalQty,
              a.totalAmount,
              case when a.ct = sum(b.ct) then 'true' else 'false' end as 'ifNew'
       from  ( select d.productRowId,
                      p.name,
                      DATE_FORMAT(o.effectiveTime, '%m/%Y') as 'dt',
                      p.rowId,
                      p.businessLiaision,
                      p.channel,
                      count(*) as 'ct',
                      sum(d.qty) as 'totalQty',
                      sum(d.amountPostDiscount) as 'totalAmount'
               from transactionParty as p 
               join transactionOrderHist as o on p.rowId = o.transactionPartyRowId
               join transactionOrderDetailHist as d on o.rowId = d.orderRowId
               where o.businessType = 'sales'
               group by d.productRowId, p.name, DATE_FORMAT(o.effectiveTime, '%m/%Y'), p.rowId, p.businessLiaision, p.channel
               ) as a
       left join ( select d.productRowId,
                          p.name,
                          DATE_FORMAT(o.effectiveTime, '%m/%Y') as 'dt',
                          count(*) as 'ct'
                   from transactionParty as p 
                   join transactionOrderHist as o on p.rowId = o.transactionPartyRowId
                   join transactionOrderDetailHist as d on o.rowId = d.orderRowId
                   where o.businessType = 'sales'
                   group by d.productRowId, p.name, DATE_FORMAT(o.effectiveTime, '%m/%Y')
                  ) as b on b.productRowId = a.productRowId and b.name = a.name and b.dt <= a.dt
        group by  a.productRowId, a.name, a.rowId, a.dt, a.ct, a.businessLiaision, a.channel, a.ct, a.totalQty, a.totalAmount
       ) as n
group by n.businessLiaision, n.channel, n.name, n.dt,  n.ifNew
执行计划结果

执行计划显示查询存在多次重复表扫描与聚合操作,性能瓶颈集中在子查询a和b的重复计算,以及关联阶段的全量匹配。

表结构说明

transactionParty

CREATE TABLE `transactionParty` ( 
`rowId` varchar(50) NOT NULL, 
`name` varchar(100) DEFAULT NULL, 
`code` varchar(20) DEFAULT NULL,
`businessLiaision` varchar(20) DEFAULT NULL, 
`type` varchar(20) DEFAULT NULL, 
`contractualType` varchar(20) DEFAULT NULL, 
`paymentMethod` varchar(20) DEFAULT NULL, 
`partyGroup` varchar(20) DEFAULT NULL, 
`channel` varchar(20) DEFAULT NULL, 
`costCenter` varchar(20) DEFAULT NULL, 
`warehouseRowId` varchar(100) DEFAULT NULL, 
`taxOption` varchar(20) DEFAULT NULL, 
PRIMARY KEY (`rowId`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8

transactionOrderHist

CREATE TABLE `transactionOrderHist` (
 `rowId` varchar(50) NOT NULL COMMENT '明道记录ID', 
 `orderId` varchar(50) DEFAULT NULL COMMENT '单据ID', 
 `orderCreationTime` datetime DEFAULT NULL COMMENT '单据创建时间', 
 `orderCreator` varchar(20) DEFAULT NULL COMMENT '单据创建人', 
 `businessType` varchar(20) DEFAULT NULL COMMENT '业务类型', 
 `businessLiaision` varchar(20) DEFAULT NULL COMMENT '内部业务负责人,如业务员/采购/文员', 
 `transactionPartyRowId` varchar(50) DEFAULT NULL COMMENT '往来单位', 
 `outboundWarehouse` varchar(50) DEFAULT NULL COMMENT '发货仓', 
 `inboundWarehouse` varchar(50) DEFAULT NULL COMMENT '收货仓', 
 `outboundWarehouseType` varchar(50) DEFAULT NULL, 
 `inboundWarehouseType` varchar(50) DEFAULT NULL, 
 `effectiveTime` datetime DEFAULT '0000-00-00 00:00:00' COMMENT '单据过账时间', 
 `orderEffectuater` varchar(20) DEFAULT NULL COMMENT '单据过账人', 
 `remark` varchar(200) DEFAULT NULL, 
 `productCount` int(10) DEFAULT NULL, 
 `totalUnitCount` int(10) DEFAULT NULL, 
 `totalCostAmount` decimal(10,0) DEFAULT NULL, 
 `totalPostDiscountAmount` decimal(10,0) DEFAULT NULL, 
 `paymentStatus` varchar(50) DEFAULT NULL, 
 `paymentDate` datetime DEFAULT NULL, 
 `outboundWarehouseRowId` varchar(50) DEFAULT NULL, 
 `inboundWarehouseRowId` varchar(50) DEFAULT NULL,
 PRIMARY KEY (`rowId`) USING BTREE, 
 KEY `orderEffectiveDate` (`effectiveTime`) 
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8

transactionOrderDetailHist

CREATE TABLE `transactionOrderDetailHist` ( 
 `rowId` varchar(50) NOT NULL, 
 `orderRowId` varchar(50) DEFAULT NULL, 
 `productRowId` varchar(50) DEFAULT NULL, 
 `qty` int(20) DEFAULT NULL, 
 `price` decimal(20,2) DEFAULT NULL, 
 `cost` decimal(20,2) DEFAULT NULL, 
 `amount` decimal(20,2) DEFAULT NULL, 
 `amountPostDiscount` decimal(20,2) DEFAULT NULL, 
 `type` varchar(50) DEFAULT NULL, 
 `effectiveTime` datetime DEFAULT NULL, 
 `costAmount` decimal(20,2) DEFAULT NULL, 
 PRIMARY KEY (`rowId`) USING BTREE 
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8
优化思路

1. 消除重复子查询,复用基础聚合结果

子查询a和b数据源完全一致,仅字段数量不同,可先计算一次基础聚合结果,再复用该结果完成后续逻辑,避免重复的表连接与计算:

WITH base_agg AS (
    SELECT 
        d.productRowId,
        p.name,
        DATE_FORMAT(o.effectiveTime, '%m/%Y') AS dt,
        p.rowId,
        p.businessLiaision,
        p.channel,
        COUNT(*) AS ct,
        SUM(d.qty) AS totalQty,
        SUM(d.amountPostDiscount) AS totalAmount
    FROM transactionParty AS p 
    JOIN transactionOrderHist AS o ON p.rowId = o.transactionPartyRowId
    JOIN transactionOrderDetailHist AS d ON o.rowId = d.orderRowId
    WHERE o.businessType = 'sales'
    GROUP BY d.productRowId, p.name, DATE_FORMAT(o.effectiveTime, '%m/%Y'), p.rowId, p.businessLiaision, p.channel
)
SELECT 
    n.businessLiaision,
    n.channel,
    n.name,
    n.dt,
    n.ifNew,
    COUNT(n.productRowId),
    SUM(n.totalQty),
    SUM(n.totalAmount)
FROM (
    SELECT 
        ba.productRowId,
        ba.name,
        ba.rowId,
        ba.dt,
        ba.businessLiaision,
        ba.channel,
        ba.ct,
        ba.totalQty,
        ba.totalAmount,
        CASE WHEN ba.ct = SUM(ba_sub.ct) THEN 'true' ELSE 'false' END AS ifNew
    FROM base_agg ba
    LEFT JOIN base_agg ba_sub 
        ON ba_sub.productRowId = ba.productRowId 
        AND ba_sub.name = ba.name 
        AND ba_sub.dt <= ba.dt
    GROUP BY ba.productRowId, ba.name, ba.rowId, ba.dt, ba.ct, ba.businessLiaision, ba.channel, ba.totalQty, ba.totalAmount
) n
GROUP BY n.businessLiaision, n.channel, n.name, n.dt, n.ifNew;

2. 用窗口函数替代关联聚合,减少层级

原逻辑中ifNew的计算可以通过窗口函数实现,避免左连接+分组的开销,进一步简化查询结构:

WITH base_agg AS (
    SELECT 
        d.productRowId,
        p.name,
        DATE_FORMAT(o.effectiveTime, '%m/%Y') AS dt,
        p.rowId,
        p.businessLiaision,
        p.channel,
        COUNT(*) AS ct,
        SUM(d.qty) AS totalQty,
        SUM(d.amountPostDiscount) AS totalAmount
    FROM transactionParty AS p 
    JOIN transactionOrderHist AS o ON p.rowId = o.transactionPartyRowId
    JOIN transactionOrderDetailHist AS d ON o.rowId = d.orderRowId
    WHERE o.businessType = 'sales'
    GROUP BY d.productRowId, p.name, DATE_FORMAT(o.effectiveTime, '%m/%Y'), p.rowId, p.businessLiaision, p.channel
),
calc_ifnew AS (
    SELECT 
        ba.*,
        CASE WHEN ba.ct = SUM(ba.ct) OVER (PARTITION BY ba.productRowId, ba.name ORDER BY ba.dt) THEN 'true' ELSE 'false' END AS ifNew
    FROM base_agg ba
)
SELECT 
    businessLiaision,
    channel,
    name,
    dt,
    ifNew,
    COUNT(productRowId),
    SUM(totalQty),
    SUM(totalAmount)
FROM calc_ifnew
GROUP BY businessLiaision, channel, name, dt, ifNew;

3. 添加针对性索引,减少扫描范围

当前表索引覆盖不足,建议添加以下联合索引:

  • 给transactionOrderHist添加:(businessType, transactionPartyRowId, effectiveTime),覆盖过滤条件、关联字段和日期字段
  • 给transactionOrderDetailHist添加:(orderRowId, productRowId),加速与订单表的关联及后续分组
  • 给transactionParty添加覆盖索引:(rowId, name, businessLiaision, channel),避免回表查询

4. 优化日期函数对索引的影响

DATE_FORMAT(o.effectiveTime, '%m/%Y')会导致effectiveTime的索引失效,可新增计算列并建立索引:

ALTER TABLE transactionOrderHist ADD COLUMN effective_month_year VARCHAR(7) AS (DATE_FORMAT(effectiveTime, '%m/%Y')) STORED;
CREATE INDEX idx_effective_month ON transactionOrderHist(effective_month_year);

之后查询中直接使用effective_month_year替代日期函数调用,可利用索引加速分组和关联。

内容的提问来源于stack exchange,提问作者jackxu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 05:42:09