2

我正在努力尝试优化一些查询并得到一些令人费解的结果(可能源于我对 MySQL 内部工作原理的有限理解)。

令人费解的事情(至少在这一点上对我来说)是,当我试图剖析完整的查询以优化它时,我发现内部选择查询(子查询)本身运行的速度要慢得多。我认为更简单的查询会运行得更快。下面是查询和我的结果:

完整的查询

SELECT r.id, r.serve_url, r.title, r.category_id, GROUP_CONCAT(hr.server_id) AS server_id
FROM hosted_resources hr
LEFT JOIN resources AS r ON (hr.resource_id = r.id)
WHERE hr.resource_id = (
    select id from resources
    where resource_type_id = 1
    and category_id = 1
    and id < 311
    order by date_added desc
    limit 1
)
GROUP BY r.id, r.serve_url, r.title, r.category_id;

EXPLAIN 查询的结果:

+----+-------------+-----------+-------+-------------------------------------------------------------------------------+----------------------------------------------+---------+-------+------+-----------------------------------------------------------+
| id | select_type | table     | type  | possible_keys                                                                 | key                                          | key_len | ref   | rows | Extra                                                     |
+----+-------------+-----------+-------+-------------------------------------------------------------------------------+----------------------------------------------+---------+-------+------+-----------------------------------------------------------+
|  1 | PRIMARY     | hr        | ref   | hosted_resources_resource_id_resource_id_idx,hosted_resources_resource_id_idx | hosted_resources_resource_id_resource_id_idx | 4       | const |    2 | Using where; Using index; Using temporary; Using filesort |
|  1 | PRIMARY     | r         | const | PRIMARY                                                                       | PRIMARY                                      | 4       | const |    1 |                                                           |
|  2 | SUBQUERY    | resources | ref   | PRIMARY,type_idx,category_idx,type_category_idx,type_category_date_idx        | type_category_date_idx                       | 8       |       |   87 | Using where; Using index                                  |
+----+-------------+-----------+-------+-------------------------------------------------------------------------------+----------------------------------------------+---------+-------+------+-----------------------------------------------------------+

基准测试结果:

