MySQL性能问题涉及大量数据

时间:2011-09-16 18:16:15

标签: c++ mysql performance

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

我正在尝试考虑存储所有这些数据并稍后检索的最佳方法。我的第一个想法是使用一些像MySQL这样的RDBMS。作为一名嵌入式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;

我提前道歉这是一个很长的问题,但我已经挖掘了其他资源,这是我的最后一招。我想我会尽可能地描述你们有没有看到我乍看之下能改进我们的设计的方法?或者,无论如何我们可以优化这些大型数据集的select和delete语句?我们目前正在运行RHEL作为操作系统,遗憾的是无法更改服务器上的硬件配置(4 GB RAM,四核)。我们正在使用C / C ++和MySQL API。任何速度改进都将非常有益。如果您需要我澄清任何事情,请告诉我。谢谢!

编辑:BTW,如果你不能提供具体的帮助,也许你可以把我链接到你为优化SQL查询,架构设计或MySQL调优而遇到的一些优秀教程?

5 个答案:

答案 0 :(得分:4)

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

答案 1 :(得分:2)

我想说在这样的情况下,分区是绝对必须的:

  • 大量数据
  • 新数据不断进入
  • 隐含:旧数据被连续删除。

查看this for mySQL

查看你的select stmt(按时间过滤),我会说时间栏上的分区。

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

- 编辑 -

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

- 编辑结束 -

如果您不熟悉,请快速进行分区。

  • 通常,单个表会转换为单个数据文件。分区表转换为每个分区一个文件。
  • 优点
    • 插入更快,因为它插入到较小的文件(分区)中。
    • 删除大量行通常会转换为删除分区(比从xxx删除时间> 100且时间<200'便宜得多;)
    • 使用关于表分区的键的where子句进行查询要快得多。
    • 索引构建更快。

答案 2 :(得分:1)

我对MySQL没有多少经验,但这里有一些先天的想法可以想到。

您在存储过程中选择了吗?

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

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 

通常在开发类似这样的东西时,我会使用较小的一组并删除谓词,以确保每一步都符合我的期望。如果你不小心在前面投了一个宽网,那么稍后缩小你可能会掩盖一些低效率。

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

答案 3 :(得分:1)

在不知道所有查询是什么的情况下难以提供具体建议,无论如何查看您提供的单个查询,都没有适合解析此问题的索引。

实际上结构有点乱 - 如果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`)

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

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

答案 4 :(得分:0)

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

顺便说一句,MySQL并不一定是你想要完成的最优化的数据库系统...查看其他解决方案,如Oracle,Microsoft SQL,PostgreSQL等。此外,性能将根据服务器的不同而有所不同使用