写相互加好友时碰到的问题。
数据库好友关系( friend_relation )表目前是这样的:
字段: id, account_id, friend_account_id, status
说明:这条好友关系 id, 用户 id, 朋友 id, 状态标记是否被删除
于是 2 个用户相互添加为好友就会出现对应的 2 条好友关系 friend_relation (这两条好友关系的 account_id 与 friend_account_id 对调)
那么现在问题来了,如果 2 个用户同时接受对方发起的交友请求,会出现对应的 2 个处理交友的进程都认为双方都没加过好友,于是最后 2 个进程共同插入了 4 条好友关系,而实际上应该插入 2 条就够了。
加锁好像不行,因为在处理之前连被加锁的数据都没有,根本不能对数据行加锁。
加唯一 index,应该怎么加,业务上要允许重复多次加删同一个人的好友关系,如果对( account_id, friend_account_id, status )作为一个唯一 index,那么只能删一次这个好友,这样也不行。
我应该怎么解决这个问题?
========== 我现在只能先把插入好友关系这一步工作都放到一个队列里去干
1
sunchen 2018-02-02 10:06:06 +08:00
unique (用户 id,朋友 id )
|
2
Soar360 2018-02-02 10:10:14 +08:00
Event Bus 或者说,队列也行。用数据库的唯一索引保证唯一性。话说,好多软件的好友删除都是单方向的啊。
|
3
Clarencep 2018-02-02 10:27:44 +08:00
提供另外一条思路,MySQL 默认的数据库隔离级别是 REPEATABLE-READ:
``` mysql> select @@global.tx_isolation; @@global.tx_isolation ----------------------- REPEATABLE-READ ``` 所以即使 LZ 用了事务,也会出现幻读 -- 虽然已经有另外一个事务 B 加了一次好友关系了,但是并发的事务 A 依然认为没有加好友关系。 而 SERIALIZABLE 级别的隔离可以避免幻读 -- 即可以在开启事务前修改下隔离级别: ``` SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; ``` |
4
jason2017 2018-02-02 10:33:33 +08:00
account_id,friend_account_id 做唯一索引,幂等操作。
添加或删除好友前先查询数据库,再做 insert 或 update 操作。 |
5
nullcc 2018-02-02 10:37:30 +08:00
@Clarencep 改隔离级别理论上是 OK,但是 SERIALIZABLE 这个级别并发性太低了,如果不是有非常严格的顺序要求建议生产环境还是不要用
|
6
Clarencep 2018-02-02 10:45:43 +08:00
@nullcc 全局都设置 SERIALIZABLE 肯定不合理,不过数据库隔离级别是可以修改的,而且是可以只改单个会话( session )的,添加好友前改成 SERIALIZABLE,添加后再改回去就行了。
SERIALIZABLE 级别其实就相当于在 MySQL 服务器上搞了个队列。如果 LZ 的应用服务器和 MySQL 是在同一台物理机上,完全没必要自己搞队列,直接用 SERIALIZABLE 级别就行了。当然,如果应用服务器和 MySQL 分开的,或者 MySQL 的资源比较紧张,那肯定最好应用服务器上的队列更佳。 |
7
zakokun 2018-02-02 10:48:51 +08:00 1
没这么麻烦把 account_id friend_id 加一个联合唯一索引不就行了? 插入的时候就是这样
insert into test set account_id =1, friend_account_id =2,state=1 on duplicate key update state=1 insert into test set account_id =2, friend_account_id =1,state=1 on duplicate key update state=1 如果数据库没记录就插入,有记录就把 state 变成 1. 这有问题吗 |
8
Immortal 2018-02-02 10:50:59 +08:00
赞同楼上
|
10
soli 2018-02-02 10:53:57 +08:00
1 楼的方法不是最简单的么?
|
11
Immortal 2018-02-02 10:56:25 +08:00
|
12
winglight2016 2018-02-02 10:58:35 +08:00
你们的数据库操作不使用 transaction 的吗?
|
13
MiguelValentine 2018-02-02 10:59:28 +08:00
难道不是互相添加才是好友?单方面只算关注? PM 有问题啊
|
14
zakokun 2018-02-02 11:02:58 +08:00
|
15
SmiteChow 2018-02-02 11:15:40 +08:00
如果有 ORM 则解决方式为 get_or_create
|
20
e9e499d78f 2018-02-02 13:05:28 +08:00 via iPhone
把好友关系设计成双向的不就行了
|
21
sutra 2018-02-02 13:12:28 +08:00 1
似乎就我没有看懂问题?
为什么会出现 4 条记录?最多就 2 条记录呀。 |
22
vjnjc 2018-02-02 13:44:09 +08:00 1
|
23
sutra 2018-02-02 13:57:47 +08:00
|
24
barbery 2018-02-02 14:07:56 +08:00
如果要从 db 上处理,unique 的方式是比较好的。如果不从 db 上处理,可以考虑引入第三方的原子性操作的服务,例如 redis 或者队列等。
|
26
Eternallyc 2018-02-02 15:06:33 +08:00
比如 a 发起添加 b 为好友请求,b 也发起添加 a 请求。
a 点接受好友(插入 2 条记录好友记录),b 也点接受好友(插入 2 条记录)。 所以在接受好友的业务里先判断再添加啊! 赞同! 插入的时候判断下 根据用户 id 去关系表里查询 有记录并且 status 字段不是被删除状态 才插入,否则提示 已经是好友 |
27
Clarencep 2018-02-02 15:37:57 +08:00
@eslizn 可以只改单个会话( session )的隔离级别,添加好友前改成 SERIALIZABLE,添加后再改回去就行了。
|
28
chuanwu 2018-02-02 16:40:03 +08:00
额,难道就我一个人觉得表设计不合理么...
|
30
vincenttone 2018-02-02 17:18:58 +08:00
唯一索引 配合上 insert into on duplicate update 即可
|
31
picasso250 2018-02-02 21:01:25 +08:00
有趣的问题.
难点之所以会出现, 是因为 每个人的 request 会新增两条数据(主动去踩不该踩的坑). 解决方案: request 的时候,只会增加一条数据. 另一条数据扔给后台的队列(这个队列本身是单线程的)去做. 完美解决. |
32
qile1 2018-02-03 06:40:50 +08:00 via Android
@vjnjc 你这个直接 a 加 b 为好友,插一条信息,此时 b 是 a 好友,但 a 不是 b 好友,或者可以理解为 b 不把 a 当好友,当 b 在添加 a 为好友时,插入一条数据,这样就不会产生冲突。
|
33
twotiger 2018-02-03 16:40:04 +08:00
@Eternallyc 没用的,并发的话,会同时写进去
|