3

我有一个表结构,可以总结如下:

pagegroup
* pagegroupid
* name

有 3600 行

page
* pageid
* pagegroupid
* data

参考页组;有 10000 行;每个页面组可以有 1-700 行之间的任何内容;数据列的类型为 mediumtext,该列每行包含 100k - 200kbytes 数据

userdata
* userdataid
* pageid
* column1
* column2
* column9

参考页面;大约有 300,000 行;每页可以有大约 1-50 行

上面的结构非常简单,问题是从用户数据到页面组的连接非常非常慢,即使我已经索引了所有应该索引的列。为此类联接(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 )

请帮助解释为什么需要这么长时间以及我能做些什么来使它更快。

编辑#1

解释以下乱码返回:

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

编辑#2

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

故事的道德启示

如果遇到诸如此类的性能问题,请将中/长文本列保留在单独的表中。

4

6 回答 6

4

userdata表中columnX的数据类型和用途是什么?需要注意的是,任何文本数据类型(即不包括 char、varchar)都会强制在磁盘上创建任何临时表。现在,由于您正在执行没有条件、分组或排序的直接连接,它可能不需要任何临时表,除了聚合最终结果。

如果您向我们展示您的索引是如何创建的,我认为这也会非常有帮助。要记住的一件事是,虽然 InnoDB 将表的主键连接到每个索引,但 MyISAM 没有。这意味着如果你索引列并用 LIKE 搜索它,但仍然想获取页组的id;然后查询仍然需要访问表以获取id,而不是能够从索引中检索它。

这意味着,在您的情况下,如果我正确理解您对apphacker的评论,就是获取每个用户页面组的名称。查询优化器希望使用索引进行连接,但对于每个结果,它还需要访问表以检索页组名称。如果name上的数据类型不大于中等 varchar,即没有文本,您还可以创建一个索引 (id, name),使查询能够直接从索引中获取名称。

作为最后的尝试,您指出如果介质文本不在页表中,整个查询可能会更快。

  1. 我想这个列被排除在您正在运行的查询之外?
  2. 您还可以尝试将页面数据与页面“配置”分开,即它属于哪个组。然后你可能会有类似的东西:
    • 页面
      • pageId
      • pageGroupId
    • 页面数据
      • pageId
      • 数据

这有望使您更快地加入,因为 Pages 中的任何列都不会占用太多空间。然后,当您需要显示某个页面时,您可以加入 pageId 列上的 PageData 表以获取显示特定页面所需的数据。

于 2009-05-09T08:53:36.667 回答
2

弄清楚 MySQL 对您的查询做了什么的简单方法是让它向您解释查询。运行它并查看输出:

EXPLAIN SELECT userdata.column1, pagegroup.name
FROM userdata
INNER JOIN page USING( pageid )
INNER JOIN pagegroup USING( pagegroupid )

MySQL 会告诉你它处理查询的顺序以及它使用的索引。您创建索引的事实并不意味着 MySQL 实际使用它们。

另请参阅使用 EXPLAIN 优化查询

编辑

EXPLAIN 的输出看起来不错。它对 userdata 表进行全表扫描,但这是正常的,因为您想返回其中的所有行。优化这一点的最佳方法是重新考虑您的应用程序。您真的需要返回所有 372K 行吗?

于 2009-05-09T07:11:56.207 回答
2

我假设 userdata 表非常大并且不适合内存。MySQL 必须从硬盘读取整个表,即使它只需要两个小列。

您可以通过定义包含查询所需的所有内容的索引来尝试消除扫描整个表的需要。这样,索引就不是一种便于搜索主表的方法,而是表本身的简写版本。MySQL 只需从磁盘读取速记表。

索引可能如下所示:

column1, pageid

这必须是非集群的,否则它将成为大表的一部分,违背了它的目的。有关 MySQL 如何决定要集群的索引的想法,请参阅此页面。最简单的方法似乎是确保您在 pageid 上有一个主键,它将被聚集,因此辅助 column1+pageid 索引将是非聚集的。

于 2009-05-09T09:08:22.137 回答
1

一个可能的问题是 MySQL 每个查询只使用一个索引,并且您可能没有包含这些列的单个索引——或者 MySQL 的查询优化器没有选择它。EXPLAIN SELECT&c 在这里告诉你什么?

于 2009-05-09T07:10:16.570 回答
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将提供额外的提示。

于 2009-05-09T07:45:55.720 回答
1

看起来您正在对所有行进行连接userdata,然后尝试选择所有内容。那是每page一个pagegroupwith userdata。条款在哪里WHERE?没有LIMIT,您想要多少个结果?userdata为什么不将结果中的行倒数explain,这应该会加快查询速度。呵呵。

于 2009-05-09T07:53:40.633 回答