次の状況でのテーブル/インデックスの設計に関するアドバイスを探しています。
複合主キー(assetid(int)、date(date))を含む大きなテーブル(株価履歴データ、InnoDB、3500万行および成長)があります。価格情報に加えて、各レコードに対応する必要がある200のdouble値があります。
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個のダブル列をこのテーブルに直接保存しました。 )、および200の二重列は読み取り専用でした。データベースサイズは約45ギグでした
ただし、次のように、これらの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のテーブルすべてを埋めてインデックスを作成しました。定期的にassetidと日付範囲を照会され、200列すべてが選択されるため、メイン列には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ギガバイトから110ギガバイトになりました。データベースをRAMに保持できなくなりました。(ただし、途中で256GigのRAMがあります)
2)新しいデータの夜間挿入は、1回ではなく200回行う必要があります
3)新しい200個のテーブルのメンテナンス/デフラグは、1つのテーブルよりも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'
上記のクエリは、explainが1000行未満で検索されることを示していますが、完了するまでに30秒以上かかる場合があります。これは、インデックスが大きすぎてメモリに収まらないためだと思います。
それは多くの悪いニュースだったので、さらに調べてパーティション分割を見つけました。メインテーブルにパーティションを実装し、3か月ごとにパーティション分割しました。月刊は私には理にかなっているように見えましたが、120パーティション以上を取得するとパフォーマンスが低下することを読みました。四半期ごとにパーティション分割することで、今後20年ほどはその下に置かれます。各パーティションは2ギガ未満です。私はパーティションの説明を実行しましたが、すべてが適切にプルーニングされているようです。
私はこの記事でかなりの時間を費やしました
http://ftp.nchu.edu.tw/MySQL/tech-resources/articles/testing-partitions-large-db.html
私のテーブルは現在、主キーがまだ残っている状態でパーティション分割されています。この記事では、主キーによってパーティションテーブルの速度が低下する可能性がありますが、それを処理できるマシンがある場合は、パーティションテーブルの主キーが高速になります。途中に大きなマシン(256 G RAM)があることを知って、キーをオンのままにしました。
私が見るように、ここに私のオプションがあります
オプション1
1)余分な200個のテーブルを削除し、クエリにテーブルスキャンを実行させて、f1、f2などの値を見つけます。一意でないインデックスは、適切にパーティション分割されたテーブルのパフォーマンスを実際に損なう可能性があります。ユーザーがクエリを実行する前にExplainを実行し、スキャンされた行数が定義したしきい値を超えている場合は拒否します。巨大なデータベースの手間を省きます。とにかく、それはすべてすぐにメモリ内にあります。
サブ質問:
適切なパーティション構成を選択したように聞こえますか?
オプション2
同じ3か月のスキームを使用して、200のテーブルすべてをパーティション分割します。より小さな行スキャンを楽しんで、ユーザーがより大きなクエリを実行できるようにします。少なくともパーティション化されているので、メンテナンスのために一度に1パーティションずつ管理できます。とにかく、それはすべてすぐにメモリ内にあります。それらを毎晩更新する効率的な方法を開発します。
サブ質問:
これらのf1、f2、f3、f4 ...テーブルの主キーインデックスを避けることができる理由がわかりますか?クエリ時に常にアセットIDと日付があることを知っていますか?私には直観に反するようですが、このサイズのデータセットには慣れていません。それはデータベースを私が推測する束を縮小するだろう
オプション3
マスター表のf1、f2、f3列をドロップして、そのスペースを再利用します。200の機能を読む必要がある場合は200の参加を行います。
オプション4
これまで考えてきた以上に、皆さんはこれを構築するためのより良い方法を持っています。
*注:間もなくこれらのdouble値のうち50〜100個を各アイテムに追加するので、それが来ることを知って設計する必要があります。
すべての助けをありがとう
アップデート#1-2013年3月24日
私は下にあるコメントで提案されたアイデアを採用し、次のセットアップで新しいテーブルを1つ作成しました:
create table 'features'{
assetid int,
date date,
feature varchar(4),
value double
}
3か月間隔でテーブルを分割しました。
以前の200個のテーブルを吹き飛ばして、データベースを45ギガまで下げ、この新しいテーブルをいっぱいにし始めました。1日半後、それは完了し、私のデータベースはぽっちゃりの 220ギグになりました!
マスターテーブルからこれらの200の値を削除する可能性があります。1つの結合から値を取得できますが、実際には25ギガ程度しか返されません
assetid、date、feature、value on indexの主キーを作成するように依頼しましたが、9時間たっても、くぼみがなく、フリーズしたように見えたので、その部分を削除しました。
いくつかのパーティションを再構築しましたが、多くのスペースを取り戻すことはできませんでした。
そのため、そのソリューションはおそらく理想的ではないように見えます。行が列よりも大幅に多くのスペースを占めるのか、それがこのソリューションがこれほど多くのスペースを占めるのはなぜでしょうか?
私はこの記事に出会いました:
http://www.chrismoos.com/2010/01/31/mysql-partitioning-tables-with-millions-of-rows
アイデアを与えてくれました。それは言います:
最初は、日付によるRANGEパーティション化について考えました。クエリで日付を使用している間、クエリの日付範囲が非常に広いのは非常に一般的であり、すべてのパーティションに簡単に適用できることを意味します。
現在、日付による範囲のパーティション分割も行っていますが、大きな日付範囲による検索も許可するため、パーティション分割の効果が低下します。検索するときは常に日付範囲がありますが、資産IDのリストも常にあります。おそらく私の解決策は、アセットIDと日付でパーティション分割することであり、通常検索されたアセットIDの範囲を特定します(標準リスト、S&P 500、Russell 2000などがあります)。この方法では、データセット全体をほとんど見ることはありません。
繰り返しになりますが、私はとにかくassetidとdateに主キーが設定されているので、おそらくそれはあまり役に立たないでしょう。
これ以上の考え/コメントをいただければ幸いです。
(value_name varchar(20), value double)
店舗のすべてのことができるようになり(value_name
さf1
、f2
、...)