Concurrency Level:      5000
Time taken for tests:   9.396 seconds
Complete requests:      100000
Failed requests:        0
Write errors:           0
Non-2xx responses:      100000
Total transferred:      31900000 bytes
HTML transferred:       16900000 bytes
Requests per second:    10642.78 [#/sec] (mean)
Time per request:       469.802 [ms] (mean)
Time per request:       0.094 [ms] (mean, across all concurrent requests)
Transfer rate:          3315.47 [Kbytes/sec] received

子查询(我曾期望它运行得更快)

select id, serve_url, title, category_id from resources
where resource_type_id = 1
and category_id = 1
and id < 311
order by date_added desc
limit 1

EXPLAIN 查询的结果:

+----+-------------+-----------+------+------------------------------------------------------------------------+------------------------+---------+-------------+------+-------------+
| id | select_type | table     | type | possible_keys                                                          | key                    | key_len | ref         | rows | Extra       |
+----+-------------+-----------+------+------------------------------------------------------------------------+------------------------+---------+-------------+------+-------------+
|  1 | SIMPLE      | resources | ref  | PRIMARY,type_idx,category_idx,type_category_idx,type_category_date_idx | type_category_date_idx | 8       | const,const |   87 | Using where |
+----+-------------+-----------+------+------------------------------------------------------------------------+------------------------+---------+-------------+------+-------------+

基准测试结果:

Concurrency Level:      5000
Time taken for tests:   42.181 seconds
Complete requests:      100000
Failed requests:        0
Write errors:           0
Total transferred:      41800000 bytes
HTML transferred:       27100000 bytes
Requests per second:    2370.75 [#/sec] (mean)
Time per request:       2109.040 [ms] (mean)
Time per request:       0.422 [ms] (mean, across all concurrent requests)
Transfer rate:          967.75 [Kbytes/sec] received

资源表:

CREATE TABLE `resources` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `date_added` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 
  `resource_type_id` int(11) NOT NULL COMMENT,
  `resource_status_id` int(11) NOT NULL COMMENT 
  `is_hosted` bit(1) NOT NULL COMMENT 
  `category_id` int(11) NOT NULL COMMENT 
  `serve_url` varchar(255) DEFAULT NULL COMMENT 
  `title` varchar(255) DEFAULT NULL COMMENT 
  `parent_resource_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `serve_url_UNIQUE` (`serve_url`),
  KEY `type_idx` (`resource_type_id`),
  KEY `status_idx` (`resource_status_id`),
  KEY `category_idx` (`category_id`),
  KEY `resources_parent_resource_id_idx` (`parent_resource_id`),
  KEY `type_category_idx` (`resource_type_id`,`category_id`),
  KEY `date_added_idx` (`date_added`),
  KEY `type_category_date_idx` (`resource_type_id`,`category_id`,`date_added`),
  CONSTRAINT `resources_category_id` FOREIGN KEY (`category_id`) REFERENCES `categories` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
  CONSTRAINT `resources_parent_resource_id` FOREIGN KEY (`parent_resource_id`) REFERENCES `resources` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
  CONSTRAINT `resources_resource_status_id` FOREIGN KEY (`resource_status_id`) REFERENCES `resource_statuses` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
  CONSTRAINT `resources_resource_type_id` FOREIGN KEY (`resource_type_id`) REFERENCES `resource_types` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=598 DEFAULT CHARSET=latin1;

任何困难都会非常非常感激。凯特

4

2 回答 2

0

try removing the key:

KEY `type_category_date_idx` (`resource_type_id`,`category_id`,`date_added`)

This key contains other keys stated above. From what I know mysql can use 2 different keys at once. If these three are very frequently used for lookups into this table the key may not be so simple to remove.

于 2013-05-29T15:37:37.530 回答
0

您的查询不具有可比性,当您自己运行子查询时,它有额外的列,我相信这就是产生差异的地方,您可以在主查询的 EXPLAIN 中看到子查询的额外内容:

Using where; Using index

当您使用附加列单独运行子查询时,它仅显示:

Using where

这样做的原因是您的非聚集索引仅存储索引列和主键,因此您的索引:

KEY `type_category_date_idx` (`resource_type_id`,`category_id`,`date_added`)

有足够的信息来满足您的整个子查询:

SELECT ID
FROM   Resources
WHERE  resource_type_id = 1
  AND  category_id = 1
  AND  id < 311
ORDER BY date_added DESC

因此没有必要重新参考表格数据。当您向选择列表添加更多列时,索引不再包含满足查询所需的所有信息,并且查询需要使用索引中的 id 列表对表数据执行书签查找,并找到数据在相关栏目中。

这就是My SQL Docs所说的Using Index

仅使用索引树中的信息从表中检索列信息,而无需执行额外的查找来读取实际行。当查询仅使用属于单个索引的列时,可以使用此策略。

如果 Extra 列还显示 Using where,则表示该索引正在用于执行键值查找。如果不使用 where,优化器可能会读取索引以避免读取数据行,但不会将其用于查找。例如,如果索引是查询的覆盖索引,优化器可能会扫描它而不使用它进行查找。

您应该看到,如果仅使用它运行子查询SELECT ID FROM...,您将获得与主查询相同的计划,并且执行时间比使用附加列运行它要快得多。

编辑

就实际优化查询而言,我认为您可以将您的查询更改LEFT JOIN为 an,INNER JOIN因为您知道 ID 必须resources基于WHERE子句存在。除此之外,我认为改进的余地不大,尽管我相信 MySQL 会比 where 子句中的子查询更好地处理 JOIN,因此以下可能会执行得更好:

SELECT  r.id, r.serve_url, r.title, r.category_id, GROUP_CONCAT(hr.server_id) AS server_id
FROM    resources r
        INNER JOIN
        (   SELECT  ID
            FROM    Resources
            WHERE   resource_type_id = 1
            AND     category_id = 1
            AND     id < 311
            ORDER BY date_added DESC
            LIMIT 1
        ) MaxR
            ON MaxR.ID = r.ID
        INNER JOIN hosted_resources hr
            ON hr.resource_id = r.id
GROUP BY r.id, r.serve_url, r.title, r.category_id;
于 2013-05-29T16:21:27.397 回答