微博相互关注互粉mysql表实现
关注关系产⽣的四种关系状态
关注
粉丝
双向关注(互粉)
⽆关系
⽤词follower表⽰粉丝 -- 追随者
⽤词following表⽰关注 -- 追随
设计的结构必须能满⾜以下功能:
查询粉丝列表
查询双向关注列表
判断两个⽤户的关系
查询带关系状态的任⼀列表
第⼀种⽅案
⽤⼀⾏纪录表⽰关注和粉丝,字段u2的值表⽰粉丝,u1表⽰被关注者。
Table: user(⽤户表)
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 (SELECT * FROM follower WHERE `u1` = 1) AS t2 ON t1.u1 = t2.u 2;
刘奕君一家三口照片判断两个⽤户的关系(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的⽤户,并显⽰关系
如上图所⽰,要查询的⽤户的那个圈,被分成了四个部分(上⾯讲的四种状态):
关注了我的⽤户
如何加盟味千拉面和我互粉的⽤户
我关注了的⽤户
我未关注的⽤户
以上复杂的集合关系,通过单⼀SQL根本⽆法实现。
要查询的⽤户与粉丝集合的交集:
SELECT * FROM
(SELECT * FROM user WHERE id < 5) AS t1
runningman千正明INNER JOIN
(SELECT * FROM follower WHERE u1 = 1) AS t2
ON t1.id = t2.u2
要查询的⽤户与关注集合的交集:
SELECT * FROM
(SELECT * FROM user WHERE id < 5) AS t1
INNER JOIN
(SELECT * FROM follower WHERE u2 = 1) AS t3
ON t1.id = t3.u1
其他的部分可以通过以上两步查询出来的数据,在内存中作计算得出。
因为关注关系是互相的,⽤⼀⾏纪录即可表⽰。以上的设计其实是把关注和粉丝的概念⽤⼀⾏纪录表达。这样会引来⼀个缺点,当follower ⾮常⼤的时候,对follower表进⾏分⽚,如果按u1或者u2分⽚,假设按u1分⽚,那么将导致关注列表,即下⾯的查询要做聚合。
SELECT * FROM follower WHERE u2 = 1
选择u1分⽚后,u2 = 1的数据⾏将会落到不同的分⽚上。
SELECT * FROM follower_0 WHERE u2 = 1
UNION
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 = 2 GROUP BY u1 HAVING num > 1
id = 3与id = 1的共同粉丝列表:
SELECT u2, COUNT(id) AS num FROM follower WHERE u1 = 3 OR u1 = 1 GROUP 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 t2
ON t1.u2 = t2.u2
id = 1的关注列表⾥谁关注了id = 5
SELECT u2 FROM (SELECT u2 FROM follower WHERE u1 = 2) AS t1 INNER JOIN
(SELECT u1 FROM follower WHERE u2 = 1) AS t2
ON t2.u1 = t1.u2
发布评论