【发布时间】: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