14

データベースに数百万行のテーブルが 2 つあり、選択と挿入がますます遅くなります。

私はspring+hibernate+mysql 5.5を使用しており、シャーディングとテーブルのパーティション分割について読み、テーブルをパーティション分割するというアイデアが好きです。

私の現在のDb構造は次のようなものです

CREATE TABLE `user` (
  `id` BIGINT(20) NOT NULL,
  `name` VARCHAR(255) DEFAULT NULL,
  `email` VARCHAR(255) DEFAULT NULL,
  `location_id` bigint(20) default NULL,
  `updated_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY  (`id`),
  KEY `FK3DC99772C476E06B` (`location_id`),
  CONSTRAINT `FK3DC99772C476E06B` FOREIGN KEY (`location_id`) REFERENCES `places` (`id`) 
) ENGINE=INNODB DEFAULT CHARSET=utf8


CREATE TABLE `friends` (
  `id` BIGINT(20) NOT NULL AUTO_INCREMENT,
  `user_id` BIGINT(20) DEFAULT NULL,
  `friend_id` BIGINT(20) DEFAULT NULL,
  `updated_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY  (`id`),
  UNIQUE KEY `unique_friend` (`user_id`,`friend_id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8

現在、パーティショニングをより適切に使用する方法をテストしています。次のユーザーテーブルについては、使用法に基づいて良いと思いました。

CREATE TABLE `user_partition` (
  `id` BIGINT(20) NOT NULL,
  `name` VARCHAR(255) DEFAULT NULL,
  `email` VARCHAR(255) DEFAULT NULL,
  `location_id` bigint(20) default NULL,
  `updated_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY  (`id`),
  KEY `FK3DC99772C476E06B` (`location_id`) 
) ENGINE=INNODB DEFAULT CHARSET=utf8
PARTITION BY HASH(id DIV 100000)
PARTITIONS 30;

2 つのテーブルにデータをロードし、2 つのテーブルのパフォーマンスを確認する手順を作成しました

DELIMITER //
CREATE PROCEDURE load_partition_table()
BEGIN
DECLARE v INT DEFAULT 0;
    WHILE v < 1000000
    DO
    INSERT INTO user_partition (id,NAME,email)
    VALUES (v,CONCAT(v,' name'),CONCAT(v,'@yahoo.com')),
    (v+1,CONCAT(v+1,' name'),CONCAT(v+1,'@yahoo.com')),
    (v+2,CONCAT(v+2,' name'),CONCAT(v+2,'@yahoo.com')),
    (v+3,CONCAT(v+3,' name'),CONCAT(v+3,'@yahoo.com')),
    (v+4,CONCAT(v+4,' name'),CONCAT(v+4,'@yahoo.com')),
    (v+5,CONCAT(v+5,' name'),CONCAT(v+5,'@yahoo.com')),
    (v+6,CONCAT(v+6,' name'),CONCAT(v+6,'@yahoo.com')),
    (v+7,CONCAT(v+7,' name'),CONCAT(v+7,'@yahoo.com')),
    (v+8,CONCAT(v+8,' name'),CONCAT(v+8,'@yahoo.com')),
    (v+9,CONCAT(v+9,' name'),CONCAT(v+9,'@yahoo.com'))
    ;
    SET v = v + 10;
    END WHILE;
    END
    //

CREATE PROCEDURE load_table()
BEGIN
DECLARE v INT DEFAULT 0;
    WHILE v < 1000000
    DO
    INSERT INTO user (id,NAME,email)
    VALUES (v,CONCAT(v,' name'),CONCAT(v,'@yahoo.com')),
    (v+1,CONCAT(v+1,' name'),CONCAT(v+1,'@yahoo.com')),
    (v+2,CONCAT(v+2,' name'),CONCAT(v+2,'@yahoo.com')),
    (v+3,CONCAT(v+3,' name'),CONCAT(v+3,'@yahoo.com')),
    (v+4,CONCAT(v+4,' name'),CONCAT(v+4,'@yahoo.com')),
    (v+5,CONCAT(v+5,' name'),CONCAT(v+5,'@yahoo.com')),
    (v+6,CONCAT(v+6,' name'),CONCAT(v+6,'@yahoo.com')),
    (v+7,CONCAT(v+7,' name'),CONCAT(v+7,'@yahoo.com')),
    (v+8,CONCAT(v+8,' name'),CONCAT(v+8,'@yahoo.com')),
    (v+9,CONCAT(v+9,' name'),CONCAT(v+9,'@yahoo.com'))
    ;
    SET v = v + 10;
    END WHILE;
    END
    //

結果は驚くべきものでした。非パーティション テーブルで挿入/選択すると、より良い結果が得られます。

mysql> select count(*) from user_partition;
+----------+
| count(*) |
+----------+
|  1000000 |
+----------+
1 row in set (0.40 sec)

mysql> select count(*) from user;
+----------+
| count(*) |
+----------+
|  1000000 |
+----------+
1 row in set (0.00 sec)


mysql> call load_table();
Query OK, 10 rows affected (20.31 sec)

mysql> call load_partition_table();
Query OK, 10 rows affected (21.22 sec)

mysql> select * from user where id = 999999;
+--------+-------------+------------------+---------------------+
| id     | name        | email            | updated_time        |
+--------+-------------+------------------+---------------------+
| 999999 | 999999 name | 999999@yahoo.com | 2012-11-27 08:06:54 |
+--------+-------------+------------------+---------------------+
1 row in set (0.00 sec)

mysql> select * from user_no_part where id = 999999;
+--------+-------------+------------------+---------------------+
| id     | name        | email            | updated_time        |
+--------+-------------+------------------+---------------------+
| 999999 | 999999 name | 999999@yahoo.com | 2012-11-27 08:03:14 |
+--------+-------------+------------------+---------------------+
1 row in set (0.00 sec)

だから2つの質問

user1)挿入と選択も高速になり、FOREIGN KEYの削除location_idが正しいように、テーブルを分割する最良の方法は何ですか? パーティションキーに基づいてアクセスする場合にのみ、パーティションが適切であることはわかっています。私の場合、ID のみでテーブルを読み取りたいと考えています。パーティションテーブルで挿入が遅いのはなぜですか?

