【问题标题】:Optimize SQL query on large-ish table优化大型表的 SQL 查询
【发布时间】:2010-09-11 23:20:56
【问题描述】:

首先,这个问题是关于 MySQL 3.23.58 的,所以请注意。

我有 2 个具有以下定义的表:

Table A: id INT (primary), customer_id INT, offlineid INT

Table B: id INT (primary), name VARCHAR(255)

现在,表 A 包含 65k+ 条记录,而表 B 包含约 40 条记录。除了2个主键索引外,A表的offlineid字段上也有一个索引。每个表的字段比较多,但是不相关(我看到的,问一下必要)对于这个查询。

我首先看到了以下查询(查询时间:~22 秒):

SELECT b.name, COUNT(*) AS orders, COUNT(DISTINCT(a.kundeid)) AS leads
FROM katalogbestilling_katalog a, medie b
WHERE a.offlineid = b.id
GROUP BY b.name

现在,medie 中的每个 id 都与不同的名称相关联,这意味着您可以按 id 和名称进行分组。一些来回的测试让我明白了这一点(查询时间:~6 秒):

SELECT a.name, COUNT(*) AS orders, COUNT(DISTINCT(b.kundeid)) AS leads
FROM medie a
INNER JOIN katalogbestilling_katalog b ON a.id = b.offline
GROUP BY b.offline;

有什么办法可以将它调低到“即时”时间(最坏的情况下最多 1 秒)?我在offlineid上添加了索引,但除此之外和查询的重新安排,我不知道该怎么办。 EXPLAIN 查询显示查询正在使用 fileshort(原始查询也使用临时表)。欢迎所有建议!

