目錄[-]數據庫
關注事務
粉絲內存
雙向關注(互粉)
無關係
用詞follower表示粉絲 – 追隨者
用詞following表示關注 – 追隨
查詢關注列表
查詢粉絲列表
查詢雙向關注列表
判斷兩個用戶的關係
查詢帶關係狀態的任一列表
用一行紀錄表示關注和粉絲,字段u2的值表示粉絲,u1表示被關注者。
Table: follower(u2表示粉絲, u1表示被關注的人)
查詢用戶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