【问题标题】:MySQL Performance issues with large amounts of data大量数据的 MySQL 性能问题
【发布时间】:2011-09-16 18:16:15
【问题描述】:

我有一个正在工作的软件项目让我发疯。这是我们的问题:我们有一系列数据联系人需要每秒记录一次。它需要包括时间、方位(360-1080 字节的数组)、范围和一些其他字段。我们的系统还需要能够将这些数据存储长达 30 天。实际上,最多可以有 100 个不同的联系人,因此在 30 天内最多可以有大约 150,000,000 到大约 1,000,000,000 个不同的点。

我正在尝试考虑存储所有这些数据并在以后检索的最佳方法。我的第一个想法是使用一些 RDBMS,比如 MySQL。作为一名嵌入式 C/C++ 程序员,我很少有使用 MySQL 处理如此大数据集的经验。我在小型数据集上涉足过它,但几乎没有那么大。我为两个将存储一些数据的表生成了以下架构:

CREATE TABLE IF NOT EXISTS `HEADER_TABLE` (
  `header_id` tinyint(3) unsigned NOT NULL auto_increment,
  `sensor` varchar(10) NOT NULL,
  `bytes` smallint(5) unsigned NOT NULL,
  PRIMARY KEY  (`header_id`),
  UNIQUE KEY `header_id_UNIQUE` (`header_id`),
  UNIQUE KEY `sensor_UNIQUE` (`sensor`)
) ENGINE=MyISAM AUTO_INCREMENT=0 DEFAULT CHARSET=latin1;

