Оптимизация запроса MySQL, чтобы избежать сканирования большого количества строк - PullRequest
5 голосов
/ 15 августа 2011

Я запускаю приложение, которое использует таблицы, подобные таблицам ниже. Есть одна таблица для статей и есть другая таблица для тегов. Я хочу получить последние 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 

выход

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

Ответы [ 4 ]

1 голос
/ 17 августа 2011

Что заставляет вас думать, что запрос будет проверять большое количество строк?

Запрос будет сканировать ровно 30 записей с использованием индекса UNIQUE на tag (tag, article_id), присоединить статью к каждой записи на 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;

Это было мгновенно.

0 голосов
/ 15 августа 2011

Редактировать: Добавить этот индекс

UNIQUE KEY tag (article_id,tag)
0 голосов
/ 15 августа 2011

Я бы предложил модифицировать механизм хранения и схему для использования внешних ключей.

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)
0 голосов
/ 15 августа 2011

попробуйте синтаксис объединения 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 является первичным ключом, он должен быть довольно быстрым.

...