SELECT COUNT具有大于100M行的表的JOIN优化

时间:2019-02-05 08:29:56

标签: mysql sql performance

我有以下查询

SELECT SUBSTRING(a0_.created_date FROM 1 FOR 10) AS sclr_0, 
       COUNT(1) AS sclr_1 
FROM applications a0_ INNER JOIN 
     package_codes p1_ ON a0_.id = p1_.application_id 
WHERE a0_.created_date BETWEEN '2019-01-01' AND '2020-01-01' AND
      p1_.type = 'Package 1'
GROUP BY sclr_0

---编辑---

你们中的大多数人都专注于GROUP BY和SUBSTRING,但这不是问题的根源。

以下查询具有相同的执行时间:

SELECT COUNT(1) AS sclr_1 
FROM applications a0_ INNER JOIN 
     package_codes p1_ ON a0_.id = p1_.application_id 
WHERE a0_.created_date BETWEEN '2019-01-01' AND '2020-01-01' AND
      p1_.type = 'Package 1'

---编辑2 ---

在application.created_date上添加索引并强制查询使用指定的索引作为@DDS后,建议执行时间降至750ms

当前查询如下:

SELECT SUBSTRING(a0_.created_date FROM 1 FOR 10) AS sclr_0, 
       COUNT(1) AS sclr_1 
FROM applications a0_ USE INDEX (applications_created_date_idx) INNER JOIN 
     package_codes p1_ USE INDEX (PRIMARY, UNIQ_70A9C6AA3E030ACD, package_codes_type_idx) ON a0_.id = p1_.application_id 
WHERE a0_.created_date BETWEEN '2019-01-01' AND '2020-01-01' AND
      p1_.type = 'Package 1'
GROUP BY sclr_0

---编辑3 ---

我发现在查询中使用过多索引可能会导致MySQL在某些情况下使用非最佳索引,因此最终查询应如下所示:

SELECT SUBSTRING(a0_.created_date FROM 1 FOR 10) AS sclr_0, 
       COUNT(1) AS sclr_1 
FROM applications a0_ USE INDEX (applications_created_date_idx) INNER JOIN 
     package_codes p1_ USE INDEX (package_codes_application_idx) ON a0_.id = p1_.application_id 
WHERE a0_.created_date BETWEEN '2019-01-01' AND '2020-01-01' AND
      p1_.type = 'Package 1'
GROUP BY sclr_0

---结束编辑---

package_codes包含超过100.000.000条记录。

应用程序包含超过250.000条记录。

查询需要2分钟才能得到结果。有什么方法可以优化它吗? 我被困在MySQL 5.5上。

表格:

CREATE TABLE `applications` (
  `id` int(11) NOT NULL,
  `created_date` datetime NOT NULL,
  `name` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL,
  `surname` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

ALTER TABLE `applications`
  ADD PRIMARY KEY (`id`),
  ADD KEY `applications_created_date_idx` (`created_date`);

ALTER TABLE `applications`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
CREATE TABLE `package_codes` (
  `id` int(11) NOT NULL,
  `application_id` int(11) DEFAULT NULL,
  `created_date` datetime NOT NULL,
  `type` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL,
  `code` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL,
  `disabled` tinyint(1) NOT NULL DEFAULT '0',
  `meta_data` longtext COLLATE utf8mb4_unicode_ci
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

ALTER TABLE `package_codes`
  ADD PRIMARY KEY (`id`),
  ADD UNIQUE KEY `UNIQ_70A9C6AA3E030ACD` (`application_id`),
  ADD KEY `package_codes_code_idx` (`code`),
  ADD KEY `package_codes_type_idx` (`type`),
  ADD KEY `package_codes_application_idx` (`application_id`),
  ADD KEY `package_codes_code_application_idx` (`code`,`application_id`);

ALTER TABLE `package_codes`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;

ALTER TABLE `package_codes`
  ADD CONSTRAINT `FK_70A9C6AA3E030ACD` FOREIGN KEY (`application_id`) REFERENCES `applications` (`id`);

4 个答案:

答案 0 :(得分:2)

我的建议是避免这种情况:

SELECT SUBSTRING(a0_.created_date FROM 1 FOR 10) AS sclr_0, 
[...]  
GROUP BY sclr_0

由于dbms每次都会“重新计算”该字段并且不能在其上使用索引,因此,如果将此数据放在它自己的列中并为其建立索引,则性能应该会提高

或者至少使用date_part函数,这样mysql可以设法使用其索引(显然,您应该在application.created_date上添加索引)

SELECT SUBSTRING(a0_.created_date FROM 1 FOR 10) AS sclr_0, COUNT(1) AS sclr_1 
FROM applications a0_ INNER JOIN 
     package_codes p1_ ON (a0_.id = p1_.application_id and a0_.created_date 
BETWEEN '2019-01-01' AND '2020-01-01' and p1_.type = 'Package 1')      
FORCE INDEX (date_index, type_index)
Group by date(a0_.created_date)

另一种优化是将条件“推”到“ on”子句,以便mysql在联接之前“过滤”数据->联接的行数要少得多

编辑: 这是在日期上创建索引

CREATE INDEX date_index ON application(created_date);

如果类型比日期多得多,则应考虑将索引放在类型上。

CREATE INDEX type_index ON package_codes(type);

[编辑2] 请发布

的结果
select count(distinct date(a0_.created_date)) as N_DATES, count(distinct type)as N_TYPES
FROM applications a0_ INNER JOIN 
     package_codes p1_ ON a0_.id = p1_.application_id 

仅对巫婆指数有一个想法会更具选择性

有用的link用于使用MySQL进行索引优化

答案 1 :(得分:1)

在application.created_date上添加索引并强制查询使用指定的索引作为@DDS后,建议执行时间降至750ms

最终查询应如下所示:

SELECT SUBSTRING(a0_.created_date FROM 1 FOR 10) AS sclr_0, 
       COUNT(1) AS sclr_1 
FROM applications a0_ USE INDEX (applications_created_date_idx) INNER JOIN 
     package_codes p1_ USE INDEX (package_codes_application_idx) ON a0_.id = p1_.application_id 
WHERE a0_.created_date BETWEEN '2019-01-01' AND '2020-01-01' AND
      p1_.type = 'Package 1'
GROUP BY sclr_0

答案 2 :(得分:0)

您需要创建一个复合索引。看来您已经在表上创建了各个索引。在这种情况下,您需要在package_codes中的created_date上有一个单独的索引,并且还要有created_date和type的复合索引。

也许强制转换日期,然后按日期分组。

答案 3 :(得分:0)

最佳指标是

p1_:  (type, application_id)
a0_:  (created_date, id)

这些适用于显示的查询的所有(?)版本,除了那些“强制”索引的版本。

优化器将尝试决定以p1_还是a0_开头。而且,有了这些索引,它应该可以很好地选择更好的表。

SUBSTRING(a0_.created_date FROM 1 FOR 10)可以简化为DATE(a0_.created_date),但我怀疑它是否会改变性能。

请注意,索引将被“覆盖”,从而提供了额外的提升。 EXPLAIN通过说Using index(不是Using index condition)来表示这一点。

进一步的改进:摆脱package_codes.id,将application_id提升为PRIMARY KEY。这可能会简化查询!

我的建议适用于(也许)所有版本的MySQL。