web-dev-qa-db-ja.com

MySQLとNoSQL:適切なものを選択するのに役立つ

スレッドと呼ばれる1,000,000,000行の大きなデータベースがあります(これらのスレッドは実際に存在します。楽しんでいるだけで物事を難しくしているわけではありません)。 Threadsには、処理を高速化するために、数個しかありません:(int id、string hash、int replycount、int dateline(timestamp)、int forumid、string title)

クエリ:

select * from thread where forumid = 100 and replycount > 1 order by dateline desc limit 10000, 100

1Gのレコードがあるため、クエリは非常に低速です。なので、この1Gのレコードを、私が持っているフォーラム(カテゴリ)と同じ数のテーブルに分割しましょう。それはほぼ完璧です。多くのテーブルがあるので、検索するレコードが少なくて済み、本当に高速です。クエリは次のようになります。

select * from thread_{forum_id} where replycount > 1 order by dateline desc limit 10000, 100

フォーラム(カテゴリ)の99%を使用すると、これは本当に速くなります。それらのほとんどは、トピックの数が少ない(100k-1M)ためです。ただし、約10Mのレコードがいくつかあるため、一部のクエリはまだ遅くなります(0.1/.2秒、私のアプリではかなり!)すでにインデックスを使用しています!)。

MySQLを使用してこれを改善する方法がわかりません。方法はありますか?

このプロジェクトでは、10台のサーバーを使用します(12GB ram、ソフトウェアRAID 10の4x7200rpmハードディスク、クアッドコア)

考えは単にデータベースをサーバー間で分割することでしたが、上記で説明した問題ではまだ十分ではありません。

これらの10台のサーバーにcassandra=をインストールする場合(想定どおりに機能させるための時間を見つけることにより))、パフォーマンスが向上すると思いますか?

どうしたらいいですか?複数のマシンで分散データベースを使用してMySQLを使い続けるか、cassandra cluster?

私はインデックスとは何かを投稿するように頼まれました、ここにあります:

mysql> show index in thread;
PRIMARY id
forumid
dateline
replycount

説明を選択:

mysql> explain SELECT * FROM thread WHERE forumid = 655 AND visible = 1 AND open <> 10 ORDER BY dateline ASC LIMIT 268000, 250;
+----+-------------+--------+------+---------------+---------+---------+-------------+--------+-----------------------------+
| id | select_type | table  | type | possible_keys | key     | key_len | ref         | rows   | Extra                       |
+----+-------------+--------+------+---------------+---------+---------+-------------+--------+-----------------------------+
|  1 | SIMPLE      | thread | ref  | forumid       | forumid | 4       | const,const | 221575 | Using where; Using filesort | 
+----+-------------+--------+------+---------------+---------+---------+-------------+--------+-----------------------------+
41
cedivad

以下を読んで、適切に設計されたinnodbテーブルの利点とクラスタ化インデックスの最適な使用方法について少し学ぶ必要があります-innodbでのみ利用可能です!

http://dev.mysql.com/doc/refman/5.0/en/innodb-index-types.html

http://www.xaprb.com/blog/2006/07/04/how-to-exploit-mysql-index-optimizations/

次に、次の簡略化された例に沿ってシステムを設計します。

スキーマの例(簡略化)

重要な機能は、テーブルがinnodbエンジンを使用し、スレッドテーブルの主キーが単一のauto_incrementingキーではなく、複合clusteredキーベースであることです。 forum_idとthread_idの組み合わせ。例えば.

threads - primary key (forum_id, thread_id)

forum_id    thread_id
========    =========
1                   1
1                   2
1                   3
1                 ...
1             2058300  
2                   1
2                   2
2                   3
2                  ...
2              2352141
...

各フォーラム行には、next_thread_id(unsigned int)と呼ばれるカウンターが含まれており、トリガーによって維持され、特定のフォーラムにスレッドが追加されるたびにインクリメントします。これは、thread_idに単一のauto_increment主キーを使用する場合、合計40億のスレッドではなく、フォーラムごとに40億のスレッドを保存できることも意味します。

forum_id    title   next_thread_id
========    =====   ==============
1          forum 1        2058300
2          forum 2        2352141
3          forum 3        2482805
4          forum 4        3740957
...
64        forum 64       3243097
65        forum 65      15000000 -- ooh a big one
66        forum 66       5038900
67        forum 67       4449764
...
247      forum 247            0 -- still loading data for half the forums !
248      forum 248            0
249      forum 249            0
250      forum 250            0

