何がより効率的でパフォーマンスが速いのか疑問に思っています:
1つの大きなテーブルまたはインデックスのない複数の小さなテーブルにインデックスがありますか?
これはかなり抽象的な問題なので、もっと実用的にしましょう。
ユーザーに関する統計を含むテーブルが1つあります(20,000ユーザーと全体で約3,000万行)。テーブルには、user_id
、actions
、timestamps
などを含む約10列があります。
最も一般的なアプリケーションは次のとおりです:user_id
によるデータの挿入とuser_idによるデータの取得(SELECT
ステートメントに複数のuser_id's
が含まれることはありません)。
今のところ、user_id
にINDEX
があり、クエリは次のようになります。
SELECT * FROM statistics WHERE user_id = 1
現在、行が増えると、テーブルの速度はますます遅くなります。 INSERT
ステートメントは、INDEX
がどんどん大きくなるため、速度が低下します。 SELECT
ステートメントは、検索する行が多いため、速度が低下します。
ユーザーごとに1つの統計テーブルを作成せず、代わりにクエリ構文を次のように変更する理由を考えていました。
SELECT * FROM statistics_1
ここで、1
は明らかにuser_id
を表します。
これにより、INDEX
は不要になり、各テーブルのデータがはるかに少なくなるため、INSERT
およびSELECT
ステートメントの方がはるかに高速になります。
ここで再び私の質問:
1つのテーブルをINDEX
?で使用する代わりに、非常に多くのテーブル(私の場合は20,000)を処理することには現実的な欠点はありますか?
私のアプローチでは実際に速度が上がるのでしょうか、それともテーブルのルックアップがすべてのものよりも遅くなるのでしょうか?
20,000個のテーブルを作成することは悪い考えです。やがて40,000個のテーブルが必要になります。
私の本ではこのシンドロームMetadata Tribblesを SQL Antipatterns と呼んでいました。これは、「Xあたりのテーブル」または「Xあたりの列」を作成するたびに発生します。
数万のテーブルがある場合、これは実際のパフォーマンスの問題を引き起こします。各テーブルは、内部データ構造、ファイル記述子、データディクショナリなどを維持するためにMySQLを必要とします。
実際の運用上の影響もあります。新しいユーザーがサインアップするたびに新しいテーブルを作成する必要があるシステムを本当に作成しますか?
代わりに、 MySQL Partitioning を使用することをお勧めします。
以下はテーブルを分割する例です:
CREATE TABLE statistics (
id INT AUTO_INCREMENT NOT NULL,
user_id INT NOT NULL,
PRIMARY KEY (id, user_id)
) PARTITION BY HASH(user_id) PARTITIONS 101;
これにより、1つの論理テーブルを定義すると同時に、パーティションキーの特定の値をクエリするときにアクセスを高速化するためにテーブルを多くの物理テーブルに分割するという利点があります。
たとえば、例のようなクエリを実行すると、MySQLは特定のuser_idを含む正しいパーティションにのみアクセスします。
mysql> EXPLAIN PARTITIONS SELECT * FROM statistics WHERE user_id = 1\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: statistics
partitions: p1 <--- this shows it touches only one partition
type: index
possible_keys: NULL
key: PRIMARY
key_len: 8
ref: NULL
rows: 2
Extra: Using where; Using index
HASH分割方式とは、行が整数パーティションキーの係数によってパーティションに配置されることを意味します。これは、多くのuser_idが同じパーティションにマップされることを意味しますが、各パーティションの行数は平均で1/Nにすぎません(Nはパーティションの数です)。また、一定数のパーティションでテーブルを定義するため、新しいユーザーを取得するたびにテーブルを拡張する必要はありません。
1024(またはMySQL 5.6では8192)までの任意の数のパーティションを選択できますが、それを超えるとパフォーマンスの問題が報告される人もいます。
素数のパーティションを使用することをお勧めします。 user_id値がパターンに従う場合(偶数のみを使用する場合など)、素数のパーティションを使用すると、データをより均等に分散できます。
コメントで質問を再記入してください:
適切な数のパーティションを決定するにはどうすればよいですか?
HASHパーティショニングの場合、上記の例で示したように101のパーティションを使用すると、特定のパーティションに平均で行の約1%が割り当てられます。統計テーブルには3,000万行あるので、このパーティションを使用すると、パーティションごとに30万行しかありません。 MySQLの方が読みやすいです。インデックスを使用することもできます(使用する必要があります)。各パーティションには独自のインデックスがあり、パーティション化されていないテーブル全体のインデックスと同じ1%の大きさになります。
したがって、適切な数のパーティションをどのように決定できるかという答えは、テーブル全体の大きさ、およびパーティションを平均してどのくらいの大きさにしたいですか?
時間の経過とともにパーティションの量が増えるのではないでしょうか?もしそうなら:どうすればそれを自動化できますか?
HASHパーティショニングを使用する場合、パーティションの数は必ずしも増加する必要はありません。最終的には合計300億行になる可能性がありますが、データ量が桁違いに大きくなると、とにかく新しいアーキテクチャが必要になることがわかりました。データがそれだけ大きくなる場合は、複数のサーバーでのshardingと、複数のテーブルへのパーティション分割が必要になる可能性があります。
つまり、ALTER TABLEを使用してテーブルを再分割できます。
ALTER TABLE statistics PARTITION BY HASH(user_id) PARTITIONS 401;
これは(ほとんどのALTER TABLEの変更と同様に)テーブルを再構築する必要があるため、しばらく時間がかかると予想されます。
パーティション内のデータとインデックスのサイズを監視することができます。
SELECT table_schema, table_name, table_rows, data_length, index_length
FROM INFORMATION_SCHEMA.PARTITIONS
WHERE partition_method IS NOT NULL;
他のテーブルと同様に、アクティブなインデックスの合計サイズをバッファプールに収める必要があります。これは、MySQLがSELECTクエリ中にバッファプールにインデックスの一部を入れたり出したりする必要がある場合、パフォーマンスが低下するためです。
RANGEまたはLISTパーティション分割を使用する場合、パーティションの追加、削除、マージ、および分割がはるかに一般的です。 http://dev.mysql.com/doc/refman/5.6/en/partitioning-management-range-list.html を参照してください
パーティショニングの手動セクション を読んで、この素晴らしいプレゼンテーションをチェックすることをお勧めします: Boost Performance With MySQL 5.1 Partitions 。
これはおそらく、頻繁に作成する予定のクエリの種類に依存します。確実に知る最善の方法は、両方のプロトタイプを実装し、いくつかのパフォーマンステストを行うことです。
そうは言っても、ほとんどのDBMSシステムは大きなテーブルにデータを見つけて挿入する正確な状況に対処するために大幅に最適化されているため、インデックスを持つ単一(大きな)テーブルが全体的に良くなると思います。パフォーマンスの向上を期待して小さなテーブルをたくさん作ろうとすると、オプティマイザとの戦いになります(通常はそれがより優れています)。
また、1つのテーブルが将来のためにおそらくより実用的であることも覚えておいてください。すべてのユーザーの統計情報を収集したい場合はどうすればよいですか? 20 000のテーブルがあると、これを実行するのが非常に困難で非効率になります。これらのスキーマの柔軟性も考慮する価値があります。このようにテーブルをパーティション分割すると、将来に備えて自分自身を設計している可能性があります。
Bill Karwinsの答えに追加することはほとんどありません。ただし、ヒントの1つは、ユーザーのすべてのデータが常に完全に詳細に必要かどうかを確認することです。
使用統計や訪問数などを提供したい場合、通常、たとえば2009年の今日のビューでは、単一のアクションと秒の細かさは得られません。したがって、集計テーブルとアーカイブテーブル(もちろんエンジンアーカイブではありません)を構築して、アクションベースの最近のデータと古いアクションの概要を確認できます。
古い行動は変わらないと思います。
また、たとえば、アーカイブテーブルのweek_idを使用して、集約から詳細に進むこともできます。
ユーザーごとに1つのテーブルから1つのテーブルに移行する代わりに、パーティション分割を使用して、途中の数個のテーブル/テーブルサイズの比率にヒットできます。
また、ユーザーの統計を保持して、「アクティブな」ユーザーを1つのテーブルに移動して、時間の経過とともにアクセスする必要があるテーブルの数を減らすこともできます。
つまり、できることはたくさんありますが、主にプロトタイプとテストを作成し、行っているさまざまな変更によるパフォーマンスへの影響を評価する必要があります。