繁体   English   中英

带有子查询和 GROUP BY 的 MySQL SELECT 的性能

[英]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忘记太久了...

我的问题:

  1. 是什么导致性能下降?
  2. 如何改进此查询?

更新一:

这是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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM