Quan hệ bạn bè trong MySQL


8

Tôi đang phát triển mối quan hệ hữu nghị trong MySQL nơi mối quan hệ bạn bè là tương hỗ. Nếu A là bạn của B, thì B là bạn của A. Nếu một trong những người dùng kết thúc tình bạn thì mối quan hệ sẽ giảm xuống. Tôi muốn học cách nào tốt hơn.

Tôi có một hệ thống đang chạy;

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

Truy vấn này hoạt động tốt. Có lẽ tôi có thể thêm một UNION. Truy vấn phức tạp hơn truy vấn bên dưới và bảng chứa một nửa số bản ghi như bên dưới.

Một cách khác là giữ quan hệ trong các hàng riêng biệt;

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'

Truy vấn này đơn giản, mặc dù bảng chiếm không gian gấp đôi.

Mối quan tâm của tôi là; giả định rằng có hàng triệu người dùng; Cách nào sẽ làm việc nhanh hơn?

Những lợi thế và bất lợi của cả hai cách là gì?

Tôi nên ghi nhớ hoặc thay đổi những cách này? Và những vấn đề tôi có thể phải đối mặt cho cả hai cách?


Đây là một câu hỏi hay mà bạn đã hỏi ngày hôm nay. +1 cho câu hỏi của bạn.
RolandoMySQLDBA

Câu trả lời:


4

Điều đầu tiên thu hút sự chú ý của tôi là thiết lập chỉ mục cho friends.

Bạn có điều này tại thời điểm này:

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

Khi kiểm tra chéo cho tình bạn lẫn nhau, nó có thể phải chịu một ít chi phí vì userid có thể được truy xuất từ ​​bảng khi duyệt qua friendidchỉ mục. Có lẽ bạn có thể lập chỉ mục như sau:

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

Điều này có thể loại bỏ bất kỳ nhu cầu truy cập vào bảng và chỉ tìm kiếm chỉ mục.

Bây giờ, về các truy vấn, cả hai đều có thể cải thiện với chỉ mục duy nhất mới. Tạo chỉ mục duy nhất cũng giúp loại bỏ sự cần thiết phải chèn (A,B)(B,A)vào bảng vì (A,B)(B,A)dù sao cũng sẽ là chỉ mục. Do đó, truy vấn thứ hai sẽ không phải thông qua bảng để xem ai đó là bạn của người khác vì người khác đã bắt đầu tình bạn. Theo cách đó, nếu tình bạn bị phá vỡ chỉ bởi một người, thì không có tình bạn mồ côi nào là một chiều (có vẻ rất giống cuộc sống ngày nay, phải không?)

Truy vấn đầu tiên của bạn có vẻ như sẽ được hưởng lợi nhiều hơn từ chỉ mục duy nhất. Ngay cả với hàng triệu hàng, việc định vị bạn bè bằng cách sử dụng chỉ mục sẽ tránh chạm vào bảng. Tuy nhiên, vì bạn không trình bày một truy vấn UNION, tôi muốn đề xuất một truy vấn 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);

Điều này sẽ cho bạn biết ai là bạn của mỗi userid

Để xem tất cả các mối quan hệ bạn bè, hãy chạy này:

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;

Đầu tiên, đây là một số dữ liệu mẫu:

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>

Hãy xem xét tất cả các mối quan hệ

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>

Hãy xem xét tất cả 5 userid và xem các mối quan hệ có được hiển thị chính xác không

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>

Tất cả đều đúng với tôi.

Bây giờ, hãy sử dụng truy vấn thứ hai của bạn để xem nó có khớp không ...

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>

Tại sao không khớp? Đó là bởi vì tôi đã không tải (B,A)cho mọi (A,B). Hãy để tôi tải các (B,A)mối quan hệ và thử lại truy vấn thứ hai của bạn.

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>

Họ vẫn không hợp nhau. Đó là bởi vì truy vấn thứ hai của bạn chỉ kiểm tra một bên.