【问题讨论】:

    标签: mysql sql optimization


    【解决方案1】:

    尝试优化服务器本身。有关最重要的变量,请参阅this post by Peter Zaitsev。有些是特定于 InnoDB 的,而另一些是针对 MyISAM 的。您没有提到您使用的引擎在这种情况下可能相关(例如,MyISAM 中的 count(*) 比 InnoDB 中的要快得多)。 Here is another post from same blog,还有一篇来自MySQL Forge的文章

    【讨论】:

      【解决方案2】:

      尝试添加索引到 (offlineid, kundeid)

      我在目录中添加了 180,000 个 BS 行,在 medie 中添加了 30,000 个 BS 行(katalog offlineid's 对应于 medie id's 和一些重叠的 kundeid's 以确保 disinct 计数有效)。请注意,这是在 mysql 5 上,所以如果你没有类似的结果,mysql 3 可能是你的罪魁祸首,但据我回忆,mysql 3 应该能够很好地处理这个问题。

      我的桌子:

      CREATE TABLE `katalogbestilling_katalog` (
        `id` int(11) NOT NULL auto_increment,
        `offlineid` int(11) NOT NULL,
        `kundeid` int(11) NOT NULL,
        PRIMARY KEY  (`id`),
        KEY `offline_id` (`offlineid`,`kundeid`)
      ) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=60001 ;
      
      CREATE TABLE `medie` (
        `id` int(11) NOT NULL auto_increment,
        `name` varchar(255) NOT NULL,
        PRIMARY KEY  (`id`)
      ) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=30001 ;
      

      我的查询:

      SELECT b.name, COUNT(*) AS orders, COUNT(DISTINCT(a.kundeid)) AS leads
      FROM medie b
      INNER JOIN katalogbestilling_katalog a ON b.id = a.offlineid
      GROUP BY a.offlineid
      LIMIT 0 , 30
      
      
      "Showing rows 0 - 29 (30,000 total, Query took 0.0018 sec)"
      

      还有解释:

      id:  1
      select_type:    SIMPLE
      table: a
      type: index
      possible_keys:  NULL
      key:    offline_id
      key_len:    8
      ref: NULL
      rows: 180000
      Extra: Using index
      
      id: 1
      select_type:    SIMPLE
      table: b
      type: eq_ref
      possible_keys:  PRIMARY
      key:    PRIMARY
      key_len:    4
      ref: test.a.offlineid
      rows: 1
      Extra:
      

      【讨论】:

        【解决方案3】:

        您可以尝试确保在每个表上都定义了覆盖索引。覆盖索引只是一个索引,其中在选择中请求或在连接中使用的每一列都包含在索引中。这样,引擎只需读取索引条目,而不必执行相应的行查找来获取任何未包含在索引中的请求列。我在 Oracle 和 MS SqlServer 中成功使用了这种技术。

        查看您的查询,您可以尝试:

        medie.id、medie.name 的一个索引
        katalogbestilling_katalog.offlineid、katalogbestilling_katalog.kundeid 的一个索引

        应按这些顺序为索引定义列。是否可以使用索引会有所不同。

        更多信息在这里:

        Covering Index Info

        【讨论】:

        • 从 ovwr 6 秒缩短到 1.2 秒 - 创造了奇迹。谢谢!
        【解决方案4】:

        kundeid 是如何定义的?查看两个表的完整架构(由 MySQL 生成,即带有索引)以及 EXPLAIN 的输出以及上述查询会很有帮助。

        调试此问题并找出瓶颈的最简单方法是开始从查询中逐个删除字段并测量运行所需的时间(请记住在运行每个查询之前运行 RESET QUERY CACHE )。在某些时候,您会看到执行时间显着下降,然后您就确定了瓶颈。例如:

        SELECT b.name, COUNT(*) AS orders, COUNT(DISTINCT(a.kundeid)) AS leads
        FROM katalogbestilling_katalog a, medie b
        WHERE a.offlineid = b.id
        GROUP BY b.name
        

        可能变成

        SELECT b.name, COUNT(DISTINCT(a.kundeid)) AS leads
        FROM katalogbestilling_katalog a, medie b
        WHERE a.offlineid = b.id
        GROUP BY b.name
        

        消除“订单”成为瓶颈的可能性,或

        SELECT b.name, COUNT(*) AS orders
        FROM katalogbestilling_katalog a, medie b
        WHERE a.offlineid = b.id
        GROUP BY b.name
        

        从等式中消除“线索”。这将引导您走向正确的方向。

        更新:我不建议从最终查询中删除任何数据。只需删除它们以减少变量的数量,同时寻找瓶颈。鉴于您的评论,我明白了

        SELECT b.name
        FROM katalogbestilling_katalog a, medie b
        WHERE a.offlineid = b.id
        GROUP BY b.name
        

        仍然表现不佳?这显然意味着它要么是未优化的连接,要么是 group by(您可以通过删除 group by 来测试 - JOIN 仍然很慢,在这种情况下,这是您需要修复的问题,或者它不会- 在这种情况下,显然是 GROUP BY)。你能发布输出的

        EXPLAIN SELECT b.name
        FROM katalogbestilling_katalog a, medie b
        WHERE a.offlineid = b.id
        GROUP BY b.name
        

        以及表模式(以便于调试)?

        更新 #2

        还有一种可能是您的所有索引都已正确创建,但是当涉及到最大内存使用量或类似的东西迫使它使用磁盘排序时,您的 mysql 安装配置错误。

        【讨论】:

        • 我已经尝试过了,删除任何一个 COUNT 都不会显着减少查询时间。我真正在寻找的是某种通过添加索引或以某种方式重写查询来优化的方法(因为我需要列出的所有数据)。
        【解决方案5】:

        您的第二个查询很好,65k+40k 行不是很大:)

        在 katalogbestilling_katalog.offline 列上添加一个新索引,它会为您运行得更快。

        【讨论】:

          【解决方案6】:

          如果查询运行的频率足以保证开销,请在表 A 上创建一个索引,其中包含查询中使用的字段。然后可以从索引中读取所有结果,而不必扫描表。

          也就是说,我所有的经验都是基于 MSSQL,所以可能行不通。

          【讨论】:

            【解决方案7】:

            不幸的是,mysql 3 不支持子查询。我怀疑通常是旧版本导致性能下降。

            【讨论】:

              【解决方案8】:

              如果删除内部连接并用嵌套的 select 语句替换它,同时删除 count(*) 并用 PK 替换,性能可能会略有提高。

              SELECT a.name, COUNT(*) AS orders, COUNT(DISTINCT(b.kundeid)) AS leads FROM medie aINNER JOIN katalogbestilling_katalog b ON a.id = b.offline GROUP BY b.offline;

              SELECT a.name, COUNT(a.id) AS orders, (SELECT COUNT(kundeid) FROM katalogbestilling_katalog b WHERE b.offline = a.id) AS Leads FROM medie a;

              【讨论】:

                【解决方案9】:

                我猜你的主要问题是你使用的是旧版本的 MySQL。也许 MySQL 3 不喜欢 COUNT(DISTINCT())。

                或者,它可能只是系统性能。你有多少内存?

                不过,MySQL 3 确实很老了。我至少会组装一个测试系统,看看新版本是否能更快地运行该查询。

                【讨论】:

                • 我知道这一点,但我被聘为外部顾问(由于缺乏更好的词)来优化性能,这可能超出了工作范围。
                • 我认为有一些原因。如果所有其他方法都失败了,那么能够指出 MySQL 3 的特定问题可能会有所帮助。不管怎样,祝你好运。
                猜你喜欢
                • 1970-01-01
                • 2017-07-22
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2013-07-18
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多