2)すべてのユーザーの友達を同じパーティションに配置し、常にuser_idを使用してアクセスしたいのでfriend、テーブルを分割する最良の方法は何ですか。user_idfriend.id の主キーを削除するか、主キーに user_id を追加する必要がありますか?

4

3 に答える 3

4

最初に、可能であれば Mysql を 5.6.5 以降にアップグレードして、パーティショニングを適切かつ最高のパフォーマンスで利用できるようにすることをお勧めします。これは GA の問題により常に可能であるとは限りませんが、私の経験では、5.5 と 5.6 の間でパフォーマンスに違いがあり、5.6 は他のタイプのパーティショニングを提供しています。

1) 私の経験では、分割されたセットでの挿入と更新は高速であり、クエリに分割している列が含まれている限り、選択も高速です。すべてのパーティションにわたるすべてのレコードのカウントを要求すると、応答が遅くなります。パーティションが別々のテーブルのように機能しているため、これは予想されることです。したがって、30 個のパーティションがある場合、1 つだけではなく 30 個のテーブルを読み取るようなものです。

パーティション化する値を主キーに含める必要があり、レコードの有効期間中は安定している必要があります。

2) 主キーに user_id と id を含めます - レコードが確立されると、友達テーブルの user_id と id がまったく変更されないと仮定します (つまり、変更は削除/挿入になります)。私の場合、それは「冗長」でしたが、アクセスする価値はありました。user_id/id または id/user_id のどちらを選択するかは、最も頻繁にアクセスする方法によって異なります。

最後のメモ。最初にデータをパーティションに分割し始めたとき、たくさんのパーティションを作成しようとしましたが、スイート スポットにヒットしたのはごくわずかで、6 ~ 12 個のパーティションが最適であることがわかりました。YMMV。

于 2012-12-05T18:32:32.717 に答える
1

1.このSQLクエリを使用して、テーブルを選択し、IDを除くすべての列を除外します。

私はあなたが必要とするものに答えます:

削除することをお勧めしFOREIGN KEYますPRIMARY KEY

私はこれが狂っていることを知っていますが、彼らはコンピュータに現在のID、最後のID、次のID、そしてこれは手動でIDを作成するよりも時間がかかるものを知るように頼むことができます. int他の方法では、Java で id を手動で作成できます。

このSQLクエリを使用してすばやく挿入します:

INSERT INTO user (id,NAME,email)
VALUES ('CREATE ID WITH JAVA', 'NAME', 'EMAIL@YAHOO.COM')

クエリがより速く動作するかどうかを判断できません...

すべてはコンピューターのパフォーマンスに依存するため、サーバーはすべてのタスクをすばやく完了することができるため、サーバーで使用するようにしてください。

選択の場合、プロファイル情報が配置されているページでは、プロファイル ID で定義された 1 人のユーザーに対して 1 つの行が必要になります。

1つだけ必要な場合と複数必要な場合は、mysql制限を使用してください... 1つの行に対して次のように制限値を変更するだけです:

select * from user where id = 999999 limit 1;

そして7行の場合:

select * from user where id = 999999 limit 7;

このクエリは、ない場合よりも高速に機能すると思います。limit また、 limit を使用してinsertも機能することを覚えておいてください

2. フレンド パーティション の場合: 答えは主キーを削除することです

主キーのないテーブルは問題ありません

もう一度、java で id を作成します... java はインターフェイスでより高速になるように設計されており、コードにはインクルードが含まれwhile ており、Java で実行できます。たとえば、すべてのフレンド データを取得する必要がある場合は、次のクエリを使用して実行を高速化します。

select fr.friend_id, usr.* from friends as fr INNER JOIN user as usr 
ON dr.friend_id = usr.id
where fr.user_id = 999999 LIMIT 10;

