SELECT COUNT与连接优化与> 100M行的表

问题描述 投票:3回答:4

我有以下查询

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和串,但这不是问题的根源。

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

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 ---

在applications.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`);
mysql sql performance
4个回答
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)

另一优化是“推”的条件在“接通”子句因此MySQL“过滤器”接合前的数据 - >加入跨少得多的行执行

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

CREATE INDEX date_index ON application(created_date);

如果您有更多的类型比日期,你应该考虑把指数型。

CREATE INDEX type_index ON package_codes(type);

[EDIT 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
投票

在applications.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

0
投票

您需要创建一个综合指数。看来,你已经在表上创建单独的索引。在这种情况下,你想在package_codes上CREATED_DATE一个单独的索引,也为CREATED_DATE和类型的复合指数。

也许通过后投之前的日期和组。


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_idPRIMARY KEY。这可能导致查询的简化!

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

© www.soinside.com 2019 - 2024. All rights reserved.