【问题标题】:What are some optimization techniques for MySQL table with 300+ million records?300+百万条记录的MySQL表有哪些优化技术?
【发布时间】:2025-11-28 18:20:03
【问题描述】:

我正在考虑将一些来自 JVM 的 JMX 数据存储在许多服务器上大约 90 天。该数据将是诸如堆大小和线程数之类的统计信息。这意味着其中一张表将有大约 3.88 亿条记录。

根据这些数据,我正在构建一些图表,以便您可以比较从 Mbean 检索到的统计数据。这意味着我将使用时间戳每隔一段时间抓取一些数据。

所以真正的问题是,是否可以优化表或查询,以便您可以在合理的时间内执行这些查询?

谢谢,

乔什

【问题讨论】:

    标签: mysql database database-design optimization jmx


    【解决方案1】:
    1. 解释您的选择查询
    2. LIMIT 1 获取唯一行时 SELECT * FROM user WHERE state = 'Alabama' // 错误 SELECT 1 FROM user WHERE state = 'Alabama' LIMIT 1

    3. 索引搜索字段 索引不仅仅用于主键或唯一键。如果您要搜索表中的任何列,您几乎应该始终为它们编制索引。

    4. 为连接建立索引并使用相同的列类型 如果您的应用程序包含许多 JOIN 查询,则需要确保您连接的列在两个表上都有索引。这会影响 MySQL 内部优化连接操作的方式。

    5. 请勿按 RAND() 订购 如果你真的需要从你的结果中随机行,有更好的方法来做到这一点。当然,它需要额外的代码,但您可以防止瓶颈随着数据的增长而呈指数级恶化。问题是,MySQL 必须对表中的每一行执行 RAND() 操作(这需要处理能力),然后才能对其进行排序并只给你 1 行。

    6. 在 VARCHAR 上使用 ENUM ENUM 类型的列非常快速且紧凑。在内部,它们像 TINYINT 一样存储,但它们可以包含和显示字符串值。

    7. 如果可以,请使用 NOT NULL 除非您有非常具体的理由使用 NULL 值,否则您应该始终将列设置为 NOT NULL。

      “NULL 列需要在行中额外的空间来记录它们的值是否为 NULL。对于 MyISAM 表,每个 NULL 列多占用一位,四舍五入到最接近的字节。”

    8. 将 IP 地址存储为 UNSIGNED INT 在您的查询中,您可以使用 INET_ATON() 将 IP 转换为整数,反之亦然。 PHP 中也有类似的函数叫做 ip2long() 和 long2ip()。

    【讨论】:

      【解决方案2】:

      一些建议。

      您可能要对这些东西运行聚合查询,因此在将数据加载到表中之后(或同时),您应该预先聚合数据,例如按小时或按用户预先计算总计,或者按周,无论如何,您会得到这个想法,并将其存储在您用于报告图表的缓存表中。如果您可以将数据集缩小一个数量级,那么对您有好处!

      这意味着我将使用时间戳每隔一段时间抓取一些数据。

      所以这意味着您只使用过去 X 天的数据?

      如果要删除几千万行,从表中删除旧数据可能会非常缓慢,分区非常适合(只需删除那个旧分区)。它还将同一时间段的所有记录在磁盘上紧密地组合在一起,因此缓存效率更高。

      现在,如果您使用 MySQL,我强烈建议您使用 MyISAM 表。你没有防崩溃或事务和锁定是愚蠢的,但表的大小比 InnoDB 小得多,这意味着它可以放入 RAM,这意味着访问速度更快。

      由于大型聚合可能涉及大量相当连续的磁盘 IO,因此像 RAID10(或 SSD)这样的快速 IO 系统是一个优势。

      是否有优化表或查询以便您可以执行这些查询 在合理的时间内?

      这取决于表和查询;如果不了解更多信息,无法提供任何建议。

      如果您需要具有大聚合和连接的复杂报告查询,请记住 MySQL 不支持任何花哨的 JOIN、散列聚合或其他任何真正有用的东西,基本上它唯一能做的就是嵌套循环索引扫描,它是在缓存表上很好,如果涉及到一些随机访问,在其他情况下绝对很糟糕。

      我建议您使用 Postgres 进行测试。对于大型聚合,更智能的优化器确实可以很好地工作。

      例子:

      CREATE TABLE t (id INTEGER PRIMARY KEY AUTO_INCREMENT, category INT NOT NULL, counter INT NOT NULL) ENGINE=MyISAM;
      INSERT INTO t (category, counter) SELECT n%10, n&255 FROM serie;
      

      (系列包含 16M 行,n = 1 .. 16000000)

      MySQL    Postgres     
      58 s     100s       INSERT
      75s      51s        CREATE INDEX on (category,id) (useless)
      9.3s     5s         SELECT category, sum(counter) FROM t GROUP BY category;
      1.7s     0.5s       SELECT category, sum(counter) FROM t WHERE id>15000000 GROUP BY category;
      

      在像这样的简单查询中,pg 大约快 2-3 倍(如果涉及复杂的连接,差异会更大)。

      【讨论】:

        【解决方案3】:

        如果您使用的是 MYSQL 5.1,则可以使用新功能。 但请注意,它们包含很多错误。

        首先你应该使用索引。 如果这还不够,您可以尝试使用分区来拆分表。

        如果这也不起作用,您也可以尝试负载平衡。

        【讨论】:

          【解决方案4】:

          你可以做几件事:

          1. 构建索引以匹配您正在运行的查询。运行 EXPLAIN 以查看正在运行的查询类型,并确保它们都尽可能使用索引。

          2. 对您的表进行分区。分区是一种通过特定(聚合)键将大表拆分为几个较小表的技术。 MySQL 从ver. 5.1 内部支持此功能。

          3. 如有必要,构建汇总表以缓存查询中成本较高的部分。然后针对汇总表运行查询。同样,临时内存表可用于存储表的简化视图作为预处理阶段。

          【讨论】:

            【解决方案5】:

            好吧,首先,我建议您使用“离线”处理来生成“图表就绪”数据(对于大多数常见情况),而不是尝试按需查询原始数据。

            【讨论】:

              【解决方案6】:

              3 条建议:

              1. 索引
              2. 索引
              3. 索引

              附言对于时间戳,您可能会遇到性能问题——取决于 MySQL 如何在内部处理 DATETIME 和 TIMESTAMP,最好将时间戳存储为整数。 (自 1970 年以来的 # 秒或其他时间)

              【讨论】: