我正在浏览一个应用程序,并试图优化一些查询,我真的在努力解决其中的一些问题。这是一个例子:
SELECT `Item` . * , `Source` . * , `Keyword` . * , `Author` . * FROM `items` AS `Item` JOIN `sources` AS `Source` ON ( `Item`.`source_id` = `Source`.`id` ) JOIN `authors` AS `Author` ON ( `Item`.`author_id` = `Author`.`id` ) JOIN `items_keywords` AS `ItemsKeyword` ON ( `Item`.`id` = `ItemsKeyword`.`item_id` ) JOIN `keywords` AS `Keyword` ON ( `Keyword`.`id` = `ItemsKeyword`.`keyword_id` ) JOIN `keywords_profiles` AS `KeywordsProfile` ON ( `Keyword`.`id` = `KeywordsProfile`.`keyword_id` ) JOIN `profiles` AS `Profile` ON ( `Profile`.`id` = `KeywordsProfile`.`profile_id` ) WHERE `KeywordsProfile`.`profile_id` IN ( 17 ) GROUP BY `Item`.`id` ORDER BY `Item`.`timestamp` DESC , `Item`.`id` DESC LIMIT 0 , 20;
这个花了10-30秒......在引用的表中,大约有500k作者行,大约750k项和items_keywords行。其他一切都少于500行。
这是解释输出: http://img.skitch.com/20090220-fb52wd7jf58x41ikfxaws96xjn.jpg
EXPLAIN对我来说相对较新,但我逐行完成了这一切,看起来一切都很好。不知道我还能做什么,因为我已经掌握了所有内容的索引......我缺少什么?
这个服务器在slicehost上只有256个切片,但是没有其它的东西在它上运行,CPU在运行之前是0%。然而它仍然在这个查询上。有什么想法吗?
编辑:一些进一步的信息;使这真令人沮丧的一件事是,如果我反复运行此查询,则需要不到0.1秒。我假设这是由于查询缓存,但如果我在它之前运行RESET QUERY CACHE,它仍然运行得非常快。只有在我等待一段时间或运行其他一些查询后才会返回10-30秒。所有的表都是MyISAM ...这是否表明MySQL正在将内容加载到内存中,这就是为什么它运行一段时间这么快?
编辑2:非常感谢大家的帮助......更新......我把所有内容都归结为:
SELECT i.id FROM items AS i ORDER BY i.timestamp DESC, i.id DESC LIMIT 0, 20;
尽管数据库中只有750k记录,但仍然持续了5-6秒。一旦我在ORDER BY子句上删除了第二列,它就很快了。显然有几件事情在这里发生,但当我把查询切换到这个时:
SELECT i.id FROM items AS i JOIN items_keywords AS ik ON ( i.id = ik.item_id ) JOIN keywords AS k ON ( k.id = ik.keyword_id ) JOIN keywords_profiles AS kp ON ( k.id = kp.keyword_id ) WHERE kp.profile_id IN (139) ORDER BY i.timestamp DESC LIMIT 20;
它还需要10秒以上......我还能做些什么?
小小的好奇心:在解释中,items_keywords的rows列总是1544,无论我在查询中使用什么profile_id。它不应该根据与该配置文件关联的项目数量而改变吗?
编辑3:好的,这太荒谬了:)。如果我完全删除ORDER BY子句,事情就会非常迅速,临时表/ filesort会从explain中消失。 item.timestamp列目前有一个索引,但是由于某些原因它没有被使用?我以为我记得有关mysql的东西只使用每个表或某个索引?我应该在这个查询引用的items表的所有列上创建一个多列索引(source_id,author_id,timestamp等)?
答案 0 :(得分:3)
看起来没问题,explain
中的每一行都使用某种索引。一个可能的担心是filesort
位。尝试在没有order by
子句的情况下运行查询,看看是否能改善它。
然后,我会做的是逐渐取出每个join
,直到你(希望)获得大幅度的速度提升,然后专注于为什么会发生这种情况。
我提到filesort
的原因是因为我无法在explain
输出中的任何地方看到时间戳的提及(即使它是您的主要排序标准) - 它可能需要完整的非 - 索引排序。
更新#1:
基于编辑#2,查询:
SELECT i.id
FROM items AS i
ORDER BY i.timestamp DESC, i.id DESC
LIMIT 0, 20;
需要5-6秒。这是令人憎恶的。尝试在TIMESTAMP
和ID
上创建综合索引,看看是否能改善它:
create index timestamp_id on items(timestamp,id);
select id from items order by timestamp desc,id desc limit 0,20;
select id from items order by timestamp,id limit 0,20;
select id from items order by timestamp desc,id desc;
select id from items order by timestamp,id;
在其中一个测试中,我已经离开了降序位(DB2 for one有时不使用索引,如果它们的顺序相反)。另一个变化是在影响它的情况下取消限制。
答案 1 :(得分:3)
试试这个并看看它是如何做的:
SELECT i.*, s.*, k.*, a.*
FROM items AS i
JOIN sources AS s ON (i.source_id = s.id)
JOIN authors AS a ON (i.author_id = a.id)
JOIN items_keywords AS ik ON (i.id = ik.item_id)
JOIN keywords AS k ON (k.id = ik.keyword_id)
WHERE k.id IN (SELECT kp.keyword_id
FROM keywords_profiles AS kp
WHERE kp.profile_id IN (17))
ORDER BY i.timestamp DESC, i.id DESC
LIMIT 0, 20;
我将一些连接分解为非相关子查询,因此您不必执行GROUP BY
将结果映射到不同的行。
实际上,在我的示例中,您仍然可以获得每行i.id
多行,具体取决于映射到给定项目的关键字数量以及profile_id
17。
您的EXPLAIN报告中报告的filesort
可能是由于GROUP BY
和ORDER BY
使用不同字段的组合。
我同意@ʞɔıu的答案,即加速可能是因为关键缓存。
答案 2 :(得分:1)
要使查询快速运行,您需要:
创建索引:CREATE INDEX ix_timestamp_id ON items (timestamp, id)
id
,sources
和authors
上的keywords
为主键。强制MySQL将此索引用于项目,并为其他项目执行NESTED LOOP
联接:
EXPLAIN EXTENDED
选择Item
。*,Source
。 *,Keyword
。 *,Author
。 *
FROM items
AS Item
FORCE INDEX FOR ORDER BY(ix_timestamp_id)
加入items_keywords
AS ItemsKeyword
FORCE INDEX(ix_item_keyword)ON(Item
。id
= ItemsKeyword
。item_id
和ItemsKeyword
。{ {1}} IN
(
选择keyword_id
FROM keyword_id
AS keywords_profiles
FORCE INDEX(ix_keyword_profile)
在KeywordsProfile
。KeywordsProfile
= 17
)
)
加入profile_id
AS sources
FORCE INDEX(主要)ON(Source
。Item
= source_id
。Source
)
加入id
AS authors
FORCE INDEX(主要)ON(Author
。Item
= author_id
。Author
)
加入id
AS keywords
FORCE INDEX(主要)ON(Keyword
。Keyword
= id
。ItemsKeyword
)
ORDER BY keyword_id
。timestamp DESC,Item
。id DESC
LIMIT 0,20
如您所见,我们摆脱Item
,将子查询推入GROUP BY
条件并强制JOIN
用于连接。
这就是我们如何确保PRIMARY KEY
NESTED LOOPS
作为主要表格将用于所有联接。
结果:
1, 'PRIMARY', 'Item', 'index', '', 'ix_timestamp_id', '12', '', 20, 2622845.00, '' 1, 'PRIMARY', 'Author', 'eq_ref', 'PRIMARY', 'PRIMARY', '4', 'test.Item.author_id', 1, 100.00, '' 1, 'PRIMARY', 'Source', 'eq_ref', 'PRIMARY', 'PRIMARY', '4', 'test.Item.source_id', 1, 100.00, '' 1, 'PRIMARY', 'ItemsKeyword', 'ref', 'PRIMARY', 'PRIMARY', '4', 'test.Item.id', 1, 100.00, 'Using where; Using index' 1, 'PRIMARY', 'Keyword', 'eq_ref', 'PRIMARY', 'PRIMARY', '4', 'test.ItemsKeyword.keyword_id', 1, 100.00, '' 2, 'DEPENDENT SUBQUERY', 'KeywordsProfile', 'unique_subquery', 'PRIMARY', 'PRIMARY', '8', 'func,const', 1, 100.00, 'Using index; Using where'
,当我们运行时,我们得到
items
20 rows fetched in 0,0038s (0,0019s)
中有500k
,items
中有600k
,items_keywords
中的512
值和keywords
中的512
值{1}}(全部包含个人资料keywords_profiles
)。
答案 3 :(得分:0)
我建议您在查询上运行一个分析器,然后您可以看到每个子查询花了多长时间以及消耗时间的位置。如果你有phpmyadmin,这是一个简单的chekbox你需要检查以获得这个功能,但我猜你也可以从mysql终端应用程序手动获取它。我之前没有看过这个解释的事情,如果它实际上是我在phpmyadmin习惯的分析我为废话道歉。
答案 4 :(得分:0)
GROUP BY子句实现了什么? SELECT中没有聚合函数,因此GROUP BY应该是不必要的
答案 5 :(得分:0)
有些事情要尝试:
答案 6 :(得分:0)
由于文件系统I / O,您是否可能遇到问题? EXPLAIN显示必须从ItemsKeyword
表中提取1544行。如果你必须为每个那些转到磁盘,你将在运行时添加大约10-15秒的总时间(假设因为你在VM上,所以需要时间很长)。通常情况下,表会缓存在RAM中,或者数据存储在磁盘上足够近,可以组合读取。但是,您运行的是具有256MB RAM的VM,因此您可能没有可以缓存的内存,如果您的表文件足够碎片,您可能会使查询性能降低这么多。
您可以通过在服务器上的另一个shell中运行pidstat -d 1
或iostat 1
之类的内容来了解查询期间I / O的情况。
编辑:
通过查看在(ItemsKeyword
。item_id
上添加索引的查询,ItemsKeyword
。keyword_id
)如果我的理论是正确的,那么它应该解决它对于ItemsKeyword
表。
答案 7 :(得分:0)
MySQL将大量内容加载到不同的缓存中,包括索引和查询。此外,您的操作系统将保留文件系统缓存,以便在重复执行时加快查询速度。
要考虑的一件事是MySQL如何在此类查询期间创建临时表。正如您在解释中所看到的,正在创建一个临时表,可能用于排序结果。通常,MySQL会在内存中创建这些临时表,除了2个条件。首先,如果它们超过了MySQL设置中设置的最大大小(最大临时表大小或堆大小 - 请查看mysqlperformanceblogs.com以获取有关这些设置的更多信息)。第二个也是更重要的一个是:
这可能会造成重大的性能损失,如果您的服务器正在采取任何措施,甚至会导致I / O瓶颈。
检查您的任何列是否属于此数据类型。如果是,您可以尝试重写查询,以便不创建临时表(我认为分组总是会导致它们),或者尝试不选择这些。另一种策略是将其分解为几个可能在很短的时间内执行的较小查询。
祝你好运!答案 8 :(得分:0)
我可能完全错了,但是当你改变
时会发生什么WHERE kp.profile_id IN (139)
到
WHERE kp.profile_id = 139
答案 9 :(得分:0)
试试这个:
SELECT i.id
FROM ((items AS i
INNER JOIN items_keywords AS ik ON ( i.id = ik.item_id ))
INNER JOIN keywords AS k ON ( k.id = ik.keyword_id ))
INNER JOIN keywords_profiles AS kp ON ( k.id = kp.keyword_id AND kp.profile_id = 139)
ORDER BY i.timestamp DESC
LIMIT 20;
答案 10 :(得分:0)
查看问题评论中的pastie.org链接:
items.source_id int(4)
加入sources.id int(16)
items.id int(16)
到itemskeywords.item_id int(11)
在这些情况下,我看不出两个字段有不同宽度的任何充分理由
我意识到这些只是显示宽度,列可以存储的实际数字范围仅由INT部分确定,但MySQL 6.0 reference manual表示:
请注意,如果存储较大的值 比整数中的显示宽度 专栏,您可能会遇到问题 当MySQL生成临时表时 对于一些复杂的连接,因为在 这些案例MySQL假设 数据适合原始列 宽度。
从您引用的粗略数字来看,您看起来并没有超出任何ID列的显示宽度。你也可以整理这些不一致的方法,只是为了消除另一个可能的错误。
如果您不需要显示宽度,也可以完全删除显示宽度
修改强>
我猜想数据库的原始作者可能认为int(4)
意味着“一个最多4位数的整数”,而它实际上意味着“-2147483648和2147482647之间的整数显示为至少4如果需要“
像authors.refreshed int(20)
或items.timestamp int(30)
这样的定义并没有多大意义,因为int
只能有10个数字加上符号。即使bigint
也不能超过20个字符。也许原作者认为int(4)
类似于varchar(4)
?
答案 11 :(得分:0)
尝试表格的备份副本。之后将原始表重命名为其他表,将新表重命名为原始表,然后再次使用新旧命名的表...
或者您可以尝试修复表格,但这并不总是有帮助。
编辑:伙计,这是一个老问题......
答案 12 :(得分:-1)
问题似乎是它甚至在尝试执行where子句之前必须在每个表上进行完全连接。这可能导致每个表500k行,你正在查看它在内存中填充的数百万行。我会尝试将JOINS更改为LEFT JOINS USING()。