如果 EXPLAIN 只显示 400 行,为啥 MySQL SELECT 查询需要 1-2 分钟才能运行?

Posted

技术标签:

【中文标题】如果 EXPLAIN 只显示 400 行,为啥 MySQL SELECT 查询需要 1-2 分钟才能运行?【英文标题】:Why would a MySQL SELECT query take 1-2 minutes to run if EXPLAIN only shows 400 rows?如果 EXPLAIN 只显示 400 行,为什么 MySQL SELECT 查询需要 1-2 分钟才能运行? 【发布时间】:2014-12-17 04:06:36 【问题描述】:

我需要从一个大表(70M 行)中获取最近的 1000 条记录,通过两个简单和小表上的 INNER JOIN 匹配几个索引良好的项目。

查询需要 1-2 分钟才能运行。然而explain 只显示了数百行可供查看。什么给了?

如何优化查询或更有效地索引表以使该查询在我期望的毫秒内运行?

桌子:

score    70,000,000 records
class           400 records
category        400 records

查询:

SELECT
    s.log_id,
    s.category_id
FROM
    score s
    INNER JOIN category ca ON s.category_id = ca.id
    INNER JOIN class cl ON ca.class_id = cl.id
WHERE
        s.score_status_type_id = 0
    AND ca.category_status_id = 1
    AND cl.class_status_id IN (1, 2)
    AND s.date > DATE_ADD(NOW(), INTERVAL -1440 minute)
GROUP BY s.log_id
ORDER BY s.date DESC
LIMIT 1000:

解释如下:

*** row 1 ***
          table:  cl
           type:  range
  possible_keys:  PRIMARY,class_status_id
            key:  class_status_id
        key_len:  4
            ref:  NULL
           rows:  36
          Extra:  Using where; Using index; Using temporary; Using filesort
*** row 2 ***
          table:  ca
           type:  ref
  possible_keys:  PRIMARY,class_id,category_status_id,category_status_id_class_id_id
            key:  category_status_id_class_id_id
        key_len:  8
            ref:  const,my_db.cl.id
           rows:  1
          Extra:  Using index
*** row 3 ***
          table:  s
           type:  ref
  possible_keys:  unique_key,category_id,date,score,score_status_type_id,score_status_and_date,category_id_score_status_type_id_date_log_id,date_reverse,category_id_date_reverse,score_date
            key:  category_id_score_status_type_id_date_log_id
        key_len:  8
            ref:  my_db.ca.id,const
           rows:  396
          Extra:  Using where; Using index

以下是一些创建表:

CREATE TABLE `score` (
  `log_id` bigint(20) NOT NULL,
  `profile_id` bigint(20) DEFAULT NULL,
  `date` datetime DEFAULT NULL,
  `class_id` int(11) NOT NULL,
  `score` float(10,6) DEFAULT NULL,
  `score_date` datetime DEFAULT NULL,
  `process_date` datetime DEFAULT NULL,
  `status_type_id` int(3) NOT NULL DEFAULT '0',
  `date_reverse` int(11) DEFAULT NULL,
  UNIQUE KEY `unique_key` (`log_id`,`class_id`),
  KEY `class_id` (`class_id`),
  KEY `profile_id` (`profile_id`),
  KEY `date` (`date`),
  KEY `score` (`score`),
  KEY `status_type_id` (`status_type_id `),
  KEY `status_type_id_date` (`status_type_id`,`date`),
  KEY `class_status_type_id_date_log_id` (`class_id`,`status_type_id`,`date`,`log_id`),
  KEY `date_reverse` (`date_reverse`),
  KEY `class_id_date_reverse` (`class_id`,`date_reverse`),
  KEY `date` (`date`),
  KEY `class_id_date_reverse_log_id` (`class_id`,`date_reverse`,`log_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

CREATE TABLE `category` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `class_id` int(11) NOT NULL,
  `category_status_id` int(11) NOT NULL DEFAULT '0',
  `name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  PRIMARY KEY (`id`),
  KEY `class_id` (`class_id`),
  KEY `name` (`name`),
  KEY `category_status_id_class_id_id` (`category_status_id`,`class_id`,`id`)
) ENGINE=InnoDB AUTO_INCREMENT=412 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

CREATE TABLE `class` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `class_status_id` int(11) NOT NULL DEFAULT '1',
  `name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  PRIMARY KEY (`id`),
  KEY `person_id` (`person_id`),
  KEY `name` (`name`),
  KEY `class_status_id` (`class_status_id`),
  KEY `class_multi_1` (`class_status_id`,`name`,`id`)
) ENGINE=InnoDB AUTO_INCREMENT=407 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

【问题讨论】:

计算 : DATE_ADD(NOW(), INTERVAL -1440 分钟) 的值并传递给查询有什么不同吗? 最后 1000 个 log_id 跨越的日期范围是否存在实际限制? 【参考方案1】:

问题在于where 子句是一个过滤器,它在连接完成后 应用,因此您在where 子句中的连接表条件需要实际进行连接并将其放入临时结果集(可能很大)。通常优化器会认识到条件可以在加入时断言,但有时它可能有点密集,所以...

尝试将非关键条件移动到连接中

SELECT s.log_id, s.category_id
FROM score s
JOIN category ca ON s.category_id = ca.id
    AND ca.category_status_id = 1
JOIN class cl ON ca.class_id = cl.id
    AND cl.class_status_id IN (1, 2)
WHERE s.score_status_type_id = 0
AND s.date > DATE_ADD(NOW(), INTERVAL -1440 minute)
GROUP BY s.log_id
ORDER BY s.date DESC
LIMIT 1000

如果这还不够帮助,请尝试首先获取score行的子集作为子查询,然后进行连接。

【讨论】:

以上是关于如果 EXPLAIN 只显示 400 行,为啥 MySQL SELECT 查询需要 1-2 分钟才能运行?的主要内容,如果未能解决你的问题,请参考以下文章

excel折线图横坐标为啥只显示一个数值?

vnc连接到服务器后,为啥只显示一个命令行

mysql-EXPLAIN

为啥网页打开错误上面出现个 HTTP 400错误的请求? 急..

uitableview group 样式为啥行高变大

为啥ie很多网页打不开还显示http400错误?