优化 SQL:如何重写此查询以提高性能? (使用子查询,摆脱 GROUP BY?)

Posted

技术标签:

【中文标题】优化 SQL:如何重写此查询以提高性能? (使用子查询,摆脱 GROUP BY?)【英文标题】:Optimize SQL: How to rewrite this query to boost performance? (Use subqueries, get rid of GROUP BY?) 【发布时间】:2019-12-29 00:51:10 【问题描述】:

我正在使用 MySQL 5.7.18-16

我使用的表格:

CREATE TABLE `invoice` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `transaction_id` bigint(20) unsigned NOT NULL,
  `transaction_name` varchar(50) NOT NULL,
  `unit_price` decimal(19,5) DEFAULT NULL,
  `quantity` decimal(19,5) DEFAULT NULL,
  `customer_name` varchar(50) DEFAULT NULL,
  `date` bigint(20) NOT NULL,
  PRIMARY KEY (`ID`),
  KEY `i_transaction_id` (`transaction_id`),
  KEY `i_date` (`date`)
)


CREATE TABLE `transaction` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `transaction_id` bigint(20) unsigned NOT NULL,
....
)


CREATE TABLE `hierarchy` (
  `PRODUCT_ID` int(11) unsigned NOT NULL,
  `PRODUCT_NAME` varchar(255) NOT NULL,
  `PRODUCT_FAMILY_ID` int(11) unsigned NOT NULL,
  `PRODUCT_FAMILY_NAME` varchar(255) NOT NULL,
  `ORG_ID` int(11) unsigned NOT NULL,
  `ORG_NAME` varchar(255) NOT NULL
...
)


CREATE TABLE `product` (
  `ID` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `PRODUCT_NAME` varchar(50) NOT NULL,
  `COMPONENT_NAME` varchar(50) NOT NULL,
...
)

每条发票记录都与交易和客户名称相关,每笔交易都与产品和组件相关。每个产品都属于一个产品系列,每个产品系列都属于一个组织。

我的要求:

我需要根据指定的发票日期计算不同层级(组织/产品系列/产品/组件)下每个客户名称的成本和数量,并按每个客户名称的成本排序。

我当前查询每个组织下每个客户的成本/数量:

SELECT  
    h.org_id,
    h.org_name,
    h.product_family_id,
    h.product_family_name,
    h.product_id,
    h.product_name,
    p.component_id,
    p.component_name,
    i.transaction_id,
    i.customer_name,
    sum(CASE WHEN i.transaction_name = 'TEST' THEN i.quantity END) AS records,
    sum(i.unit_price * i.quantity) AS cost
FROM invoice i
    LEFT JOIN transaction t
        ON i.transaction_id = t.transaction_id
    JOIN hierarchy h
        ON t.product_id = h.product_id
    JOIN product p
        ON t.product_id = p.id
    WHERE i.date >= 1514764800000
    AND i.date <= 1543622400000
    GROUP BY h.org_id, i.customer_name
    ORDER by i.cost DESC;

对于其他级别的计算,我只是更改了 WHERE 和 GROUP BY:

    //By product_family under one specific org
    WHERE h.org_id = 9
    AND i.date >= 1514764800000
    AND i.date <= 1543622400000
    GROUP BY h.product_family_id, i.customer_name
    ORDER by i.cost DESC;

    //By product under one specific product family
    WHERE h.product_family_id = 2011
    AND i.date >= 1514764800000
    AND i.date <= 1543622400000
    GROUP BY h.product_id, i.customer_name
    ORDER by i.cost DESC;

    //By component under one specific product
    WHERE h.product_id = 101
    AND i.date >= 1514764800000
    AND i.date <= 1543622400000
    GROUP BY p.component_name, i.customer_name
    ORDER by i.cost DESC;

组织级计算在生产数据库上运行大约需要 3.5 秒,这太慢了。一个主要原因是“发票”表无法使用任何索引。 (我为'i.date'创建了索引,但由于日期范围太大,没有使用索引。)

是否有任何可能的方法来重写此查询以优化速度?

【问题讨论】:

