MySQLの友達関係


8

私はMySQLで友情関係を築いています。友好関係は相互関係です。AがBの友達である場合、BはAの友達です。ユーザーの1人が友情を終了した場合、関係は低下します。どっちがいいのか知りたい。

実行中のシステムがあります。

user
-----------
userid p.k
name 

friends
-------
userid
friendid
primary key (`userid`,`friendid`),
key `friendid` (`friendid`)

1 2
2 5
1 3


To get all of my friends;
SELECT u.name, f.friendid , IF(f.userid = $userid, f.friendid, f.userid) friendid 
FROM friends f 
    inner join user u  ON ( u.userid = IF(f.userid = $userid, f.friendid, f.userid)) 
WHERE ( f.userid = '$userid' or f.friendid = '$userid' ) 

このクエリはうまく機能します。多分私は追加することができますUNION。クエリは下のクエリよりも複雑で、テーブルには下のレコードの半分の数のレコードが含まれています。

別の方法は、リレーションを別々の行に保持することです。

1 2
2 1
2 5
5 2
1 3
3 1

SELECT u.name, f.friendid 
FROM friends f inner join user u ON ( u.userid = f.friendid ) 
WHERE f.userid = '$userid'

このクエリは単純ですが、テーブルのスペースは2倍になります。

私の懸念は; 数百万のユーザーがいると仮定します。どちらの方法が速く動作しますか?

両方の方法の長所と短所は何ですか?

これらの方法について、何を覚えておくか、変更する必要がありますか?そして、私は両方の方法でどのような問題に直面することができますか?


これはあなたが今日尋ねた良い質問でした。あなたの質問に+1してください。
RolandoMySQLDBA 2012

回答:


4

最初に目を引くのは、のインデックス設定ですfriends

あなたは現在これを持っています:

friends
-------
userid
friendid
primary key (`userid`,`friendid`),
key `friendid` (`friendid`)

相互の友情をクロスチェックする場合、friendidインデックスをトラバースするときにユーザーIDがテーブルから取得される可能性があるため、少し費用がかかる可能性があります。おそらく、次のように索引付けできます。

friends
-------
userid
friendid
primary key (`userid`,`friendid`),
unique key `friendid` (`friendid`,`userid`)

これにより、テーブルにアクセスしてインデックスのみを検索する必要がなくなる可能性があります。

現在、クエリに関しては、新しい一意のインデックスを使用することで両方とも改善される可能性があります。一意のインデックスを作成するとも挿入する必要がなくなり(A,B)および(B,A)ので、テーブルへ(A,B)(B,A)、とにかくインデックスだろうが。したがって、2番目のクエリは、別の人が友情を開始したため、誰かが他の人の友達であるかどうかを確認するためにテーブルを調べる必要はありません。このようにして、友情が1人だけで破られた場合、一方的な孤立した友情はありません(最近の生活によく似ているようですね)。

最初のクエリは、一意のインデックスの方がメリットがあるようです。何百万もの行がある場合でも、インデックスのみを使用して友達を見つけると、テーブルに触れないようになります。それでも、UNIONクエリを提示しなかったので、UNIONクエリをお勧めします。

SET @givenuserid = ?;
SELECT B.name "Friend's Name"
FROM 
(
    SELECT userid FROM friends WHERE friendid=@givenuserid
    UNION
    SELECT friendid FROM friends WHERE userid=@givenuserid
) A INNER JOIN user B USING (userid);

これにより、各ユーザーIDの友達を確認できます

すべての友情を表示するには、次のコマンドを実行します。

SELECT A.userid,A.name,B.friendid,C.name
FROM user A
INNER JOIN friends B ON A.userid=B.userid
INNER JOIN user C on B.friendid=C.userid;

まず、ここにいくつかのサンプルデータがあります:

mysql> drop database if exists key_ilyuk;
Query OK, 2 rows affected (0.01 sec)

mysql> create database key_ilyuk;
Query OK, 1 row affected (0.00 sec)

mysql> use key_ilyuk
Database changed
mysql> create table user
    -> (
    ->     userid INT NOT NULL AUTO_INCREMENT,
    ->     name varchar(20),
    ->     primary key(userid)
    -> ) ENGINE=MyISAM;
Query OK, 0 rows affected (0.04 sec)

mysql> insert into user (name) values
    -> ('rolando'),('pamela'),('dominique'),('carlik'),('diamond');
Query OK, 5 rows affected (0.01 sec)
Records: 5  Duplicates: 0  Warnings: 0

mysql> create table friends
    -> (
    ->     userid INT NOT NULL,
    ->     friendid INT NOT NULL,
    ->     primary key (userid,friendid),
    ->     unique key (friendid,userid)
    -> ) ENGINE=MyISAM;
Query OK, 0 rows affected (0.03 sec)

