web-dev-qa-db-ja.com

巨大なテーブルで時間間隔クエリを処理するための効率的なスキーマ?

巨大なテーブル(+ 1000万行)があり、日時列で時間間隔検索を使用して値を集計しています。現在、アプリで単一のページを作成するために、このテーブルに数回クエリを実行しているため、クエリの遅延が大きくなっています。

このテーブルには1つの特定のプロパティがあり、レコードは挿入後に更新されることはありません。

このシナリオには2つの解決策がありますが、どちらが優れているか、またデータベースの専門家がどちらを推奨しているのかわかりません。

  1. 1回の旅行ですべてを取得しようとして、クエリを最大限に最適化します。
  2. データベースアーキテクチャを改善してレコード数を減らし、古い行を補助テーブルに集約できます。

私のデータは、次のような通貨市場に似ています: https://bitcoinity.org/markets 。どのようにして、さまざまな時間間隔(分、時間、日、月、年...)でクイッククエリを実行できますか?

このようなスキーマのよく知られたソリューションはありますか?

背景情報

  • Ruby on Rails App;
  • MySQL
  • クエリの最適化はほとんど(またはまったく)行われません。
  • 2011年からの最初の行。

スキーマの詳細

収益

CREATE TABLE `earnings` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `earner_id` int(11) DEFAULT NULL,
  `sale_id` int(11) DEFAULT NULL,
  `amount` int(11) DEFAULT NULL,
  `created_at` datetime NOT NULL,
  PRIMARY KEY (`id`),
  KEY `index_earnings_on_sale_id` (`sale_id`),
  KEY `index_earnings_on_created_at` (`created_at`)
) ENGINE=Inno

サンプルクエリ

SELECT DISTINCT *, count(amount) total FROM earnings
WHERE (created_at BETWEEN '2015-09-01 07:00:00' AND '2015-10-01 06:59:59')
  AND sale_id IN [....]
GROUP BY earner_id

このクエリは非常に単純であるだけでなく、月単位や過去10日間など、さまざまな期間で何度も実行されます。そのため、2番目のアイデア(2)で、各タイムスパムごとに合計をキャッシュする補助テーブルを検討しています。 (目的のデータ集計については、この例を参照してください https://bitcoinity.org/markets

3
Peoplee

どのようにして、さまざまな時間間隔(分、時間、日、月、年...)でクイッククエリを実行できますか?

トリックは、実行時にそれをまったく行わないことです。これが標準SQLの「必要なときに選択」によって行われると考える場合は、エラーになります。

  • データは通常、受信時に集計され、集計された行は必要に応じて(分、時間など)別のテーブルに書き込まれます。

  • 特に「現在から最後までのX」バータイプのクエリを表示する場合、グラフはデータのメモリ内コピーから作成されます。データベースに何度も尋ねることは、単に効率的ではありません。再生のためにログに記録しますが、最も必要なデータをメモリに保持します。

ティックデータをデータベースに保存する必要はまったくなく、集計のみです。少なくともこれが私が行うことです-ティックデータが必要なまれなケースですが、戻ってバイナリコードファイルを解析します。

確かに標準のSQLアプローチを使用できますが、パフォーマンスの低下にはかなりの費用がかかると予想されます。時系列集計は非常に具体的なシナリオです。

1
TomTom

各SELECTで何行が集計されますか?

これは言うまでもありません-日時列にインデックスがあることを確認してください。クエリの実行内容に応じて、複合インデックスを日時と集計対象の列に配置して、インデックスのみを使用し、ディスクからのデータブロックの読み取りを回避することもできます。

SELECTクエリにEXPLAIN EXTENDEDをCREATE TABLEとともに投稿してください。JOINSを実行している場合は、それらのテーブルにもCREATE TABLEを含めてください。

テーブルエンジンにMyISAM InnoDBに切り替える を使用している場合。

インデックスが高度にフラグメント化されている場合は、 パーティション テーブルを日付で検討することをお勧めします。インデックス検索が問題の一部ではない場合、これは役に立ちません。

これらのクエリの出力をキャッシュでき、その後のリクエストがそのデータで引き続き有効である場合は、できるだけ多くのデータをキャッシュしてください。

また、my.cnfが使用パターンに合わせて設定されていることを確認してください。

追加の雑学として、InnoDBは、データファイルを高速ストレージ上に置くこと、および特に集約が行われている場合に、より多くのコアよりも高速なコアから恩恵を受けます。

[編集]Op投稿されたSQL

このクエリの最後に_ORDER BY NULL_を追加すると、ファイルソートを削除できます。

クエリで集計されている行の数や、_sale_id_列と_created_at_列のカーディナリティがわからない場合、複合インデックスの方がパフォーマンスが良いかどうかを推測するのは困難です。あなたはそれをテストしたいかもしれません。

例:個別のインデックスを削除し、コンパウンドを作成します(sale_id、created_at):

ALTER TABLE earnings DROP INDEX index_earnings_on_sale_id, DROP INDEX index_earnings_on_created_at, ADD INDEX index_sale_id_created_at (sale_id, created_at);

あなたの例では、COUNT(amount)の代わりにSUM(amount)を実行しています。収益を総計するのではなく、erear_idでのみカウントすることを目標としている場合は、内部クエリで選択を行い、外部クエリでカウントすることもできます。これにより、クエリによってはパフォーマンスが向上する場合があります。

SELECT earner_id, COUNT(amount) AS total FROM ( SELECT earner_id, amount FROM earnings WHERE sale_id IN (428, 245) AND (created_at BETWEEN '2016-06-01 07:00:00' AND '2016-06-22 06:59:59') ) AS derived GROUP BY earner_id ORDER BY NULL

1
Bill Croft