[英]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.