优化 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 查询的性能以选择具有值的行不在子查询中的一次计数