您能否更新问题以包括数据库(我假设是 mysql)和正在使用的版本? 您在SELECT 中包含了一堆不在GROUP BY 中的列。这是一个格式错误的查询。 我已经更新了我使用的 MySQL 版本。我知道我应该在 GROUP BY 中包含 SELECT 中的所有列,但这无助于提高性能,这是我现在想要改进的主要内容;( 这些日期以人类可读的形式是什么? WHERE i.date >= 1514764800000 AND i.date 【参考方案1】:

使用子查询

这通常是最坏的结果,而不是更好的结果。顾名思义,关系数据库可以很好地处理关系(也称为 JOIN)。

很可能索引设置不正确。为了显示它,有一个EXPLAIN命令,只要在查询的开头写这个词,看看优化器要告诉什么。

https://dev.mysql.com/doc/refman/8.0/en/using-explain.html

然后需要一些挖掘来设置模式中的索引。您也可以将 EXPLAIN 结果粘贴到您的问题中。

【讨论】:

【参考方案2】:

我建议您创建一个视图或 cte 以获取每个客户的产品总和,然后加入层次结构和产品以过滤并获取其他信息

with customer_products as (
select t.product_id, i.customer_name
    sum(CASE WHEN i.transaction_name = 'TEST' THEN i.quantity END) AS records,
    sum(i.unit_price * i.quantity) AS cost
FROM invoice i
LEFT JOIN transaction t
ON i.transaction_id = t.transaction_id
WHERE i.date >= 1514764800000
AND i.date <= 1543622400000
group by t.product_id, i.customer_name)
SELECT
h.org_id,
    h.org_name,
    h.product_family_id,
    h.product_family_name,
    h.product_id,
    h.product_name,
    p.component_id,
    p.component_name,
    cp.customer_name,
    cp.records,
    cp.cost
FROM customer_products cp
JOIN hierarchy h
ON cp.product_id = h.product_id
JOIN product p
ON cp.product_id = p.id

按家庭分组

SELECT
    h.org_id,
    h.org_name,
    h.product_family_id,
    h.product_family_name,
    cp.customer_name,
    sum(cp.records) as records,
    sum(cp.cost) as cost
FROM customer_products cp
JOIN hierarchy h
ON cp.product_id = h.product_id
JOIN product p
ON cp.product_id = p.id
group by h.org_id,
    h.org_name,
    h.product_family_id,
    h.product_family_name,
    cp.customer_name

按组件分组

SELECT
h.org_id,
    h.org_name,
    h.product_family_id,
    h.product_family_name,
    p.component_id,
    p.component_name,
    cp.customer_name,
    sum(cp.records) as records,
    sum(cp.cost) as cost
FROM customer_products cp
JOIN hierarchy h
ON cp.product_id = h.product_id
JOIN product p
ON cp.product_id = p.id
group by     h.org_name,
    h.product_family_id,
    h.product_family_name,
    p.component_id,
    p.component_name,
    cp.customer_name

或仍然使用窗口函数在同一查询中获取所有内容

SELECT
h.org_id,
    h.org_name,
    h.product_family_id,
    h.product_family_name,
    h.product_id,
    h.product_name,
    p.component_id,
    p.component_name,
    cp.customer_name,
    cp.records,
    cp.cost,
    sum(cp.records) over (partition by h.org_id,
    h.org_name,
    h.product_family_id,
    h.product_family_name,
    cp.customer_name) as familyRecord,
    sum(cp.cost) over (partition by h.org_id,
    h.org_name,
    h.product_family_id,
    h.product_family_name,
    cp.customer_name) as familyCost,
    sum(cp.records) as (parititon by h.org_name,
    h.product_family_id,
    h.product_family_name,
    p.component_id,
    p.component_name,
    cp.customer_name) as componentRecord,
    sum(cp.cost) over (partition by h.org_name,
    h.product_family_id,
    h.product_family_name,
    p.component_id,
    p.component_name,
    cp.customer_name) as costComponent
FROM customer_products cp
JOIN hierarchy h
ON cp.product_id = h.product_id
JOIN product p
ON cp.product_id = p.id

【讨论】:

感谢您的回复!我已经更新了我正在处理的表格和要求。

以上是关于优化 SQL:如何重写此查询以提高性能? (使用子查询,摆脱 GROUP BY?)的主要内容,如果未能解决你的问题,请参考以下文章

如何提高查询性能?

如何提高 SQL Server 查询的性能以选择具有值的行不在子查询中的一次计数

优化给定的 sql 查询以提高速度

如何编写此查询以在 Sql Server 中获得更好的性能?删除子字符串行

优化 SQL Server 2008 查询

优化我的 T-SQL 查询以提高性能