mysql> insert into friends values (1,2),(2,5),(1,3);
Query OK, 3 rows affected (0.00 sec)
Records: 3  Duplicates: 0  Warnings: 0

mysql> select * from user;
+--------+-----------+
| userid | name      |
+--------+-----------+
|      1 | rolando   |
|      2 | pamela    |
|      3 | dominique |
|      4 | carlik    |
|      5 | diamond   |
+--------+-----------+
5 rows in set (0.00 sec)

mysql> select * from friends;
+--------+----------+
| userid | friendid |
+--------+----------+
|      1 |        2 |
|      1 |        3 |
|      2 |        5 |
+--------+----------+
3 rows in set (0.00 sec)

mysql>

すべての関係を見てみましょう

mysql> SELECT A.userid,A.name,B.friendid,C.name
    -> FROM user A
    -> INNER JOIN friends B ON A.userid=B.userid
    -> INNER JOIN user C on B.friendid=C.userid
    -> ;
+--------+---------+----------+-----------+
| userid | name    | friendid | name      |
+--------+---------+----------+-----------+
|      1 | rolando |        2 | pamela    |
|      1 | rolando |        3 | dominique |
|      2 | pamela  |        5 | diamond   |
+--------+---------+----------+-----------+
3 rows in set (0.00 sec)

mysql>

5つのユーザーIDをすべて見て、関係が正しく表示されているかどうかを確認しましょう

mysql> SET @givenuserid = 1;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT B.name "Friend's Name"
    -> FROM
    -> (
    ->     SELECT userid FROM friends WHERE friendid=@givenuserid
    ->     UNION
    ->     SELECT friendid FROM friends WHERE userid=@givenuserid
    -> ) A INNER JOIN user B USING (userid);
+---------------+
| Friend's Name |
+---------------+
| pamela        |
| dominique     |
+---------------+
2 rows in set (0.00 sec)

mysql> SET @givenuserid = 2;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT B.name "Friend's Name"
    -> FROM
    -> (
    ->     SELECT userid FROM friends WHERE friendid=@givenuserid
    ->     UNION
    ->     SELECT friendid FROM friends WHERE userid=@givenuserid
    -> ) A INNER JOIN user B USING (userid);
+---------------+
| Friend's Name |
+---------------+
| rolando       |
| diamond       |
+---------------+
2 rows in set (0.00 sec)

mysql> SET @givenuserid = 3;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT B.name "Friend's Name"
    -> FROM
    -> (
    ->     SELECT userid FROM friends WHERE friendid=@givenuserid
    ->     UNION
    ->     SELECT friendid FROM friends WHERE userid=@givenuserid
    -> ) A INNER JOIN user B USING (userid);
+---------------+
| Friend's Name |
+---------------+
| rolando       |
+---------------+
1 row in set (0.01 sec)

mysql> SET @givenuserid = 4;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT B.name "Friend's Name"
    -> FROM
    -> (
    ->     SELECT userid FROM friends WHERE friendid=@givenuserid
    ->     UNION
    ->     SELECT friendid FROM friends WHERE userid=@givenuserid
    -> ) A INNER JOIN user B USING (userid);
Empty set (0.00 sec)

mysql> SET @givenuserid = 5;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT B.name "Friend's Name"
    -> FROM
    -> (
    ->     SELECT userid FROM friends WHERE friendid=@givenuserid
    ->     UNION
    ->     SELECT friendid FROM friends WHERE userid=@givenuserid
    -> ) A INNER JOIN user B USING (userid);
+---------------+
| Friend's Name |
+---------------+
| pamela        |
+---------------+
1 row in set (0.00 sec)

mysql>

それらはすべて私には正しいように見えます。

次に、2番目のクエリを使用して、一致するかどうかを確認します...

mysql> SET @givenuserid = 1;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT u.name, f.friendid
    -> FROM friends f inner join user u ON ( u.userid = f.friendid )
    -> WHERE f.userid = @givenuserid;
+-----------+----------+
| name      | friendid |
+-----------+----------+
| pamela    |        2 |
| dominique |        3 |
+-----------+----------+
2 rows in set (0.00 sec)

mysql> SET @givenuserid = 2;
Query OK, 0 rows affected (0.01 sec)

mysql> SELECT u.name, f.friendid
    -> FROM friends f inner join user u ON ( u.userid = f.friendid )
    -> WHERE f.userid = @givenuserid;
+---------+----------+
| name    | friendid |
+---------+----------+
| diamond |        5 |
+---------+----------+
1 row in set (0.00 sec)

mysql> SET @givenuserid = 3;
Query OK, 0 rows affected (0.01 sec)

mysql> SELECT u.name, f.friendid
    -> FROM friends f inner join user u ON ( u.userid = f.friendid )
    -> WHERE f.userid = @givenuserid;
Empty set (0.00 sec)

mysql> SET @givenuserid = 4;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT u.name, f.friendid
    -> FROM friends f inner join user u ON ( u.userid = f.friendid )
    -> WHERE f.userid = @givenuserid;
Empty set (0.00 sec)

mysql> SET @givenuserid = 5;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT u.name, f.friendid
    -> FROM friends f inner join user u ON ( u.userid = f.friendid )
    -> WHERE f.userid = @givenuserid;
Empty set (0.00 sec)

mysql>

なぜ一致しないのですか?これは、(B,A)forをロードしなかったため(A,B)です。(B,A)リレーションシップを読み込んで、2番目のクエリをもう一度試してみましょう。

mysql> insert into friends values (2,1),(5,2),(3,1);
Query OK, 3 rows affected (0.02 sec)
Records: 3  Duplicates: 0  Warnings: 0

mysql> SET @givenuserid = 1;
Query OK, 0 rows affected (0.01 sec)

mysql> SELECT u.name, f.friendid
    -> FROM friends f inner join user u ON ( u.userid = f.friendid )
    -> WHERE f.userid = @givenuserid;
+-----------+----------+
| name      | friendid |
+-----------+----------+
| pamela    |        2 |
| dominique |        3 |
+-----------+----------+
2 rows in set (0.00 sec)

mysql> SET @givenuserid = 2;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT u.name, f.friendid
    -> FROM friends f inner join user u ON ( u.userid = f.friendid )
    -> WHERE f.userid = @givenuserid;
+---------+----------+
| name    | friendid |
+---------+----------+
| rolando |        1 |
| diamond |        5 |
+---------+----------+
2 rows in set (0.00 sec)

mysql> SET @givenuserid = 3;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT u.name, f.friendid
    -> FROM friends f inner join user u ON ( u.userid = f.friendid )
    -> WHERE f.userid = @givenuserid;
+---------+----------+
| name    | friendid |
+---------+----------+
| rolando |        1 |
+---------+----------+
1 row in set (0.00 sec)

mysql> SET @givenuserid = 4;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT u.name, f.friendid
    -> FROM friends f inner join user u ON ( u.userid = f.friendid )
    -> WHERE f.userid = @givenuserid;
Empty set (0.00 sec)

mysql> SET @givenuserid = 5;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT u.name, f.friendid
    -> FROM friends f inner join user u ON ( u.userid = f.friendid )
    -> WHERE f.userid = @givenuserid;
+--------+----------+
| name   | friendid |
+--------+----------+
| pamela |        2 |
+--------+----------+
1 row in set (0.00 sec)

mysql>

彼らはまだ一致しません。これは、2番目のクエリが片側のみをチェックしているためです。

(B、A)ではなく(A、B)のみを含むすべての値に対して最初のクエリをチェックしてみましょう。

mysql> SET @givenuserid = 1;
SELECT u.name, f.friendid userid, IF(f.userid = @givenuserid, f.friendid, f.userid) friendid
FROM friends f
    inner join user u  ON ( u.userid = IF(f.userid = @givenuserid, f.friendid, f.userid))
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT u.name, f.friendid userid, IF(f.userid = @givenuserid, f.friendid, f.userid) friendid
    -> FROM friends f
    ->     inner join user u  ON ( u.userid = IF(f.userid = @givenuserid, f.friendid, f.userid))
    -> WHERE ( f.userid = @givenuserid or f.friendid = @givenuserid  );
+-----------+--------+----------+
| name      | userid | friendid |
+-----------+--------+----------+
| pamela    |      2 |        2 |
| dominique |      3 |        3 |
+-----------+--------+----------+
2 rows in set (0.00 sec)

mysql> SET @givenuserid = 2;
FROM friends f
    inner join user u  ON ( u.userid = IF(f.userid = @givenuserid, f.friendid, f.userid))
WHERE ( f.userid = @givenuserid or f.friendid = @givenuserid  );
Query OK, 0 rows affected (0.01 sec)

mysql> SELECT u.name, f.friendid userid, IF(f.userid = @givenuserid, f.friendid, f.userid) friendid
    -> FROM friends f
    ->     inner join user u  ON ( u.userid = IF(f.userid = @givenuserid, f.friendid, f.userid))
    -> WHERE ( f.userid = @givenuserid or f.friendid = @givenuserid  );
+---------+--------+----------+
| name    | userid | friendid |
+---------+--------+----------+
| rolando |      2 |        1 |
| diamond |      5 |        5 |
+---------+--------+----------+
2 rows in set (0.00 sec)

mysql> SET @givenuserid = 3;
SELECT u.name, f.friendid userid, IF(f.userid = @givenuserid, f.friendid, f.userid) friendid
FROM friends f
    inner join user u  ON ( u.userid = IF(f.userid = @givenuserid, f.friendid, f.userid))
