繁体   English   中英

优化 MySQL 查询,避免扫描大量行

[英]Optimizing MySQL query to avoid scanning a lot of rows

我正在运行一个使用类似于下表的表的应用程序。 文章有一个表,标签有另一个表。 我想通过文章 ID 获取特定标签订单的最新 30 篇文章。 例如“acer”,下面的查询可以完成这项工作,但它没有被正确索引,因为如果有很多与特定标签相关的文章,它会扫描很多行。 如何在不扫描大量行的情况下运行查询以获得相同的结果?

EXPLAIN SELECT title
FROM tag, article
WHERE tag = 'acer'
AND tag.article_id = article.id
ORDER BY tag.article_id DESC 
LIMIT 0 , 30 

Output

id  select_type     table   type    possible_keys   key     key_len     ref     rows    Extra
1   SIMPLE  tag     ref     tag     tag     92  const   220439  Using where; Using index
1   SIMPLE  article     eq_ref  PRIMARY     PRIMARY     4   testdb.tag.article_id   1 

以下是表格和示例数据:

CREATE TABLE `article` (
  `id` int(11) NOT NULL auto_increment,
  `title` varchar(60) NOT NULL,
  `time_stamp` int(11) NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1000001 ;

-- 
-- Dumping data for table `article`
-- 

INSERT INTO `article` VALUES (1, 'Saudi Apple type D', 1313390211);
INSERT INTO `article` VALUES (2, 'Japan Apple type A', 1313420771);
INSERT INTO `article` VALUES (3, 'UAE Samsung type B', 1313423082);
INSERT INTO `article` VALUES (4, 'UAE Apple type H', 1313417337);
INSERT INTO `article` VALUES (5, 'Japan Samsung type D', 1313398875);
INSERT INTO `article` VALUES (6, 'UK Acer type B', 1313387888);
INSERT INTO `article` VALUES (7, 'Saudi Sony type D', 1313429416);
INSERT INTO `article` VALUES (8, 'UK Apple type B', 1313394549);
INSERT INTO `article` VALUES (9, 'Japan HP type A', 1313427730);
INSERT INTO `article` VALUES (10, 'Japan Acer type C', 1313400046);



CREATE TABLE `tag` (
  `tag` varchar(30) NOT NULL,
  `article_id` int(11) NOT NULL,
  UNIQUE KEY `tag` (`tag`,`article_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

-- 
-- Dumping data for table `tag`
-- 


INSERT INTO `tag` VALUES ('Samsung', 1);
INSERT INTO `tag` VALUES ('Acer', 2);
INSERT INTO `tag` VALUES ('Sony', 3);
INSERT INTO `tag` VALUES ('Apple', 4);
INSERT INTO `tag` VALUES ('Acer', 5);
INSERT INTO `tag` VALUES ('HP', 6);
INSERT INTO `tag` VALUES ('Acer', 7);
INSERT INTO `tag` VALUES ('Sony', 7);
INSERT INTO `tag` VALUES ('Acer', 7);
INSERT INTO `tag` VALUES ('Samsung', 9);

是什么让您认为查询将检查大量行?

该查询将使用tag (tag, article_id)上的UNIQUE索引准确扫描30条记录,将文章连接到PRIMARY KEY上的每条记录并停止。

这正是你的计划所说的。

我刚刚制作了这个测试脚本:

CREATE TABLE `article` (
  `id` int(11) NOT NULL auto_increment,
  `title` varchar(60) NOT NULL,
  `time_stamp` int(11) NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1000001 ;

CREATE TABLE `tag` (
  `tag` varchar(30) NOT NULL,
  `article_id` int(11) NOT NULL,
  UNIQUE KEY `tag` (`tag`,`article_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

INSERT
INTO    article
SELECT  id, CONCAT('Article ', id), UNIX_TIMESTAMP('2011-08-17' - INTERVAL id SECOND)
FROM    t_source;

INSERT
INTO    tag
SELECT  CASE fld WHEN 1 THEN CONCAT('tag', (id - 1) div 10 + 1) ELSE tag END AS tag, id
FROM    (
        SELECT  tag,
                id,
                FIELD(tag, 'Other', 'Acer', 'Sony', 'HP', 'Dell') AS fld,
                RAND(20110817) AS rnd
        FROM    (
                SELECT  'Other' AS tag
                UNION ALL
                SELECT  'Acer' AS tag
                UNION ALL
                SELECT  'Sony' AS tag
                UNION ALL
                SELECT  'HP' AS tag
                UNION ALL
                SELECT  'Dell' AS tag
                ) t
        JOIN    t_source
        ) q
WHERE   POWER(3, -fld) > rnd;

,其中t_source是一个包含1M记录的表,然后运行您的查询:

SELECT  *
FROM    tag t
JOIN    article a
ON      a.id = t.article_id
WHERE   t.tag = 'acer'
ORDER BY
        t.article_id DESC
LIMIT 30;

这是瞬间的。

尝试 ANSI 连接语法:

SELECT title
FROM tag t
INNER JOIN article a
    ON t.article_id = a.id
WHERE
    t.tag = 'acer'
ORDER BY 
    tag.article_id DESC
LIMIT 0 , 30

然后在 tag.tag 上放置一个索引。 假设您对该表有足够的选择性,并且 article.id 是一个主键,那应该是非常活泼的。

编辑:添加此索引

UNIQUE KEY tag (article_id,tag)

我建议修改存储引擎和架构以利用外键。

CREATE TABLE `article` (
  `id` int(11) NOT NULL auto_increment,
  `title` varchar(60) NOT NULL,
  `time_stamp` int(11) NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1000001 ;

CREATE TABLE `tag` (
 `id` int(11) NOT NULL auto_increment,
 `tag` varchar(30) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `article_tag` (
 `id` int(11) NOT NULL auto_increment,
 `article_id` int(11) NOT NULL,
 `tag_id` int(11) NOT NULL,
 PRIMARY KEY (`id`),
 FOREIGN KEY (`article_id`) REFERENCES article(id),
 FOREIGN KEY (`tag_id`) REFERENCES tag(id)
) ENGINE=Innodb;

这会导致这样的查询:

EXPLAIN 
SELECT * FROM article 
    JOIN article_tag ON article.id = article_tag.id 
    JOIN tag ON article_tag.tag_id = tag.id 
WHERE tag.tag="Acer";
+----+-------------+-------------+--------+----------------+---------+---------+-------------------------+------+-------------+
| id | select_type | table       | type   | possible_keys  | key     | key_len | ref                     | rows | Extra       |
+----+-------------+-------------+--------+----------------+---------+---------+-------------------------+------+-------------+
|  1 | SIMPLE      | article_tag | ALL    | PRIMARY,tag_id | NULL    | NULL    | NULL                    |    1 |             |
|  1 | SIMPLE      | tag         | eq_ref | PRIMARY        | PRIMARY | 4       | temp.article_tag.tag_id |    1 | Using where |
|  1 | SIMPLE      | article     | eq_ref | PRIMARY        | PRIMARY | 4       | temp.article_tag.id     |    1 |             |
+----+-------------+-------------+--------+----------------+---------+---------+-------------------------+------+-------------+
3 rows in set (0.00 sec)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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