CREATE TABLE IF NOT EXISTS `RAW_DATA_TABLE` (
  `internal_id` bigint(20) NOT NULL auto_increment,
  `time_sec` bigint(20) unsigned NOT NULL,
  `time_nsec` bigint(20) unsigned NOT NULL,
  `transverse` bit(1) NOT NULL default b'0',
  `data` varbinary(1080) NOT NULL,
  PRIMARY KEY  (`internal_id`,`time_sec`,`time_nsec`),
  UNIQUE KEY `internal_id_UNIQUE` (`internal_id`),
  KEY `time` (`time_sec`)
  KEY `internal_id` (`internal_id`)
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;

CREATE TABLE IF NOT EXISTS `rel_RASTER_TABLE` (
  `internal_id` bigint(20) NOT NULL auto_increment,
  `raster_id` int(10) unsigned NOT NULL,
  `time_sec` bigint(20) unsigned NOT NULL,
  `time_nsec` bigint(20) unsigned NOT NULL,
  `header_id` tinyint(3) unsigned NOT NULL,
  `data_id` bigint(20) unsigned NOT NULL,
  PRIMARY KEY  (`internal_id`, `raster_id`,`time_sec`,`time_nsec`),
  KEY `raster_id` (`raster_id`),
  KEY `time` (`time_sec`),
  KEY `data` (`data_id`)
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;

标题表仅包含 10 行并且是静态的。它只是告诉原始数据来自哪个传感器,以及该类型传感器输出的字节数。 RAW_DATA_TABLE 本质上存储原始方位数据(一个 360-1080 字节的数组,每度最多表示三个样本)。 rel_RASTER_TABLE 保存 RAW_DATA_TABLE 的元数据,可以有多个联系人引用相同的原始数据行。在 rel_RASTER_TABLE 中找到的 data_id 指向 RAW_DATA_TABLE 中某行的 internal_id,我这样做是为了减少所需的写入量。

显然,正如您可能知道的那样,我在从该数据库读取和删除时遇到了性能问题。我们软件的操作员可以看到实时数据,还可以进入重建模式并覆盖过去的数据范围,例如过去一周。我们的后端日志服务器抓取历史记录行并通过 CORBA 接口将它们发送到显示器。虽然所有这一切都在发生,但我有一个工作线程,它一次删除 1000 行数据超过 30 天。如果会话运行时间超过 30 天,就会出现这种情况。

我们目前实施的系统适用于较小的数据集,但不适用于大型数据集。我们的 select 和 delete 语句可能需要 2 分钟以上的时间才能返回结果。这完全扼杀了我们实时消费者线程的性能。我怀疑我们没有正确设计我们的模式,选择错误的键,没有正确优化我们的 SQL 查询,或者每个子集。除非其他操作运行时间过长,否则我们的写入不会受到影响。

这是我们用来获取历史数据的示例 SQL 查询:

SELECT 
  rel_RASTER_TABLE.time_sec, 
  rel_RASTER_TABLE.time_nsec, 
  RAW_DATA_TABLE.transverse, 
  HEADER_TABLE.bytes, 
  RAW_DATA_TABLE.data 
FROM 
  RASTER_DB.HEADER_TABLE, 
  RASTER_DB.RAW_DATA_TABLE, 
  RASTER_DB.rel_RASTER_TABLE 
WHERE 
  rel_RASTER_TABLE.raster_id = 2952704 AND 
  rel_RASTER_TABLE.time_sec >= 1315849228 AND 
  rel_RASTER_TABLE.time_sec <= 1315935628 AND 
  rel_RASTER_TABLE.data_id = RAW_DATA_TABLE.internal_id AND 
  rel_RASTER_TABLE.header_id = HEADER_TABLE.header_id;

对于这个问题如此冗长,我提前道歉,但我已经利用了其他资源,这是我最后的手段。我想我会尽量做到描述性强,你们第一眼看到有什么方法可以改进我们的设计吗?或者,无论如何我们都可以针对如此大的数据集优化我们的选择和删除语句?我们目前正在运行 RHEL 作为操作系统,遗憾的是无法更改服务器上的硬件配置(4 GB RAM,四核)。我们正在使用 C/C++ 和 MySQL API。任何速度改进都将是非常有益的。如果您需要我澄清任何事情,请告诉我。谢谢!

编辑:顺便说一句,如果您不能提供具体帮助,也许您可​​以将我链接到您遇到的一些用于优化 SQL 查询、架构设计或 MySQL 调优的优秀教程?

【问题讨论】:

  • 数据是否每秒都在变化?是否存在可以跳过存储数据的暂停期?
  • 如果您期望即时结果,那么在 mysql 中运行超过 1 亿到 10 亿条记录的范围查询是一件痛苦的事情。您的“实时”要求是否非常严格?如果没有,您可能会将此处理中的一些卸载到批处理作业,这些作业会为某些预选范围生成聚合。您是否考虑过对数据进行分区。有这种可能吗?
  • 因为您是 MySQL 新手。你熟悉解释计划吗? dev.mysql.com/doc/refman/5.0/en/explain.html dev.mysql.com/doc/refman/5.0/en/explain-output.html 这篇文章也可能有帮助:dev.mysql.com/doc/refman/5.0/en/range-optimization.html 哦,是的,考虑按照建议对数据进行非规范化。在具有 1 亿条记录的表上加入是一个杀手。
  • 您不能使用某种只读缓存,否则您的用户会无法接受吗?如果是,您可以查看类似 infinidb.org 的缓存数据(它比标准 mysql 数据库从大量行中检索信息要快得多)。
  • 不仅仅是优化器,我希望您可以查看查询的解释计划并检查您是否看到全表扫描或未使用的索引,并添加正确类型的索引。查询优化几乎是一门艺术,需要耐心。我会按照我之前的建议来研究分区。 @thekashyap 概述了一些优点。我从未使用过任何优化器工具,只是查看了解释计划并四处游荡。以下是一些好的提示:20bits.com/articles/…

标签: c++ mysql performance


【解决方案1】:

您可以尝试的第一件事是对数据进行反规范化。在这样大小的数据集上,进行连接,即使你有索引也需要非常密集的计算。把这三张桌子变成一张桌子。当然会有重复的数据,但是没有连接它会更容易使用。第二件事,看看你能不能得到一台有足够内存的机器来容纳整个表。对于具有 24GB RAM 的机器来说,它的成本并不高(1000 美元或更少)。我不确定这是否会保存您的整个数据集,但它也将极大地帮助您获得 SSD。对于未存储在内存中的任何内容,SSD 应该可以帮助您高速访问它。第三,研究其他数据存储技术,例如 BigTable,它们旨在处理非常大的数据集。

【讨论】:

  • 这将取决于连接和索引查找(where 子句)的评估顺序。根据用例的不同,“拥有 3 个较小的表并从每个表中连接一些行”实际上可能比“拥有一个巨大的表并进行索引查找”更快。
  • 这是一个很棒的想法,我一直认为规范化是存储数据的最佳策略,我绝对可以看到对于这个特定问题来说不一定是这种情况。我必须给这个尝试...不幸的是,更改/升级硬件对我们来说是不可能的:(...也许在未来的构建中这可能是一个选择。您以前使用过 BigTable 吗?它是否适用于繁重的写作、阅读和删除?这也是我们的一个选择。
  • 是的,很多这将是反复试验。取决于您的用例的具体情况。将数据归一化可能有助于某些查询,但如果您最终不得不在将数百万行连接在一起的地方进行查询,您最好准备好等待一段时间。再次,如果您只选择非常小的数据部分,并且所有内容都被很好地索引,那么我的三表方法会很好地工作。
  • @thekashyap 我该如何检查评估序列?你有什么好的查询分析器推荐吗?
  • 不幸的是,我从未使用过 MySQL。在 Oracle 中,您可以在 OEM 中看到使用说明和/或查看非常详细的信息(例如哈希查找、读取 xxx 字节,...)。也许一些 MySQL 专家可以提供帮助。 ##edit## MySQL 见dev.mysql.com/doc/refman/5.0/en/execution-plan-information.html
【解决方案2】:

我会说在这种情况下分区是绝对必须的:

  • 大量数据
  • 新数据源源不断
  • 隐式:旧数据不断被删除。

查看this for mySQL

查看您选择的 stmt(按时间过滤),我会说时间列上的分区。

当然,您可能想根据您想要使用的频繁查询添加一些索引。

--编辑--

我看到很多人都建议了索引。我的经验是,在具有大量行的表上建立索引要么会降低性能(最终),要么需要大量资源(CPU、内存……)才能使索引保持最新。 因此,虽然我也建议添加索引,但请注意,除非您先对表进行分区,否则它绝对没用。 最后,在添加索引时遵循 symcbean 的建议(优化索引的数量和键)。

--编辑结束--

如果您不熟悉分区,请快速了解分区。

  • 通常单个表转换为单个数据文件。分区表转换为每个分区一个文件。
  • 优势
    • 插入速度更快,因为它在物理上被插入到较小的文件(分区)中。
    • 删除大量行通常会转化为删除分区(比“从 xxx 中删除时间 > 100 且时间
    • 对表分区的键使用 where 子句的查询要快得多。
    • 索引构建速度更快。

【讨论】:

    【解决方案3】:

    我对 MySQL 没有太多经验,但这里有一些先验的想法。

    您的选择是否在存储过程中?

    select 的谓词通常按照其询问的顺序进行搜索。如果磁盘上的数据被重新排序以匹配主键,那么先执行 raster id 就可以了。不过,您将支付每次插入重新订购的费用。如果数据按时间顺序存储在磁盘上,您可能希望在raster_id 之前搜索time_sec

    WHERE 
      rel_RASTER_TABLE.raster_id = 2952704 AND 
      rel_RASTER_TABLE.time_sec >= 1315849228 AND 
      rel_RASTER_TABLE.time_sec <= 1315935628 AND 
      rel_RASTER_TABLE.data_id = RAW_DATA_TABLE.internal_id AND 
      rel_RASTER_TABLE.header_id = HEADER_TABLE.header_id;
    

    您的索引不遵循搜索谓词。

    它通常会根据键创建索引。

      PRIMARY KEY  (`internal_id`, `raster_id`,`time_sec`,`time_nsec`),
      KEY `raster_id` (`raster_id`),
      KEY `time` (`time_sec`),
      KEY `data` (`data_id`)
    

    它可能没有使用主索引,因为您没有使用 internal_id。您可能希望将internal_id 设置为主键并根据您的搜索参数创建一个单独的索引。至少在raster_idtime_sec 上。

    连接是否太松?

    这可能是我对 MySQL 的缺乏经验,但我希望看到连接的条件。在这里使用 FROM 会自然加入吗?我没有看到任何指定的外键,所以我不知道它会如何合理地加入这些表。

    FROM 
      RASTER_DB.HEADER_TABLE, 
      RASTER_DB.RAW_DATA_TABLE, 
      RASTER_DB.rel_RASTER_TABLE 
    

    通常在开发这样的东西时,我会使用较小的集合并删除谓词,以确保每个步骤都符合我的期望。如果你不小心在前面撒了一张大网,后来又缩小了,你可能会掩盖一些低效率的问题。

    大多数查询优化器都有办法输出如何优化,确保它符合您的期望。其中一个 cmets 提到了解释计划,我认为这就是所谓的。

    【讨论】:

      【解决方案4】:

      如果不知道所有查询是什么,很难给出具体建议,但是查看您提供的单个查询,没有非常适合解决此问题的索引。

      实际上结构有点混乱——如果internal_id 是一个自动递增值,那么它是唯一的——为什么要在主键中添加其他内容?看起来 rel_RASTER_TABLE 的一个更合理的结构是:

      PRIMARY KEY  (`internal_id`),
      KEY (`raster_id`,`time_sec`,`time_nsec`),
      

      对于 RAW_DATA_TABLE,它的索引远非最佳应该是显而易见的。应该是:

      PRIMARY KEY  (`internal_id`,`time_sec`,`time_nsec`),
      KEY `time` (`time_sec`, `time_nsec`)
      

      请注意,删除冗余索引将加快插入/更新速度。 捕获慢查询应该会有所帮助 - 并学习如何使用“解释”来查看哪些索引是冗余/需要的。

      您还可以通过调整 mysql 实例来提高性能 - 特别是增加排序和连接缓冲区 - 尝试运行 mysqltuner

      【讨论】:

      • 哈哈哈谢谢您的评论...是的,我的表格结构很草率。我在搞乱不同的事情,碰巧复制并粘贴了似乎“最好”的模式。我的数据库技能不是最强的,如果你看不出来,我肯定需要重新学习索引。我也来看看mysqltuner。
      【解决方案5】:

      首先,我会尝试创建一个视图,其中仅包含需要在不同表之间选择的必要信息。

      顺便说一句,对于您要完成的工作,MySQL 不一定是最优化的数据库系统...查看其他解决方案,例如 Oracle、Microsoft SQL、PostgreSQL 等。此外,性能会因所使用的服务器而异用过。

      【讨论】:

      • 你得到你“支付”的东西。 :)
      • 视图在这里有什么帮助?它仍然运行相同的选择。您是否知道在这种情况下可能有效的特定视图算法?我不是在挑战你的说法。我想更好地了解您的回复。
      • @thekashyap/Alerty 你认为另一个 RDBMS 会提供更好的结果吗?如果您可以分享一些指标,它将使 OP 受益。
      • @Deep Kapadia:来自 MySQL 文档:dev.mysql.com/doc/refman/5.0/en/view-algorithms.html
      • @Alerty 我还是不明白。在这种情况下,视图如何帮助解决性能问题?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-03-16
      • 1970-01-01
      • 2012-11-21
      • 1970-01-01
      • 1970-01-01
      • 2017-07-29
      • 1970-01-01
      相关资源
      最近更新 更多