用MySQL實現微博關注關係的方案分析

目錄[-]數據庫

用MySQL實現微博關注關係的方案分析


關注關係產生的四種關係狀態

  • 關注事務

  • 粉絲內存

  • 雙向關注(互粉)

  • 無關係

用詞follower表示粉絲 – 追隨者
用詞following表示關注 – 追隨

設計的結構必須能知足如下功能:

  • 查詢關注列表

  • 查詢粉絲列表

  • 查詢雙向關注列表

  • 判斷兩個用戶的關係

  • 查詢帶關係狀態的任一列表

第一種方案

用一行紀錄表示關注和粉絲,字段u2的值表示粉絲,u1表示被關注者。

Table: user(用戶表)
3.png

Table: follower(u2表示粉絲, u1表示被關注的人)
1.png

查詢用戶id = 1的關注列表

SELECT * FROM follower WHERE u2 = 1

查詢用戶id = 1的粉絲列表

SELECT * FROM follower WHERE u1 = 1

查詢用戶id = 1的雙向關注列表

SELECT t1.* FROM (SELECT * FROM follower WHERE u2 = 1)  AS t1 INNER JOIN follower t2 ON t1.u1 = t2.u2 LIMIT 10

判斷兩個用戶的關係(id = 1 –> id = 5)

SELECT * FROM follower WHERE (u2 = 1 or u1 = 1) AND (u2 = 5 or u1 = 5) LIMIT 3

id = 1的用戶查詢全部 id < 5的用戶,並顯示關係

![](../img/2.png)

如上圖所示,要查詢的用戶的那個圈,被分紅了四個部分(上面講的四種狀態):

  • 關注了個人用戶

  • 和我互粉的用戶

  • 我關注了的用戶

  • 我未關注的用戶

以上覆雜的集合關係,經過單一SQL根本沒法實現。

要查詢的用戶與粉絲集合的交集:

SELECT * FROM (SELECT * FROM user WHERE id < 5) AS t1INNER JOIN(SELECT * FROM follower WHERE u1 = 1) AS t2ON t1.id = t2.u2

要查詢的用戶與關注集合的交集:

SELECT * FROM (SELECT * FROM user WHERE id < 5) AS t1INNER JOIN(SELECT * FROM follower WHERE u2 = 1) AS t3ON t1.id = t3.u1

其餘的部分能夠經過以上兩步查詢出來的數據,在內存中做計算得出。

由於關注關係是互相的,用一行紀錄便可表示。以上的設計實際上是把關注和粉絲的概念用一行紀錄表達。這樣會引來一個缺點,當follower很是大的時候,對follower表進行分片,若是按u1或者u2分片,假設按u1分片,那麼將致使關注列表,即下面的查詢要作聚合。

SELECT * FROM follower WHERE u2 = 1

選擇u1分片後,u2 = 1的數據行將會落到不一樣的分片上。

SELECT * FROM follower_0 WHERE u2 = 1UNION SELECT * FROM follower_1 WHERE u2 = 1

而粉絲列表的查詢不會受影響,同一個用戶的全部粉絲分在一個片上。

SELECT * FROM follower_1 WHERE u1 = 1

若是按u2分片,一樣也會致使粉絲列表會落在不一樣的分片上。兩個查詢不可能同時知足分片。

若是分片是跨數據庫或者是跨主機的方案,問題會變得更復雜。

針對方片的優化方案

能夠用冗餘數據的辦法來解決數據分片帶來的問題,即將關注和粉絲分2個表存放。
用follower表存放粉絲
用following表存放關注

當用戶Ub關注Ua,分別往follower, following寫入一行紀錄。 (Ua -> Ub) 只是他們表示的含義不一樣。

follower表示Ua的粉絲是Ub
following表示Ub關注Ua

分片的時候,同時對follower和following進行分片。同時上面分析的全部查詢方法也要相應改變,思路仍是同樣,只是單個表的自聯接變成2個表的聯接。

以上方案缺點就是數據量會增長一倍,進行關注或者取消關注的寫操做會多一次,要同時維護2個表的數據。

以上優化雖然解決了一些問題,但同時也帶來一些問題。可見關係型數據庫在處理用戶關係的時候,表現得很吃力。咱們不得不認可,雖然叫「關係」型數據庫卻不太懂得處理集合關係。

另外一種方案

還有一種方案,即用一行紀錄表示出兩個用戶之間的全部關係,此方案能節省很大的數據空佔用。

字段: u1, u2, type

type=1 表示u2關注u1
type=2 表示u1,u2互相關注
type=0 表示u1,u2無關係(默認)

保證插入數據時,u1是被關注者,u2是粉絲(固然你也能夠換過來,只是邏輯會變了)

每次寫入數據時要檢查當前的狀態:

若是u1(1) -> u2(2)紀錄已經存在(u2已經關注u1),這個時候u1再關注u2,只須要將type字段的值變爲type = 2。

若是u1(1) -> u2(2) type(2)時,即u1和u2互相關注,若是有一我的取消關注,問題會很複雜,最壞的狀況要修改整行紀錄,交換u1,u2這兩個字段的值,再修改type=1。

同時上面的方案查詢也會變化。例如要查詢id = 1的粉絲列表:

SELECT * FROM table WHERE u1 = 1 OR (u2 = 1 AND type = 2)

例如要查詢id = 1的關注列表:

SELECT * FROM table WHERE u2 = 1 OR (u1 = 1 AND type = 2)

上面的方案只強調關注關係,雙向關係只是在單一關係上用字段區分,關注的前後關係很明顯,事務性更強。

查詢id = 1的雙向關注

SELECT * FROM table WHERE type = 2 AND (u1 = 1 OR u2 = 1)

這個方案雖然節省數據空間,可是不容易理解,並且寫入時每次要檢查判斷當前的關係,邏輯上過於複雜。並且數據量大後,因爲查詢WHERE條件同時有u1和u2,很難進行分片。

其餘一些問題

  • ua與ub的共同關注列表

  • ua與ub的共同粉絲列表

  • ua的關注列表裏誰關注了ub

以上的關係計算你們可能很容易理解,但要在MySQL裏實現,是很是難的。

id = 3與id = 2的共同關注列表:

SELECT  u1, COUNT(id) AS num FROM follower WHERE u2 = 3 OR u2 = 2GROUP BY u1 HAVING num > 1

id = 3與id = 1的共同粉絲列表:

SELECT  u2, COUNT(id) AS num FROM follower WHERE u1 = 3 OR u1 = 1GROUP BY u2 HAVING num > 1

固然你能夠用集合的方法查詢:

SELECT t1.u2 FROM (SELECT  u2 FROM follower WHERE u1 = 3) AS t1 INNER JOIN(SELECT  u2 FROM follower WHERE u1 = 1) AS t2ON t1.u2 = t2.u2

id = 1的關注列表裏誰關注了id = 5


SELECT u2 FROM (SELECT u2 FROM follower WHERE u1 = 2) AS t1INNER JOIN (SELECT u1 FROM follower WHERE u2 = 1) AS t2ON t2.u1 = t1.u2
相關文章
相關標籤/搜索