[英]Performance of MySQL SELECT with subqueries and GROUP BY
我正在为旧应用程序 (MySQL 5.6) 构建报告工具。 它使用单个reports
表,所有相关列都有一个索引,该表有大约 120'000 行。
我的查询如下,其中 GROUP BY 列和 WHERE 子句可能会根据用户配置报告的方式而改变:
SELECT
nationality AS groupValue,
( SELECT COUNT( request_id ) FROM reports WHERE create_date BETWEEN '2020-10-01 00:00:00' AND '2021-12-31 23:59:59' AND nationality = groupValue ) AS totalRequests,
( SELECT SUM(effective_amount) FROM reports WHERE pay_off_date BETWEEN '2020-10-01 00:00:00' AND '2021-12-31 23:59:59' AND nationality = groupValue ) AS totalSum,
( SELECT COUNT( request_id ) FROM reports WHERE pay_off_date BETWEEN '2020-10-01 00:00:00' AND '2021-12-31 23:59:59' AND current_status = 5 AND nationality = groupValue ) AS totalPaid,
( SELECT COUNT( request_id ) FROM reports WHERE failed_date BETWEEN '2020-10-01 00:00:00' AND '2021-12-31 23:59:59' AND current_status = 3 AND nationality = groupValue ) AS totalRefused,
( SELECT COUNT( request_id ) FROM reports WHERE failed_date BETWEEN '2020-10-01 00:00:00' AND '2021-12-31 23:59:59' AND current_status = 4 AND nationality = groupValue ) AS totalRenounced,
( SELECT COUNT( request_id ) FROM reports WHERE failed_date BETWEEN '2020-10-01 00:00:00' AND '2021-12-31 23:59:59' AND current_status = 2 AND nationality = groupValue ) AS totalInProgress
FROM
reports
GROUP BY
nationality;
对于包含大约 40 个不同值的 GROUP BY 列,性能很好,但如果我选择包含大约 200 个不同值的列(例如国籍,请参见下文),性能会下降到未知时间(我放弃等待)。
我的感觉是,这可能与一些二次复杂性有关,因为在主查询和子查询中都使用了 GROUP BY,这是我需要的,因为 WHERE 子句根据它们各自的 SELECT 值而变化。 但是我的SQL fu忘记太久了...
我的问题:
这是reports
表定义(简化):
CREATE TABLE `reports` (
`request_id` int(11) NOT NULL,
`employee_id` int(11) NOT NULL,
`create_date` datetime NOT NULL,
`failed_date` datetime DEFAULT NULL,
`pay_off_date` datetime DEFAULT NULL,
`nationality` varchar(30) DEFAULT NULL,
`effective_amount` decimal(13,2) DEFAULT NULL,
`current_status` tinyint(2) DEFAULT NULL,
PRIMARY KEY (`request_id`,`customer_id`,`employee_id`),
UNIQUE KEY `request_id` (`request_id`),
KEY `fk_request_id` (`request_id`),
KEY `fk_employee_id` (`employee_id`),
KEY `create_date_index` (`create_date`),
KEY `failed_date_index` (`failed_date`),
KEY `pay_off_date_index` (`pay_off_date`),
KEY `nationality_index` (`nationality`(2))
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
这是一个执行良好的查询:
ID | 选择类型 | 桌子 | 分区 | 类型 | 可能的键 | 钥匙 | 密钥长度 | 参考 | 行 | 过滤 | 额外的 |
---|---|---|---|---|---|---|---|---|---|---|---|
1个 | 基本的 | 报告 | NULL | 全部 | NULL | NULL | NULL | NULL | 118923 | 100.00 | 使用临时的; 使用文件排序 |
7 | 依赖子查询 | 报告 | NULL | 参考 | failed_date_index,nationality_index | 国籍索引 | 5个 | 功能 | 720 | 0.12 | 在哪里使用 |
6个 | 依赖子查询 | 报告 | NULL | 参考 | failed_date_index,nationality_index | 国籍索引 | 5个 | 功能 | 720 | 0.12 | 在哪里使用 |
5个 | 依赖子查询 | 报告 | NULL | 参考 | failed_date_index,nationality_index | 国籍索引 | 5个 | 功能 | 720 | 0.12 | 在哪里使用 |
4个 | 依赖子查询 | 报告 | NULL | 全部 | pay_off_date_index,nationality_index | NULL | NULL | NULL | 118923 | 1.00 | 为每条记录检查的范围(索引 map:0x8080) |
3个 | 依赖子查询 | 报告 | NULL | 全部 | pay_off_date_index,nationality_index | NULL | NULL | NULL | 118923 | 10.00 | 为每条记录检查的范围(索引 map:0x8080) |
2个 | 依赖子查询 | 报告 | NULL | 参考 | 创建日期索引,国籍索引 | 国籍索引 | 5个 | 功能 | 720 | 2.10 | 在哪里使用 |
这是带有国籍的慢速查询:
ID | 选择类型 | 桌子 | 分区 | 类型 | 可能的键 | 钥匙 | 密钥长度 | 参考 | 行 | 过滤 | 额外的 |
---|---|---|---|---|---|---|---|---|---|---|---|
1个 | 基本的 | 报告 | NULL | 全部 | NULL | NULL | NULL | NULL | 118923 | 100.00 | 使用临时的; 使用文件排序 |
7 | 依赖子查询 | 报告 | NULL | 参考 | failed_date_index,nationality_index | 国籍索引 | 5个 | 功能 | 720 | 0.12 | 在哪里使用 |
6个 | 依赖子查询 | 报告 | NULL | 参考 | failed_date_index,nationality_index | 国籍索引 | 5个 | 功能 | 720 | 0.12 | 在哪里使用 |
5个 | 依赖子查询 | 报告 | NULL | 参考 | failed_date_index,nationality_index | 国籍索引 | 5个 | 功能 | 720 | 0.12 | 在哪里使用 |
4个 | 依赖子查询 | 报告 | NULL | 全部 | pay_off_date_index,nationality_index | NULL | NULL | NULL | 118923 | 1.00 | 为每条记录检查的范围(索引 map:0x8080) |
3个 | 依赖子查询 | 报告 | NULL | 全部 | pay_off_date_index,nationality_index | NULL | NULL | NULL | 118923 | 10.00 | 为每条记录检查的范围(索引 map:0x8080) |
2个 | 依赖子查询 | 报告 | NULL | 参考 | 创建日期索引,国籍索引 | 国籍索引 | 5个 | 功能 | 720 | 2.10 | 在哪里使用 |
正如下面的评论者所指出的,这不是 GROUP BY 的正确用法。 我不确定它究竟是如何工作的,但它确实需要在每个子查询的 WHERE 子句中使用nationality = groupValue
表达式,这似乎会导致子查询中的聚合在主查询中为 GROUP BY 分组(欢迎解释。)。
我不推荐以这种方式使用子查询。 在我的例子中,查询通常有效,但是一旦您分组的不同值的数量变多,它们似乎也会在性能方面下降悬崖(或 go 进入无限循环?)。
相反,go 和 CASE 在答案中得到了完美展示。
您可以使用条件聚合编写查询并避免 6 个相关子查询:
SELECT
nationality AS groupValue,
COUNT(CASE WHEN create_date BETWEEN '2020-10-01 00:00:00' AND '2021-12-31 23:59:59' THEN request_id END) AS totalRequests,
SUM(CASE WHEN pay_off_date BETWEEN '2020-10-01 00:00:00' AND '2021-12-31 23:59:59' THEN effective_amount END) AS totalSum,
COUNT(CASE WHEN pay_off_date BETWEEN '2020-10-01 00:00:00' AND '2021-12-31 23:59:59' AND current_status = 5 THEN request_id END) AS totalPaid,
COUNT(CASE WHEN failed_date BETWEEN '2020-10-01 00:00:00' AND '2021-12-31 23:59:59' AND current_status = 3 THEN request_id END) AS totalRefused,
COUNT(CASE WHEN failed_date BETWEEN '2020-10-01 00:00:00' AND '2021-12-31 23:59:59' AND current_status = 4 THEN request_id END) AS totalRenounced,
COUNT(CASE WHEN failed_date BETWEEN '2020-10-01 00:00:00' AND '2021-12-31 23:59:59' AND current_status = 2 THEN request_id END) AS totalInProgress
FROM reports
GROUP BY nationality;
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.