不懂就问系列, web + 并发 + 锁 问题,具体情况在正文

2019-06-03 10:08:11 +08:00
 chaleaochexist
不知道这样的问题,如何归纳成主题.

数据库写操作.
其中一个字段`version`需要+1 操作.
在高并发情况下,如何确保 version 唯一.

table 大概是这样的

| version | object_name | operation |
| :-----: | :---------: | :-------: |
| 1 | 小明 | 吃饭 |
| 2 | 小明 | 吃饭 |
| 1 | 小红 | 吃饭 |
| 1 | 小红 | 睡觉 |

version 只在 object_name 和 operation 相同的情况下+1.
1. 自增不行.
2. 可以加一个唯一约束.
3. 有没有在后端的代码里面加锁控制? 求最佳实践和通用解决方案.
3292 次点击
所在节点    程序员
30 条回复
chaleaochexist
2019-06-03 10:09:05 +08:00
v2markdown 不支持表格吗...
Maboroshii
2019-06-03 10:12:42 +08:00
redis ?
zhengxiaowai
2019-06-03 10:15:39 +08:00
行锁了解一下
chaleaochexist
2019-06-03 10:19:51 +08:00
@zhengxiaowai +1 操作是在代码里执行. 行锁如何控制? 没想明白.
liuzhedash
2019-06-03 10:21:33 +08:00
2、唯一约束不能直接解决问题:当发现重复记录的时候,如何处理?
3、理论上可以,但是假设未来需要多台服务器进行负载均衡,这个方法就失效了。
3L 的行锁应该可以,但是我没有试过。
之前我遇到类似的问题是通过把请求先缓存到 redis 队列中,然后逐个插入数据库解决的。
NaVient
2019-06-03 10:22:18 +08:00
redis 实现分布式锁不行吗 以{table_name}_{id}的方式做键值
NewDraw
2019-06-03 10:24:23 +08:00
没看明白你说的啥意思。
是说,在现有表的基础上再插入一条 小明,吃饭,该记录 version 的值要是 3 嘛?

如果是上面的问题,那么就是问题就可以转化为如何解决幻读,四种事务隔离级别可以了解一下,这种情况只能用表锁了。
chaleaochexist
2019-06-03 10:25:20 +08:00
@liuzhedash
谢谢.
2. 唯一约束可以解决问题, 代码里面捕获异常,然后+2,+3...
3. 您说的对.谢谢.
4. 行锁您能从理论上解释一下吗?我没反应过来,虽然我知道行锁是啥.也许对行锁了解不扎实.给点提示?
5. redis 可行. 我在想想.
airfling
2019-06-03 10:25:28 +08:00
高并发应该是读的高并发吧,那就是加个写的锁,当写入完毕释放锁
guiling
2019-06-03 10:26:30 +08:00
这不是乐观锁么?
update b set version=version+1 where version=?
更新之前先查一下版本,需要其他限制加在 where 后面
chaleaochexist
2019-06-03 10:26:33 +08:00
@NewDraw
对两个线程同时对数据库 小明 吃饭 +1 写了两条 version=3 的数据,这是不可以被接受的.
NewDraw
2019-06-03 10:28:50 +08:00
额,是我说的意思啊,两个线程并发写入,就会有幻读的情况,可以让数据库隔离级别设为最高级。
chaleaochexist
2019-06-03 10:29:21 +08:00
@airfling 考虑这种情况
两个线程 已经读到 version = 2
各自+1...

写锁的约束会约束到代码吗?
这块不懂.
请多指教.
chaleaochexist
2019-06-03 10:30:22 +08:00
@NewDraw 我刚才脑袋抽了.我在想想,好像你说的对.
mooncakejs
2019-06-03 10:30:36 +08:00
太追求数据库层面解决不是一个好的实践,锁缓存是个好主意,#6 楼说的分布式锁。
liukanshan
2019-06-03 10:35:12 +08:00
每一次更新操作之前 检查下版本号

def fun(){
def version = select version from xxx;
//do something


update xxx set field=value,version = version + 1 where version = $version;
}
night98
2019-06-03 10:38:57 +08:00
新增一个唯一主键,按唯一主键 + version 确定单条记录。
然后执行楼上说的:
update b set version=version+1 where version=?
再根据返回的修改条数的数据判断是否修改成功。
或者就是你这种,联合主键 + version 确定单条记录,更新执行完毕后根据返回的更新条数判断是否更新成功。
fantastM
2019-06-03 10:52:22 +08:00
不知道你的程序逻辑里,具体需要执行哪些 SQL......粗略看下来,似乎是「幻读」的问题。你可以对着 MySQL 的文档看看,如果是「幻读」的话,那可以用 MySQL 的隔离级别来解决。当然也可以用分布式锁解决

参考资料:
https://dev.mysql.com/doc/refman/5.7/en/innodb-next-key-locking.html
https://dev.mysql.com/doc/refman/5.7/en/innodb-transaction-isolation-levels.html
https://www.cnblogs.com/zhoujinyi/p/3437475.html
sleepiforest
2019-06-03 10:54:00 +08:00
需求是插入的时候,object_name 和 operation 相同的情况下,在原有最大的基础上+1,再插一条么……为啥要干这种事情。
mysql 的话直接用单机事务吧……
csys
2019-06-03 11:19:02 +08:00
低并发非分布式: 内存锁
低并发分布式: 数据库行锁 /乐观锁 /分布式锁
高并发非分布式: eventsourcing(queue+batch commit)
高并发分布式: actor+eventsourcing

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/570204

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX