我正在针对以下情况寻求有关表/索引设计的建议:
我有一个大表(股价历史数据,InnoDB,3500万行,并且还在不断增长),它具有复合主键(资产(整数),日期(日期))。除了定价信息外,我还有200个双精度值需要与每个记录相对应。
CREATE TABLE `mytable` (
`assetid` int(11) NOT NULL,
`date` date NOT NULL,
`close` double NOT NULL,
`f1` double DEFAULT NULL,
`f2` double DEFAULT NULL,
`f3` double DEFAULT NULL,
`f4` double DEFAULT NULL,
... skip a few …
`f200` double DEFAULT NULL,
PRIMARY KEY (`assetid`, `date`)) ENGINE=`InnoDB` DEFAULT CHARACTER SET latin1 COLLATE
latin1_swedish_ci ROW_FORMAT=COMPACT CHECKSUM=0 DELAY_KEY_WRITE=0
PARTITION BY RANGE COLUMNS(`date`) PARTITIONS 51;
我最初直接将200个双列存储在此表中,以便于更新和检索,并且工作得很好,因为在此表上进行的唯一查询是按资产ID和日期进行的(这些宗教性地包含在针对该表的任何查询中) ),只读取了200个双列。我的数据库大小约为45 Gig
但是,现在我有一个要求,我需要能够通过这200列的任何组合来查询此表(名为f1,f2,... f200),例如:
select from mytable
where assetid in (1,2,3,4,5,6,7,....)
and date > '2010-1-1' and date < '2013-4-5'
and f1 > -0.23 and f1 < 0.9
and f117 > 0.012 and f117 < .877
etc,etc
我以前从没有历史性地处理过如此大量的数据,所以我的第一个直觉是在这200个列中的每个列上都需要索引,否则我将进行大表扫描,等等。对我而言,这意味着我需要一个具有主键,值和索引值的200列中的每一个表。所以我同意了。
CREATE TABLE `f1` (
`assetid` int(11) NOT NULL DEFAULT '0',
`date` date NOT NULL DEFAULT '0000-00-00',
`value` double NOT NULL DEFAULT '0',
PRIMARY KEY (`assetid`, `date`),
INDEX `val` (`value`)
) ENGINE=`InnoDB` DEFAULT CHARACTER SET latin1 COLLATE latin1_swedish_ci ROW_FORMAT=COMPACT CHECKSUM=0 DELAY_KEY_WRITE=0;
我填满并索引了所有200张桌子。我将所有200列的表保留完整,因为通常会在资产ID和日期范围内查询该表,并选择所有200列。我认为将那些列留在父表(未索引)中以供读取,然后另外在自己的表中建立索引(以进行联接筛选)将是最有效的。我对查询的新形式进行了解释
select count(p.assetid) as total
from mytable p
inner join f1 f1 on f1.assetid = p.assetid and f1.date = p.date
inner join f2 f2 on f2.assetid = p.assetid and f2.date = p.date
where p.assetid in(1,2,3,4,5,6,7)
and p.date >= '2011-01-01' and p.date < '2013-03-14'
and(f1.value >= 0.96 and f1.value <= 0.97 and f2.value >= 0.96 and f2.value <= 0.97)
的确,确实达到了我想要的结果,解释说明给我显示此查询扫描的行要小得多。但是,我最后有一些不良的副作用。
1)我的数据库从45 Gig升至110 Gig。我无法再将数据库保留在RAM中。(但是我有256Gig的RAM)
2)现在需要每晚进行200次新数据插入,而不是一次
3)新200张桌子的维护/碎片整理时间比一张桌子长200倍。它无法在一夜之间完成。
4)针对f1等表的查询不一定是高性能的。例如:
select min(value) from f1
where assetid in (1,2,3,4,5,6,7)
and date >= '2013-3-18' and date < '2013-3-19'
上面的查询虽然解释说明了它在<1000行处的外观,但可能需要30秒钟以上的时间才能完成。我认为这是因为索引太大而无法容纳在内存中。
由于那是很多坏消息,因此我进一步查找并发现了分区。我在主表上实现了分区,每3个月按日期分区一次。月刊对我来说似乎很有意义,但我已经读到,一旦获得超过120个分区,性能就会下降。按季度划分将使我在接下来的20年左右处于该水平之下。每个分区略低于2 Gig。我跑了解释分区,一切似乎都在适当地修剪,所以无论我觉得分区是一个好步骤,至少出于分析/优化/修复的目的。
我花了很多时间写这篇文章
http://ftp.nchu.edu.tw/MySQL/tech-resources/articles/testing-partitions-large-db.html
我的表当前已分区,并且主键仍在上面。本文提到主键会使分区表变慢,但是如果您有一台可以处理的表,则分区表上的主键会变快。知道我正在路上有一台大机器(256 G RAM),所以我一直不停地按键。
如我所见,这是我的选择
选项1
1)删除多余的200个表,然后让查询进行表扫描以找到f1,f2等值。非唯一索引实际上会损害正确分区表上的性能。在用户运行查询之前运行解释,如果扫描的行数超过我定义的某个阈值,则拒绝它们。为自己省去了巨型数据库的痛苦。哎呀,无论如何,所有这些都会很快被保存在内存中。
子问题:
听起来我已经选择了合适的分区方案吗?
选项2
使用相同的3个月方案对所有200个表进行分区。享受较小的行扫描,并允许用户运行较大的查询。现在,它们至少已分区,出于维护目的,我可以一次管理1个分区。哎呀,无论如何,所有这些都会很快被保存在内存中。开发每晚更新它们的有效方法。
子问题:
您是否知道我在查询时总是拥有资产编号和日期的原因而避免在这些f1,f2,f3,f4 ...表上使用主键索引的原因?在我看来似乎很直观,但我不习惯这种规模的数据集。我认为这会缩小数据库
选项3
在主表中删除f1,f2,f3列以回收该空间。如果我需要阅读200项功能,请进行200次加入,也许它不会像听起来那样慢。
选项4
到目前为止,大家都拥有一种更好的结构方式。
*注意:我很快将在每个项目中再添加50-100个这些双精度值,因此我需要知道即将来临而进行设计。
感谢您提供的所有帮助
更新#1-2013/3/24
我接受下面的注释中建议的想法,并使用以下设置创建了一个新表:
create table 'features'{
assetid int,
date date,
feature varchar(4),
value double
}
我以3个月的间隔对表进行了分区。
我销毁了之前的200张表,以便数据库降到45 Gig,并开始填充此新表。一天半后,它完成了,现在我的数据库只有 220 Gigs!
它确实允许从主表中删除这200个值,因为我可以从一个联接中获取它们,但这实际上只能给我25个Gigs左右的可能
我要求它在资产编号,日期,功能和价值指数上创建一个主键,经过9个小时的修改之后,它实际上并未产生任何凹痕,而且似乎冻结了,所以我将这部分内容销毁了。
我重建了几个分区,但似乎并没有回收太多/任何空间。
因此,该解决方案可能看起来并不理想。我想知道,行是否比列占用的空间大得多,这可能就是为什么此解决方案占用了更多的空间吗?
我碰到了这篇文章:
http://www.chrismoos.com/2010/01/31/mysql-partitioning-tables-with-millions-of-rows
它给了我一个主意。它说:
起初,我想到了按日期对RANGE进行分区,而当我在查询中使用日期时,查询具有很大的日期范围是很常见的,这意味着它可以轻松地跨越所有分区。
现在,我也按日期对区域进行分区,但也将允许按较大的日期范围进行搜索,这将降低分区的有效性。当我搜索时,我将始终具有日期范围,但是我还将始终具有资产编号列表。也许我的解决方案应该是按资产ID和日期进行划分,在这里我可以识别通常搜索的资产ID范围(我可以提供标准列表,S&P 500,Russell 2000等)。这样,我几乎永远不会查看整个数据集。
再说一次,无论如何,我还是以资产号和日期为主要键,所以也许这没有太大帮助。
任何更多的想法/意见,将不胜感激。
(value_name varchar(20), value double)
将能够存储一切(value_name
是f1
,f2
...)