Hubungan teman di MySQL


8

Saya mengembangkan hubungan pertemanan di MySQL di mana hubungan pertemanan saling menguntungkan. Jika A adalah teman B, maka B adalah teman A. Jika salah satu pengguna mengakhiri pertemanan maka relasinya turun. Saya ingin belajar mana yang lebih baik.

Saya memiliki sistem yang sedang berjalan;

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' ) 

Kueri ini berfungsi dengan baik. Mungkin saya bisa menambahkan UNION. Kueri lebih rumit daripada yang di bawah ini dan tabel tersebut berisi setengah dari jumlah rekaman seperti yang di bawah ini.

Cara lain adalah menjaga hubungan di baris yang terpisah;

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'

Kueri ini sederhana, meskipun tabel membutuhkan ruang dua kali lebih banyak.

Kekhawatiran saya adalah; dengan asumsi bahwa ada jutaan pengguna; jalan mana yang akan bekerja lebih cepat?

Apa kelebihan dan kekurangan dari kedua cara tersebut?

Apa yang harus saya ingat atau ubah untuk cara-cara ini? Dan masalah apa yang bisa saya hadapi untuk kedua cara?


Ini adalah pertanyaan bagus yang Anda tanyakan hari ini. +1 untuk pertanyaan Anda.
RolandoMySQLDBA

Jawaban:


4

Hal pertama yang menarik perhatian saya adalah pengaturan indeks untuk friends.

Anda memiliki ini saat ini:

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

Saat memeriksa ulang untuk persahabatan bersama, hal itu dapat menimbulkan sedikit biaya karena userid dapat diambil dari tabel saat melintasi friendidindeks. Mungkin Anda bisa mengindeks sebagai berikut:

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

Ini dapat menghapus kebutuhan untuk mengakses tabel dan mencari indeks saja.

Sekarang, dalam hal kueri, keduanya dapat meningkat dengan indeks unik baru. Membuat indeks unik juga menghilangkan kebutuhan untuk memasukkan (A,B)dan (B,A)ke dalam tabel karena (A,B)dan (B,A)akan tetap menjadi indeks. Dengan demikian, kueri kedua tidak harus melalui tabel untuk melihat apakah seseorang adalah teman orang lain karena orang lain yang memulai persahabatan. Dengan begitu, jika pertemanan dipatahkan oleh satu orang saja, tidak ada persahabatan yatim yang satu sisi (sepertinya sangat mirip dengan kehidupan akhir-akhir ini, bukan?)

Permintaan pertama Anda sepertinya akan mendapat manfaat lebih dari indeks unik. Bahkan dengan jutaan baris, mencari teman menggunakan indeks saja tidak akan menyentuh meja. Namun, karena Anda tidak menyajikan kueri UNION, saya ingin merekomendasikan kueri 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);

Ini akan membuat Anda melihat siapa teman dari setiap userid

Untuk melihat semua pertemanan, jalankan ini:

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;

Pertama, ini beberapa data sampel:

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>

Mari kita lihat semua hubungannya

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>

Mari kita lihat ke-5 userid dan lihat apakah hubungannya ditampilkan dengan benar

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>

Mereka semua terlihat benar bagiku.

Sekarang, mari gunakan permintaan kedua Anda untuk melihat apakah itu cocok ...

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>

Kenapa tidak cocok? Itu karena saya tidak memuat (B,A)untuk setiap (A,B). Biarkan saya memuat (B,A)hubungan dan coba permintaan kedua Anda lagi.

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>

Mereka masih belum cocok. Itu karena permintaan kedua Anda hanya memeriksa satu sisi.

Mari kita periksa permintaan pertama Anda terhadap setiap nilai hanya dengan (A, B) dan tidak (B, A):

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>

Pertama Anda bekerja dengan baik. Saya yakin itu mendapat manfaat dari indeks unik seperti yang saya katakan sebelumnya, tapi IMHO saya pikir UNION lebih sederhana. Dengan indeks unik, akan tampak enam dari satu setengah lusin lainnya dalam hal eksekusi dan output.

Anda harus membandingkan permintaan pertama Anda dengan UNION saran saya dan lihat.

Ini adalah pertanyaan bagus yang Anda tanyakan hari ini. +1 untuk pertanyaan Anda.


Saya telah melakukan beberapa tes untuk melihat seberapa cepat pengaturan saat ini. Saya belum mengubah skema tabel. Kueri pertama 1.000.000 baris (tabel pengguna) 2.045.007 baris (tabel teman - satu baris untuk setiap relasi. Persahabatan dibuat secara acak untuk 10.000 pengguna) Kueri pertama membutuhkan 0,01094 detik untuk mengembalikan 600 baris. Permintaan yang sama diubah dengan UNION mengambil 0,0086 untuk mengembalikan 600 baris. Kueri kedua 1.000.000 baris (tabel pengguna) 4.048.781 baris (tabel friends_twoway - dua baris untuk setiap relasi) Kueri kedua dalam posting pertama saya membutuhkan 0,0090 detik. untuk mengembalikan 600 baris. Apa pendapat Anda tentang hasil ini?
kent ilyuk

Setelah banyak tes, saya akan mengubah pengaturan tabel, menambahkan indeks yang berbeda seperti yang Anda sarankan.
kent ilyuk

Dalam tes pertama Anda, 0,0086 (dengan UNION) lebih baik daripada 0,01094 (tanpa UNION). Bahkan, itu 27,21% lebih cepat. Kinerja kueri pertama Anda dengan data dua kali lebih banyak .0004 detik lebih lambat. Bahkan dengan angka-angka yang diberikan, saya akan tetap mendukung UNION dengan hanya memiliki data dan membuat indeks yang unik karena indeks akan sepenuhnya digunakan dalam permintaan dan meninggalkan data sendirian.
RolandoMySQLDBA

Saya telah mengganti kunci teman ke kunci unik ( friendid, userid) dan sekarang hasilnya sekitar 0,00794 Apakah ini secepat mungkin? Melihat hasilnya apakah menurut Anda cara pertama lebih baik (satu baris untuk setiap relasi)? Karena ini dua kali lebih sedikit ruang daripada yang kedua dan hasilnya hampir sama dengan pengaturan saat ini.
kent ilyuk

Dalam kasus khusus Anda, lebih sedikit data yang baik karena mengandalkan indeks. Indeks ini membengkak tetapi untuk tujuan yang bermanfaat. Ini adalah konsep yang disebut meliputi indeks, yang tujuannya adalah untuk indeks dibuat yang WHERE, GROUP BYdan ORDER BYklausa menghasilkan data yang dibaca dari indeks saja. Berikut adalah beberapa tautan bagus yang membenarkan penggunaan kunci unik dan utama sebagai meliputi indeks: 1) peter-zaitsev.livejournal.com/6949.html , 2) mysqlperformanceblog.com/2006/11/23/… , 3) ronaldbradford .com / blog / tag / meliputi-indeks
RolandoMySQLDBA
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.