MySQL大数据量下的性能问题

8
我有一个编程项目在工作中让我感到很苦恼。我们的问题是:我们需要记录一系列数据联系人每秒发送的信息,包括时间、方位(360-1080字节的数组)、范围和其他几个字段。我们的系统还需要能够将这些数据存储长达30天。实际上,最多可能会有100个不同的联系人,因此在30天内可能会有大约150,000,000到1,000,000,000个不同的点。
我正在思考最佳的方法来存储所有这些数据并在以后检索。我的第一个想法是使用像MySQL这样的关系型数据库管理系统。作为一个嵌入式C/C++程序员,我很少有处理如此大量数据集的经验。我曾在小数据集上尝试过它,但从未处理过如此大的数据量。我为两个表生成了以下模式,用于存储部分数据:
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天,这种情况可能会发生。
我们目前实施的系统对于较小的数据集运作良好,但对于大型数据集则不然。我们的选择和删除语句可能需要花费超过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调整?

1
在MySQL上运行针对1亿到10亿条记录的范围查询,如果您期望即时结果,那将是一件痛苦的事情。您的“实时”需求是否非常严格?如果不是,您可能有可能将部分处理转移到批处理作业中,以生成某些预选范围的聚合数据。您是否考虑过对数据进行分区?这是一个可能性吗? - Deep Kapadia
1
由于您是Mysql的新手,您是否熟悉Explain计划?http://dev.mysql.com/doc/refman/5.0/en/explain.html http://dev.mysql.com/doc/refman/5.0/en/explain-output.html这篇文章也可能有所帮助: http://dev.mysql.com/doc/refman/5.0/en/range-optimization.html哦,是的,请考虑按建议对数据进行去规范化。在具有1亿条记录的表上进行连接是一种杀手。 - Deep Kapadia
你不能使用某种只读缓存吗?或者这对你的用户来说是不可接受的吗?如果可以,你可以考虑使用类似 http://infinidb.org/ 这样的东西来缓存你的数据(它比标准的 MySQL 数据库在检索大量行的信息时要快得多)。 - Shautieh
数据不一定每秒钟都会改变,但它并不是确定性的。它根据联系物是否相对于传感器移动而改变,基本上增加了随机性。我真的很喜欢这个问题的去规范化思想。我认为把所有东西都放在一个表中效率低下,我想你知道当你做出假设时会发生什么...@Deep Kapadia,你对查询优化器有什么经验?你推荐哪一个?我还会看看hadoop,我们当前的系统是高度分布式的,但这个特定模块不是。 - demarr
1
我希望您不仅仅是优化器,还能查看查询的解释计划,并检查是否存在全表扫描或未使用的索引,并添加正确类型的索引。查询优化几乎是一门艺术,需要耐心。正如我之前建议的那样,我会关注分区。@thekashyap提出了一些好的观点。我从未使用过任何优化工具,只是查看了解释计划并进行了一些调整。以下是一些很好的开始优化MySQL查询的提示:http://20bits.com/articles/10-tips-for-optimizing-mysql-queries-that-dont-suck/ - Deep Kapadia
显示剩余2条评论
5个回答

4

首先,您可以尝试去规范化数据。在这样大的数据集上进行连接操作,即使有索引也需要非常强大的计算能力。将这三个表合并为一个表。当然会有重复数据,但是没有连接操作,处理起来会更容易。其次,请查看是否可以获得具有足够内存的机器,以便将整个表放入内存中。对于拥有24GB RAM且价格不到$1000的机器,这并不需要花费太多。我不确定它是否可以容纳整个数据集,但这将极大地帮助您。还要购买SSD硬盘。对于任何未存储在内存中的内容,SSD都可以帮助您以高速访问。第三点,研究其他数据存储技术,例如 BigTable ,专门用于处理非常大的数据集。


这将取决于连接和索引查找(where子句)的评估顺序。根据用例,实际上“拥有3个较小的表并加入每个表中的某些行”可能比“拥有一个巨大的表并进行索引查找”更快。 - Kashyap
这是一个很棒的想法,我一直认为规范化是存储数据的最佳策略,但我确实能看到在这个特定问题上并非总是如此。我得试一试...不幸的是,对于我们来说,更改/升级硬件是不可能的 :( ...也许在未来的构建中这可能是一个选择。你以前用过BigTable吗? 它对于大量的写入、读取和删除操作效果如何?这也可能是我们的一个选择。 - demarr
是的,很多时候都需要不断尝试和调整。这取决于你使用情况的具体细节。保持数据规范化可以帮助某些查询,但如果你最终需要进行连接数百万行的查询,那么你最好做好等待的准备。另一方面,如果你只选择数据的非常小的部分,并且所有内容都被良好地索引,那么三个表的方法将非常有效。 - Kibbee
@thekashyap 我应该如何检查评估顺序?您推荐使用哪些好的查询分析器呢? - demarr
很遗憾,我从未使用过MySQL。在Oracle中,您可以使用EXPLAIN并/或在OEM中查看非常详细的信息(例如哈希查找,读取xxx字节等)。也许一些MySQL专家可以提供帮助。##编辑##请参阅http://dev.mysql.com/doc/refman/5.0/en/execution-plan-information.html以获取有关MySQL的执行计划信息。 - Kashyap
PS:在我帖子中所描述的,做这些(查询/索引优化,去/规范化)是次要的。表格的分区是不可谈判的。 - Kashyap

2
我会说,在这种情况下,分区绝对是必须的:
  • 大量数据
  • 不断有新数据进来
  • 隐含条件:旧数据不断被删除。

请查看这个关于mySQL的链接

看着你的select语句(它是基于时间过滤的),我建议在时间列上进行分区。

当然,你可能想根据经常使用的查询添加一些索引。

--编辑--

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

--编辑结束--

如果你还不熟悉分区,这里简单介绍一下。

通常一个单独的表格对应一个数据文件。一个分区表格对应一个分区文件。 优点: - 插入速度更快,因为数据被物理地插入到一个较小的文件(分区)中。 - 删除大量行通常相当于删除一个分区(比“delete from xxx where time > 100 and time < 200”要便宜得多)。 - 按表格分区关键字进行where子句查询速度更快。 - 索引构建速度更快。

1

我对MySQL没有太多经验,但以下是一些我首先想到的想法。

你的选择语句在存储过程中吗?

选择语句的谓词通常按照请求的顺序进行搜索。如果将磁盘上的数据重新排序以匹配主键,则首先执行raster 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 

通常在开发这样的东西时,我会使用较小的数据集并删除谓词,以确保每个步骤都符合我的预期。如果你一开始就广泛地铺开网,然后再缩小范围,可能会掩盖一些低效率。
大多数查询优化器都有一种输出方式来展示它们是如何进行优化的,请确保它符合您的期望。其中一个评论提到了解释计划,我想这就是它的名称。

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

哈哈哈,感谢您的评论...是的,我的表结构相当混乱。我在尝试不同的东西,并复制和粘贴了似乎最好的模式。我的数据库技能并不是很强,我肯定需要加强索引方面的知识,如果您还没有注意到的话。我也会看一下mysqltuner。 - demarr

0
首先,我会尝试创建一个视图,其中只选择不同表之间需要选择的必要信息。
顺便说一句,MySQL并不一定是您尝试实现目标的最优化数据库系统...请考虑其他解决方案,例如Oracle、Microsoft SQL、PostgreSQL等。此外,性能将取决于使用的服务器。

1
你得到你“付出”的东西。 :) - Kashyap
1
一个视图在这里会有什么帮助?它仍然运行相同的选择操作。您是否知道在这种情况下可能起作用的特定视图算法?我并不挑战您的陈述。我想更好地理解您的回答。 - Deep Kapadia
@Alerty 我还是不明白。在这种情况下,视图如何帮助解决性能问题? - Deep Kapadia
MySQL默认情况下没有物化视图。虽然有一些解决方法,但有些研究表明它们很难得到有效的应用。因此,我有一个问题。MySQL仅支持临时表和合并视图,这两种方式都需要每次有人从视图中选择时运行SQL。 - Deep Kapadia
@Kashyap,MyISAM是世界级的快速引擎,而InnoDB则是世界级的容错引擎,在正确配置和经过良好调整的应用层上表现出色。此外,对于企业级用户,支持服务并非免费。MySQL在某些方面表现极为优秀,但确实需要专业技能。 - zanlok
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接