WHERE ( f.userid = @givenuserid or f.friendid = @givenuserid  );
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT u.name, f.friendid userid, IF(f.userid = @givenuserid, f.friendid, f.userid) friendid
    -> FROM friends f
    ->     inner join user u  ON ( u.userid = IF(f.userid = @givenuserid, f.friendid, f.userid))
    -> WHERE ( f.userid = @givenuserid or f.friendid = @givenuserid  );
+---------+--------+----------+
| name    | userid | friendid |
+---------+--------+----------+
| rolando |      3 |        1 |
+---------+--------+----------+
1 row in set (0.00 sec)

mysql> SET @givenuserid = 4;
FROM friends f
    inner join user u  ON ( u.userid = IF(f.userid = @givenuserid, f.friendid, f.userid))
WHERE ( f.userid = @givenuserid or f.friendid = @givenuserid  );
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT u.name, f.friendid userid, IF(f.userid = @givenuserid, f.friendid, f.userid) friendid
    -> FROM friends f
    ->     inner join user u  ON ( u.userid = IF(f.userid = @givenuserid, f.friendid, f.userid))
    -> WHERE ( f.userid = @givenuserid or f.friendid = @givenuserid  );
Empty set (0.01 sec)

mysql> SET @givenuserid = 5;
FROM friends f
Query OK, 0 rows affected (0.00 sec)

    inner join user u  ON ( u.userid = IF(f.userid = @givenuserid, f.friendid, f.userid))
mysql> SELECT u.name, f.friendid userid, IF(f.userid = @givenuserid, f.friendid, f.userid) friendid
    -> FROM friends f
    ->     inner join user u  ON ( u.userid = IF(f.userid = @givenuserid, f.friendid, f.userid))
    -> WHERE ( f.userid = @givenuserid or f.friendid = @givenuserid  );
+--------+--------+----------+
| name   | userid | friendid |
+--------+--------+----------+
| pamela |      5 |        2 |
+--------+--------+----------+
1 row in set (0.00 sec)

mysql>

あなたの最初はうまくいきます。先に述べたように、一意のインデックスの恩恵を受けていると確信していますが、私見UNIONの方が簡単だと思います。一意のインデックスを使用すると、実行と出力の点で6分の1と他の6分の1のように見えます。

あなたは私の提案UNIONに対して最初のクエリをベンチマークし、参照する必要があります。

これはあなたが今日尋ねた良い質問でした。質問に+1してください。


現在の設定がどれだけ速いかを確認するためにいくつかのテストを行いました。テーブルのスキームは変更していません。最初のクエリ1,000,000行(ユーザーテーブル)2,045,007行(friendsテーブル-リレーションごとに1行。友情は10.000ユーザーに対してランダムに作成されます)最初のクエリは600行を返すのに0.01094秒かかります。UNIONで変更された同じクエリは、600行を返すのに0.0086かかります。2番目のクエリ1,000,000行(ユーザーテーブル)4,048,781行(friends_twowayテーブル-リレーションごとに2行)最初の投稿の2番目のクエリには0.0090秒かかります。600行を返します。これらの結果についてどう思いますか?
ケントilyuk 2012

一連のテストの後、テーブルの設定を変更し、提案されたように別のインデックスを追加します。
kent ilyuk 2012

最初のテストでは、.0086(UNIONあり)は.01094(UNIONなし)よりも優れています。実際、これは27.21%高速です。2倍のデータを使用した最初のクエリのパフォーマンスは、.0004秒遅くなります。与えられた数値があったとしても、インデックスのみがクエリで完全に使用され、データはそのままにされるため、データのみを持ち、一意のインデックスを作成するUNIONを優先します。
RolandoMySQLDBA 2012

friendid-keyを一意のキー(friendiduserid)に置き換えました。結果は約.00794です。これは可能な限り高速ですか?結果を見て、最初の方法の方が良いと思いますか(関係ごとに1行)。それは2番目のものより2倍少ないスペースであり、結果は現在のセットアップとほぼ同じだからです。
kent ilyuk 2012

特定のケースでは、インデックスに依存しているため、少ないデータで十分です。インデックスは肥大化していますが、有益な目的のためです。これは、その目的は、その作成されたインデックスにあるインデックスをカバーすると呼ばれる概念でありWHEREGROUP BYおよびORDER BY句は唯一のインデックスから読まれるデータにつながります。インデックスをカバーするための一意の主キーの使用を正当化するいくつかの優れたリンクを以下に示します。1)peter-zaitsev.livejournal.com/6949.html、2mysqlperformanceblog.com/2006/11/23/…、3ronaldbradford .com / blog / tag / covering-index
RolandoMySQLDBA '10
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.