複合キーを使用することの欠点は、次のように単一のキー値でスレッドを選択できないことです。

select * from threads where thread_id = y;

あなたはしなければならない:

select * from threads where forum_id = x and thread_id = y;

ただし、アプリケーションコードはユーザーが閲覧しているフォーラムを認識する必要があるため、実装するのはそれほど難しくありません。現在表示されているforum_idをセッション変数や非表示のフォームフィールドなどに格納します。

簡略化したスキーマは次のとおりです。

drop table if exists forums;
create table forums
(
forum_id smallint unsigned not null auto_increment primary key,
title varchar(255) unique not null,
next_thread_id int unsigned not null default 0 -- count of threads in each forum
)engine=innodb;


drop table if exists threads;
create table threads
(
forum_id smallint unsigned not null,
thread_id int unsigned not null default 0,
reply_count int unsigned not null default 0,
hash char(32) not null,
created_date datetime not null,
primary key (forum_id, thread_id, reply_count) -- composite clustered index
)engine=innodb;

delimiter #

create trigger threads_before_ins_trig before insert on threads
for each row
begin
declare v_id int unsigned default 0;

  select next_thread_id + 1 into v_id from forums where forum_id = new.forum_id;
  set new.thread_id = v_id;
  update forums set next_thread_id = v_id where forum_id = new.forum_id;
end#

delimiter ;

(forum_id、thread_id)コンポジットはそれ自体が一意であるため、少し奇妙ですが、プライマリキーの一部としてreply_countを含めていることに気づいたかもしれません。これは、reply_countを使用するクエリが実行されたときにI/Oを節約する、単なるインデックスの最適化です。詳細については、上記の2つのリンクを参照してください。

クエリの例

まだサンプルテーブルにデータを読み込んでいますが、これまでのところ約読み込まれています。 5億行(システムの半分)。ロードプロセスが完了すると、およそ次のようになるはずです。

250 forums * 5 million threads = 1250 000 000 (1.2 billion rows)

私は故意にいくつかのフォーラムに500万を超えるスレッドが含まれるようにしました。たとえば、フォーラム65には1,500万のスレッドがあります。

forum_id    title   next_thread_id
========    =====   ==============
65        forum 65      15000000 -- ooh a big one

クエリランタイム

select sum(next_thread_id) from forums;

sum(next_thread_id)
===================
539,155,433 (500 million threads so far and still growing...)

innodbでは、next_thread_idsを合計して合計スレッド数を取得すると、通常よりもはるかに高速になります。

select count(*) from threads;

フォーラム65にはいくつのスレッドがありますか。

select next_thread_id from forums where forum_id = 65

next_thread_id
==============
15,000,000 (15 million)

これも通常よりも高速です。

select count(*) from threads where forum_id = 65

これで、これまでに約5億のスレッドがあり、フォーラム65には1,500万のスレッドがあることがわかりました。スキーマのパフォーマンスを見てみましょう:)

select forum_id, thread_id from threads where forum_id = 65 and reply_count > 64 order by thread_id desc limit 32;

runtime = 0.022 secs

select forum_id, thread_id from threads where forum_id = 65 and reply_count > 1 order by thread_id desc limit 10000, 100;

runtime = 0.027 secs

私にはかなりパフォーマンスが良いように見えます-これは、5002億行以上の(そして成長している)1つのテーブルであり、クエリが1,500万行を0.02秒でカバーするクエリです(負荷がかかっている間!)

さらなる最適化

これらには以下が含まれます:

  • 範囲による分割

  • シャーディング

  • それにお金とハードウェアを投げる

等...

この回答がお役に立てば幸いです:)

81
Jon Black

