我有一个表结构,可以总结如下:
pagegroup
* pagegroupid
* name
有3600行
page
* pageid
* pagegroupid
* data
引用pagegroup; 有10000行; 每页组可以有1-700行之间的任何内容; 数据列的类型为mediumtext,该列包含每行100k - 200kbytes的数据
userdata
* userdataid
* pageid
* column1
* column2
* column9
参考页面; 有大约300,000行; 每页可以有大约1-50行
上面的结构是相当直接的转发,问题是从userdata到页面组的连接非常非常慢,即使我已经索引了应该被索引的所有列。运行此类连接的查询所需的时间(userdata inner_join page inner_join pagegroup)超过3分钟。考虑到我根本没有选择数据列这一事实,这非常慢。查询示例耗时太长:
SELECT userdata.column1, pagegroup.name
FROM userdata
INNER JOIN page USING( pageid )
INNER JOIN pagegroup USING( pagegroupid )
请帮助解释为什么需要这么长时间,我该怎么做才能让它更快。
按照胡言乱语说明回复:
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE userdata ALL pageid 372420
1 SIMPLE page eq_ref PRIMARY,pagegroupid PRIMARY 4 topsecret.userdata.pageid 1
1 SIMPLE pagegroup eq_ref PRIMARY PRIMARY 4 topsecret.page.pagegroupid 1
SELECT
u.field2, p.pageid
FROM
userdata u
INNER JOIN page p ON u.pageid = p.pageid;
/*
0.07 sec execution, 6.05 sec fecth
*/
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE u ALL pageid 372420
1 SIMPLE p eq_ref PRIMARY PRIMARY 4 topsecret.u.pageid 1 Using index
SELECT
p.pageid, g.pagegroupid
FROM
page p
INNER JOIN pagegroup g ON p.pagegroupid = g.pagegroupid;
/*
9.37 sec execution, 60.0 sec fetch
*/
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE g index PRIMARY PRIMARY 4 3646 Using index
1 SIMPLE p ref pagegroupid pagegroupid 5 topsecret.g.pagegroupid 3 Using where
如果遇到诸如此类的性能问题,请将中/长文本列保留在单独的表中。
答案 0 :(得分:4)
userdata表中columnX的数据类型和用途是什么?应该注意的是,任何文本数据类型(即排除char,varchar)都会强制在磁盘上创建任何临时表。既然你在没有条件,分组或排序的情况下进行直接连接,它可能不需要任何临时表,除了聚合最终结果。
如果您向我们展示如何创建索引,我认为这也会非常有用。需要记住的一点是,虽然InnoDB将表的主键连接到每个索引,但MyISAM却没有。这意味着,如果您将列 name 编入索引并使用LIKE搜索它,但仍希望获取该页组的 id ;然后查询仍然需要访问该表以获取 id ,而不是能够从索引中检索它。
这意味着,在您的情况下,如果我理解您对 apphacker 的正确评论,就是获取每个用户页面组的名称。查询优化器希望使用索引进行连接,但是对于每个结果,它还需要访问该表以检索页组名称。如果 name 上的数据类型不大于中等varchar,即没有文本,您还可以创建一个索引(id,name),这将使查询能够直接从索引中获取名称。 / p>
作为最后一次尝试,您指出如果mediumtext不在页表中,整个查询可能会更快。
这样可以让您更快地加入,因为Pages中的列没有占用太多空间。然后,当您需要显示某个页面时,您可以使用pageId-column上的PageData表连接,以获取显示特定页面所需的数据。
答案 1 :(得分:2)
找出MySQL对您的查询执行的操作的简单方法是让它向您解释查询。运行此命令并查看输出:
EXPLAIN SELECT userdata.column1, pagegroup.name
FROM userdata
INNER JOIN page USING( pageid )
INNER JOIN pagegroup USING( pagegroupid )
MySQL将告诉您它处理查询的顺序以及它使用的索引。您创建索引的事实并不意味着MySQL实际使用它们。
另见Optimizing queries with EXPLAIN
修改强>
EXPLAIN的输出看起来很好。它在userdata表上执行全表扫描,但这是正常的,因为您要返回其中的所有行。优化此方法的最佳方法是重新考虑您的应用程序。你真的需要返回所有372K行吗?
答案 2 :(得分:2)
我假设userdata表非常大并且不适合内存。 MySQL必须从硬盘读取整个表,即使它只需要两个小列。
您可以尝试通过定义包含查询所需内容的索引来消除扫描整个表的需要。这样,索引不是一种方便搜索主表的方法,但它是表本身的简写版本。 MySQL只需要从磁盘读取速记表。
索引可能如下所示:
column1, pageid
这必须是非群集的,否则它将成为大表的一部分,从而破坏其目的。有关MySQL如何决定群集索引的想法,请参阅this page。最简单的方法似乎是确保在pageid上有一个主键,它将被聚集,因此辅助column1 + pageid索引将是非聚集的。
答案 3 :(得分:1)
一个可能的问题是MySQL每个查询只使用一个索引,也许你没有这些列的单个索引 - 或者MySQL的查询优化器没有选择它。 EXPLAIN SELECT
& c告诉你什么?
答案 4 :(得分:1)
我会先打破查询,弄清楚是否有一个慢速和一个快速部分,或两者都很慢(对不起,我不喜欢USING语法,所以我打算用ON):
SELECT
u.userdata, p.pageid
FROM
userdata u
INNER JOIN page p ON u.pageid = p.pageid
SELECT
p.pageid, g.pagegroupid
FROM
page
INNER JOIN pagegroup g ON p.pagegroupid = g.pagegroupid
这给你带来了什么?使用EXPLAIN EXTENDED
运行这些将提供其他提示。
答案 5 :(得分:1)
看起来你正在对userdata
上的所有行进行连接,然后尝试选择所有内容。这是page
pagegroup
中的userdata
WHERE
。 LIMIT
条款在哪里?没有userdata
,您想要多少结果?为什么不在explain
结果的{{1}}行中计算行数,这会加快查询速度。嘿。