これで十分だと思います。申し訳ありませんが、java ではなく mysql についてしか説明できません。なぜなら、私はJavaの専門家ではありませんが、それについては理解しています

于 2012-12-07T06:37:08.337 に答える
0

1) 常に (またはほとんど) id のみを使用してデータを選択する場合、このフィールドをパーティショニング条件のベースとして使用することは明らかです。数値であるため、ハッシュ関数は必要ありません。単に範囲分割を使用します。作成するパーティションの数 (境界線として選択する数) を自分で見つける必要がありますが、@TJChambers が前述したように、約 8 ~ 10 で十分に効率的です。

テストが間違っているため、挿入が遅くなります。ランダム性なしで 1000000 行を次々と挿入するだけです。唯一の違いは、パーティション化されたテーブルの場合、mysql が余分な時間であるハッシュを計算する必要があることです。しかし、あなたの場合のように、id はパーティショニングの条件のベースです。すべての新しい行がテーブルの最後にあるため、挿入しても何も得られません。

たとえば、GPS ローカリゼーションを含むテーブルがあり、それを緯度と経度でパーティション分割した場合、たとえば各パーティションが異なる大陸である場合、挿入の違いを見ることができます。また、ランダムな(実際の)データを含むテーブルがあり、線形ではないランダムな値を挿入していると、違いが見られます。

パーティション化されたテーブルの選択は、テストが間違っているため遅くなります。

@TJChambersはそれについて私の前に書いた、あなたのクエリはすべてのパーティションで動作する必要があるため(多くのテーブルで動作するようなものです)、時間が長くなります。違いを確認するために、1 つのパーティションからデータを操作する場所を使用してみてください。

たとえば、次を実行します。

select count(*) from user_partition where id<99999;

select count(*) from user where id<99999;

違いがわかります。

2) これは難しいです。データの冗長性なしにパーティション化する方法はありません (少なくとも私の頭には思い浮かびません) が、アクセス時間 (速度の選択) が最も重要な場合、最善の方法は、ユーザー テーブルと同じ方法でパーティション化することです (範囲ID の 1 つ) を作成し、(a,b) と (b,a) の関係ごとに 2 行を挿入します。行数は2倍になりますが、4つ以上の部分に分割すると、クエリごとに処理するレコードが少なくなり、 or の必要がないことを確認する条件が1つだけになります。

このスキーマでテストしました

CREATE TABLE `test`.`friends` (
`a` INT NOT NULL ,
`b` INT NOT NULL ,
INDEX ( `a` ),
INDEX ( `b` )
) ENGINE = InnoDB;

CREATE TABLE `test`.`friends_part` (
`a` INT NOT NULL ,
`b` INT NOT NULL ,
INDEX ( `a` , `b` )
) ENGINE = InnoDB
PARTITION BY RANGE (a) (
    PARTITION p0 VALUES LESS THAN (1000),
    PARTITION p1 VALUES LESS THAN (2000),
    PARTITION p2 VALUES LESS THAN (3000),
    PARTITION p3 VALUES LESS THAN (4000),
    PARTITION p4 VALUES LESS THAN (5000),
    PARTITION p5 VALUES LESS THAN (6000),
    PARTITION p6 VALUES LESS THAN (7000),
    PARTITION p7 VALUES LESS THAN (8000),
    PARTITION p8 VALUES LESS THAN (9000),
    PARTITION p9 VALUES LESS THAN MAXVALUE
);

delimiter //
DROP procedure IF EXISTS fill_friends//
create procedure fill_friends()
begin
    declare i int default 0;
    declare a int;
    declare b int;
    while i<2000000
    do
    set a = rand()*10000;
    set b = rand()*10000;
    insert into friends values(a,b);
    set i = i + 1;
    end while;
end
//
delimiter ;

delimiter //
DROP procedure IF EXISTS fill_friends_part//
create procedure fill_friends_part()
begin
    insert into friends_part (select a,b from friends);
    insert into friends_part (select b as a, a as b from friends);
end
//
delimiter ;

私が実行したクエリは次のとおりです。

select * from friends where a=317 or b=317;

結果セット: 475 回: 1.43、0.02、0.01

select * from friends_part where a=317;

結果セット: 475 回: 0.10、0.00、0.00

select * from friends where a=4887 or b=4887;

結果セット: 483 回: 1.33、0.01、0.01

select * from friends_part where a=4887;

結果セット: 483 回: 0.06、0.01、0.00

データの一意性については気にしませんでしたが、あなたの例では一意のインデックスを使用できます。同様に InnoDB エンジンを使用しましたが、ほとんどのクエリが選択され、多くの書き込みを行わない場合は、MyISAM の方が適しています。2 回目と 3 回目はキャッシュのためか大きな違いはありませんが、1 回目は目に見える違いがあります。データベース設計の主要なルールの 1 つを破っているため高速ですが、最終的には手段が正当化されるため、非常に大きなテーブルには適したソリューションになる可能性があります。レコードが 1M 未満になる場合は、パーティショニングなしで生き残ることができると思います。

于 2012-12-09T09:37:53.877 に答える