[〜#〜] edit [〜#〜]:1列のインデックスでは不十分です。少なくとも、関連する3つの列をカバーする必要があります。

より高度なソリューション:_replycount > 1_を_hasreplies = 1_で置き換え、_replycount > 1_のときに1となる新しいhasrepliesフィールドを作成します。これが完了したら、3つの列にINDEX(forumid, hasreplies, dateline)の順序でインデックスを作成します。順序付けをサポートするBTREEインデックスであることを確認してください。

以下に基づいて選択しています。

  • 与えられたforumid
  • 与えられたhasreplies
  • datelineによる注文

これを実行すると、クエリの実行には次のことが含まれます。

  • bTREEを下に移動して、_forumid = X_に一致するサブツリーを見つけます。これは対数演算です(期間:log(フォーラム数))。
  • bTREEをさらに下に移動して、_hasreplies = 1_に一致する(ただし、_forumid = X_に一致する)サブツリーを見つけます。 hasrepliesは0または1だけなので、これは一定時間の操作です。
  • フォーラムのアイテムのリスト全体を読んで再ソートする必要なしに、必要な結果を得るために日付ラインでソートされたサブツリーを移動します。

replycountにインデックスを付けるという以前の提案は正しくありませんでした。これは範囲クエリであり、結果としてdatelineを使用して結果を並べ替えることができなかったためです(そのため、返信のあるスレッドを選択したはずです)非常に高速ですが、結果として得られる100万行のリストは、必要な100要素を探す前に完全にソートする必要がありました。

[〜#〜]重要[〜#〜]:これにより、すべてのケースでパフォーマンスが向上しますが、MySQLは次のようにできないため、OFFSET値(10000!)が大きくなるとパフォーマンスが低下します。 BTREEを直読しても先にスキップします。したがって、OFFSETが大きいほど、リクエストは遅くなります。

オフセットの問題は、計算をいくつかの計算に分散させる(とにかくオフセットを並列にスキップする方法はありますか)か、NoSQLに移動しても、自動的には解決されないようです。すべてのソリューション(NoSQLソリューションを含む)は、dateline(基本的に_dateline > Y LIMIT 100_ではなく_LIMIT Z, 100_と言う)に基づくOFFSETのシミュレーションに要約されます。ここで、Yは、アイテムの日付ですオフセットZ)。これは機能し、オフセットに関連するパフォーマンスの問題を排除しますが、200ページのうち100ページに直接移動することはできません。

25
Victor Nicollet

NoSQLまたはMySQLオプションに関連する質問の一部があります。実際、これはここに隠されている基本的なものの1つです。 SQL言語は人間にとっては書きやすく、コンピューターにとっては少し難しい。大量のデータベースでは、SQLバックエンドを使用しないことをお勧めします。これには、追加の手順-コマンドの解析が必要になるためです。私は広範なベンチマークを行っており、SQLパーサーが最も遅い場合があります。それについてあなたができることは何もありません。 OK、事前に解析されたステートメントを使用してそれらにアクセスすることができます。

ところで、それは広く知られていませんが、MySQLはNoSQLデータベースから成長しました。 MySQL DavidとMontyの作者が働いた会社はデータウェアハウジング会社であり、珍しいタスクのためにカスタムソリューションを作成しなければならないことがよくありました。これにより、Oracleやその他のパフォーマンスが低いときにデータベース関数を手動で作成するために使用される自作のCライブラリの大きなスタックが発生しました。 SQLは、1996年にこの20年近くある古い動物園に楽しみのために追加されました。あなたが知った後に来たもの。

実際、MySQLではSQLオーバーヘッドを回避できます。しかし、通常、SQL解析は最も遅い部分ではなく、知っておくとよいでしょう。パーサーのオーバーヘッドをテストするには、たとえば「SELECT 1」のベンチマークを作成するだけです;)。

4
Tõnu Samuel

購入する予定のハードウェアにデータベースアーキテクチャを適合させるのではなく、データベースアーキテクチャに適合するハードウェアを購入することを計画してください。

十分なRAM=インデックスのワーキングセットをメモリに保持できるようになると、インデックスを使用できるすべてのクエリが高速になります。キーバッファーがインデックスを保持するのに十分な大きさに設定されていることを確認してください。

したがって、12GBでは不十分な場合は、12GBのRAMを搭載した10台のサーバーを使用せず、32GBまたは64GBのRAMを搭載したサーバーを使用してください。

3
Dan Grossman

インデックスは必須ですが、正しいタイプのインデックスを選択することを忘れないでください。WHERE句で「<」または「>」を使用するクエリを使用する場合はBTREEがより適しています。一方、1つの列に多数の異なる値があり、 WHERE句で「=」または「<=>」を使用している。

さらに読む http://dev.mysql.com/doc/refman/5.0/en/mysql-indexes.html

0
descent89