Freundesbeziehungen in MySQL

8

Ich entwickle eine Freundschaftsbeziehung in MySQL, in der die Freundschaftsbeziehung gegenseitig ist. Wenn A ein Freund von B ist, ist B ein Freund von A. Wenn einer der Benutzer die Freundschaft beendet, wird die Beziehung unterbrochen. Ich möchte lernen, welcher Weg besser ist.

Ich habe ein laufendes System;

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

Diese Abfrage funktioniert gut. Vielleicht kann ich eine hinzufügen UNION. Die Abfrage ist komplizierter als die folgende und die Tabelle enthält halb so viele Datensätze wie die folgende.

Eine andere Möglichkeit besteht darin, die Beziehungen in separaten Zeilen zu halten.

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'

Diese Abfrage ist einfach, obwohl die Tabelle doppelt so viel Speicherplatz benötigt.

Meine Sorge ist; unter der Annahme, dass es Millionen von Benutzern gibt; Welcher Weg wird schneller funktionieren?

Was sind die Vor- und Nachteile beider Wege?

Was muss ich beachten oder auf diese Weise ändern? Und welchen Problemen kann ich in beide Richtungen begegnen?

kent ilyuk
quelle
Dies war eine gute Frage, die Sie heute gestellt haben. +1 für deine Frage.
RolandoMySQLDBA

Antworten:

4

Das erste, was mir auffällt, ist das Index-Setup für friends.

Sie haben dies im Moment:

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

Bei der Überprüfung auf gegenseitige Freundschaft kann dies zu geringen Kosten führen, da die Benutzer-ID beim Durchlaufen des friendidIndex möglicherweise aus der Tabelle abgerufen wird . Vielleicht könnten Sie wie folgt indizieren:

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

Dadurch entfällt möglicherweise die Notwendigkeit, auf die Tabelle zuzugreifen und nur den Index zu durchsuchen.

In Bezug auf die Abfragen können sich beide mit dem neuen eindeutigen Index verbessern. Den eindeutigen Index Erstellen entfällt auch die Notwendigkeit einzufügen (A,B)und (B,A)in der Tabelle da (A,B)und (B,A)den Index ohnehin wäre. Somit müsste die zweite Abfrage nicht durch die Tabelle blättern, um zu sehen, ob jemand ein Freund von jemand anderem ist, weil eine andere Person die Freundschaft initiiert hat. Auf diese Weise gibt es, wenn die Freundschaft nur von einer Person gebrochen wird, keine verwaisten Freundschaften, die einseitig sind (scheint heutzutage dem Leben sehr ähnlich zu sein, nicht wahr?)

Ihre erste Abfrage scheint mehr vom eindeutigen Index zu profitieren. Selbst bei Millionen von Zeilen würde das Auffinden von Freunden, die nur die Indizes verwenden, das Berühren der Tabelle vermeiden. Da Sie jedoch keine UNION-Abfrage vorgelegt haben, möchte ich eine UNION-Abfrage empfehlen:

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

Auf diese Weise können Sie sehen, wer die Freunde jeder Benutzer-ID sind

Führen Sie Folgendes aus, um alle Freundschaften anzuzeigen:

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;

Hier sind zunächst einige Beispieldaten:

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>

Schauen wir uns alle Beziehungen an

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>

Schauen wir uns alle 5 Benutzer-IDs an und sehen, ob die Beziehungen korrekt angezeigt werden

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>

Sie sehen für mich alle richtig aus.

Lassen Sie uns nun Ihre zweite Abfrage verwenden, um zu sehen, ob sie übereinstimmt ...

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>

Warum nicht zusammenpassen? Das liegt daran, dass ich das nicht (B,A)für jeden geladen habe (A,B). Lassen Sie mich die (B,A)Beziehungen laden und Ihre zweite Abfrage erneut versuchen.

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>

Sie passen immer noch nicht zusammen. Das liegt daran, dass Ihre zweite Abfrage nur eine Seite überprüft.

Lassen Sie uns Ihre erste Abfrage mit jedem Wert nur mit (A, B) und nicht mit (B, A) vergleichen:

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>

Dein erstes funktioniert gut. Ich bin sicher, dass es von dem einzigartigen Index profitiert, wie ich bereits sagte, aber meiner Meinung nach ist die UNION einfacher. Mit einem eindeutigen Index scheint es in Bezug auf Ausführung und Ausgabe sechs von anderthalb Dutzend der anderen zu sein.

Sie müssten Ihre erste Anfrage mit meinem Vorschlag UNION vergleichen und sehen.

Dies war eine gute Frage, die Sie heute gestellt haben. +1 für deine Frage.

RolandoMySQLDBA
quelle
Ich habe einige Tests durchgeführt, um festzustellen, wie schnell das aktuelle Setup ist. Ich habe das Schema der Tabellen nicht geändert. Erste Abfrage 1.000.000 Zeilen (Benutzertabelle) 2.045.007 Zeilen (Freundes-Tabelle - eine Zeile für jede Beziehung. Freundschaften werden zufällig für 10.000 Benutzer erstellt) Die erste Abfrage dauert 0,01094 Sekunden, um 600 Zeilen zurückzugeben. Dieselbe mit UNION geänderte Abfrage benötigt 0,0086, um 600 Zeilen zurückzugeben. Zweite Abfrage 1.000.000 Zeilen (Benutzertabelle) 4.048.781 Zeilen (Tabelle friends_twoway - zwei Zeilen für jede Beziehung) Die zweite Abfrage in meinem ersten Beitrag dauert 0,0090 Sekunden. 600 Zeilen zurückgeben. Was denkst du über diese Ergebnisse?
Kent Ilyuk
Nach einer Reihe von Tests werde ich die Tabelleneinstellungen ändern und verschiedene Indizes hinzufügen, wie Sie vorgeschlagen haben.
Kent Ilyuk
In Ihrem ersten Test ist .0086 (mit UNION) besser als .01094 (ohne UNION). Tatsächlich ist das 27,21% schneller. Die Leistung Ihrer ersten Abfrage mit doppelt so vielen Daten ist .0004 Sekunden langsamer. Selbst mit den angegebenen Zahlen würde ich mich immer noch für UNION entscheiden, nur die Daten zu haben und einen eindeutigen Index zu erstellen, da die Indizes vollständig in der Abfrage verwendet würden und die Daten in Ruhe lassen würden.
RolandoMySQLDBA
Ich habe den Friendid-Schlüssel durch den eindeutigen Schlüssel ( friendid, userid) ersetzt und jetzt sind die Ergebnisse ungefähr .00794. Ist das so schnell wie möglich? Wenn Sie sich die Ergebnisse ansehen, denken Sie, dass der erste Weg besser ist (eine Zeile für jede Beziehung)? Weil es doppelt so wenig Speicherplatz wie das zweite ist und die Ergebnisse mit den aktuellen Setups ungefähr gleich sind.
Kent Ilyuk
In Ihrem speziellen Fall sind weniger Daten gut, da Sie sich auf die Indizes verlassen. Die Indizes sind aufgebläht, aber zu einem vorteilhaften Zweck. Dies ist ein Konzept Indizes genannt abdeckt, deren Zweck erstellten Indizes ist , deren WHERE, GROUP BYund ORDER BYKlauseln führen zu Daten nur von Indizes gelesen werden. Hier sind einige gute Links, die die Verwendung der eindeutigen und Primärschlüssel als Indexe rechtfertigen: 1) peter-zaitsev.livejournal.com/6949.html , 2) mysqlperformanceblog.com/2006/11/23/… , 3) ronaldbradford .com / blog / tag / covering-index
RolandoMySQLDBA