【问题标题】:MySQL MyISAM table performance... painfully, painfully slowMySQL MyISAM 表的性能......非常缓慢,非常缓慢
【发布时间】:2010-10-24 23:28:58
【问题描述】:

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

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

故事的寓意

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

【问题讨论】:

    标签: sql mysql performance query-optimization myisam


    【解决方案1】:

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

    【讨论】:

      【解决方案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 行吗?

      【讨论】:

      • 我已经修改了我的问题并添加了解释命令的结果。似乎使用了正确的索引,但仍然需要 128 秒才能执行。
      【解决方案3】:

      我将从分解查询开始,以确定是否有一个慢的部分和一个快的部分,或者两者是否都很慢(抱歉,我不喜欢 USING 语法,所以我将使用开):

      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 运行这些将提供额外的提示。

      【讨论】:

      • 我已经发布了两个查询的输出。解释扩展返回不同语法的相似查询。
      • 看起来第二个查询是麻烦制造者。请包括信息你有什么索引。
      • 主键 + 连接中使用的所有键都被索引。对于这三个表,我在 pagegroup.pagegroupid (PK)、page.pageid (PK)、page.pagegroupid (INDEX)、userdata.userdataid (PK)、userdata.pageid (INDEX)、userdata.column1 (INDEX) 上有索引/跨度>
      【解决方案4】:

      看起来您正在对userdata 上的所有行进行连接,然后尝试选择所有内容。这是pagegroupuserdata 中的每个pageWHERE 子句在哪里?没有LIMIT,你想要多少个结果?为什么不在explain 结果中对userdata 行倒计时,这样可以加快查询速度。呵呵。

      【讨论】:

      • 我需要从用户数据中选择列的转储以及 pagegroup.name 以供交叉引用。如果页表中没有“mediumtext”列,我相信它应该足够快。
      • 也许你想开始在日志中记录这些信息而不是使用 SQL,也许考虑使用 SQL 以外的东西来处理这些数据,比如非规范化的 berkeley db 或其他东西。跨度>
      【解决方案5】:

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

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

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

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

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

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

      【讨论】:

      • 答案:#1 - 是的,我没有选择数据列; #2 - 是的,这是一个“应该”工作的解决方法,另一种可能是对表进行一点反规范化并将 pagegroupid 添加到 userdata 但问题是表结构或查询中是否有问题。
      • 大部分列都是 varchar 100,包括 pagesource.name 和 userdata.field2
      • 在我将“数据”列移动到与页表一对一关联的单独表“pagetemp”中后,它起作用了。否则所有索引都不起作用。
      【解决方案6】:

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

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

      索引可能如下所示:

      column1, pageid
      

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

      【讨论】:

      • 我尝试在页表上创建一个两列索引(pageid-sourceid),以便在 userdata 和 pagesource 之间创建一个短路,从而减少执行时间但不多。
      • 您在上面的评论说使用 1:1 的桌子性能会更好。这似乎确实表明(pageid,sourceid)上的索引以某种方式聚集在一起。哦,好吧……我猜问题解决了。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-11-11
      • 2019-08-11
      • 2010-10-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多