Hãy kiểm tra truy vấn đầu tiên của bạn theo mọi giá trị chỉ với (A, B) chứ không phải (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>

Đầu tiên của bạn hoạt động tốt. Tôi chắc chắn rằng nó được hưởng lợi từ chỉ số duy nhất như tôi đã nói trước đó, nhưng IMHO tôi nghĩ rằng UNION đơn giản hơn. Với chỉ số duy nhất, nó sẽ xuất hiện là sáu trong số một và một nửa tá khác về mặt thực thi và đầu ra.

Bạn sẽ phải điểm chuẩn truy vấn đầu tiên của bạn theo UNION gợi ý của tôi và xem.

Đây là một câu hỏi hay mà bạn đã hỏi ngày hôm nay. +1 cho câu hỏi của bạn.


Tôi đã thực hiện một số thử nghiệm để xem thiết lập hiện tại nhanh như thế nào. Tôi đã không thay đổi sơ đồ của các bảng. Truy vấn đầu tiên 1.000.000 hàng (bảng người dùng) 2.045.007 hàng (bảng bạn bè - một hàng cho mỗi mối quan hệ. Tình bạn được tạo ngẫu nhiên cho 10.000 người dùng) Truy vấn đầu tiên mất 0,01094 giây để trả về 600 hàng. Cùng một truy vấn thay đổi với UNION mất 0,0086 để trả về 600 hàng. Truy vấn thứ hai 1.000.000 hàng (bảng người dùng) 4.048.781 hàng (bảng friends_twoway - hai hàng cho mỗi mối quan hệ) Truy vấn thứ hai trong bài đăng đầu tiên của tôi mất 0,0090 giây. để trả lại 600 hàng. Bạn nghĩ gì về những kết quả này?
kent ilyuk

Sau một loạt các thử nghiệm, tôi sẽ thay đổi cài đặt bảng, thêm các chỉ mục khác nhau như bạn đề xuất.
kent ilyuk

Trong thử nghiệm đầu tiên của bạn, 0,0086 (với UNION) tốt hơn 0,01094 (không có UNION). Trên thực tế, đó là 27,21% nhanh hơn. Hiệu suất của truy vấn đầu tiên của bạn với số lượng dữ liệu gấp đôi, chậm hơn .0004 giây. Ngay cả với các số đã cho, tôi vẫn sẽ nghiêng về UNION khi chỉ có dữ liệu và tạo một chỉ mục duy nhất vì các chỉ mục sẽ được sử dụng đầy đủ trong truy vấn và để dữ liệu một mình.
RolandoMySQLDBA

Tôi đã thay thế friendid-key thành khóa duy nhất ( friendid, userid) và bây giờ kết quả là khoảng 0,00794 Đây có phải là nhanh nhất có thể? Nhìn vào kết quả bạn có nghĩ rằng cách đầu tiên là tốt hơn (một hàng cho mỗi mối quan hệ)? Bởi vì nó ít hơn hai lần so với không gian thứ hai và kết quả tương đương với các thiết lập hiện tại.
kent ilyuk

Trong trường hợp cụ thể của bạn, ít dữ liệu là tốt vì dựa vào các chỉ mục. Các chỉ số là cồng kềnh nhưng cho một mục đích có lợi. Đây là một khái niệm gọi là bao gồm các chỉ số, mà mục đích là để chỉ tạo ra mà WHERE, GROUP BYORDER BYđiều khoản dẫn đến dữ liệu được đọc từ chỉ lập chỉ mục. Dưới đây là một số liên kết tốt chứng minh việc sử dụng các khóa chính và duy nhất như bao gồm các chỉ mục: 1) peter-zaitsev.livejournal.com/6949.html , 2) mysqlperformanceblog.com/2006/11/23/ trên , 3) ronaldbradford .com / blog / tag / cover-index
RolandoMySQLDBA
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.