两个线程一起从表中取数据,表和 sql 如何设计呢?

141 天前
 qviqvi

一个任务表,两个线程同时每次取一条数据执行业务逻辑,然后把这条数据标记为完成或暂时放弃,暂时放弃的后面会被再次取出

要求同一条数据不能被多个线程同时取出

求优秀的设计方案

2027 次点击
所在节点    数据库
32 条回复
qviqvi
141 天前
@yjhatfdu2 感觉这个是正解
rqxiao
140 天前
@yjhatfdu2 第一次知道
csrocks
140 天前
number of task = N

task_0: select * from t where mod(id_hash, N)=0
...
task_N: select * from t where mod(id_hash, N)=N-1
nothingistrue
140 天前
@Vegetable #20 看好取数的查询条件,不是 select * from t by id ,而是 select * from t where stauts= 未处理(以及未锁定) 中的第一条。两个线程之间的共享对象,不是一行数据,而是所有未处理数据,你要加锁或者占坑,也只能这么占。加锁之后的结果就是,多个线程只能轮流处理,跟单线程一个效果,无法并行。
Vegetable
140 天前
@nothingistrue 我没看明白你说的是什么,锁只是确保状态只会被一个线程修改,和 MoYi123 说的判断影响行数的方案本质是一样的,锁的持续时间也就几毫秒而已,哪里和单线程一个效果
nothingistrue
140 天前
@yjhatfdu2 #18
https://stackoverflow.com/questions/53288584/select-for-update-skip-locked-in-repetable-read-transactions
看下 skip locked 的效果,可以重复加锁,但是事务提交的时候要判定数据有没有被更改过,如果已经更改,那么本事务要失败。这就是个乐观锁,先提交的成功,后提交的失败,使用场景是并发修改的机率不高的场景。如果你在多线程并发场景中用乐观锁,那没跑几步就会只剩一个线程活着,其他线程全部出错终止(加了出错之后重启机制,效果会更差,绝大部分性能将被浪费在失败重启上)。

事务不是一句「开事务」就完事大吉的。什么时候开事务,开什么样的事务,都是有考究的。最常见的错误,就是认为开了事务就不怕并发数据冲突了。事务的隔离性是分级别的,序列化级别才能保证完全不出现并发数据冲突——但同时也没并发了。常用的隔离级别是可重复读,只在并发数据是确定的单行/多行数据的时候才能保证无并发冲突,当并发数据是表,或者表中不确定的数据时,还是要加锁处理并发冲突的。

而怎么加锁,也是有考究的。楼主的场景是没法加锁的,因为它的并发数据是「 stauts = 未处理」的表,不是「主键 = xxx 」的行。加常规悲观锁就让线程排队,等同于失去并发。加乐观锁,就是开玩笑。

@qviqvi #21 不要只找简单答案,容易错。Karte 那三个方案才是正确的。
nothingistrue
140 天前
@Vegetable #25
update table set status = 'work' where id = 1 and stats = 'ready'
id = 1 怎么来的:是 select id from table where stauts= 未处理 limit 1 得到的
那要是 select 语句跟 update 语句之间,其他线程已经提前做了 update 呢:这个 update 会返回 0 ,本线程处理失败
接着呢:如果你当失败处理,那么这个线程没了;如果你重新 select ,那么大部分时间要消耗在 select -> update return 0 -> select 的死循环中了。

我对 @MoYi123 第一个回复确实是错了,不是表锁,而是乐观锁。但乐观锁不解决问题,详见我上面对 yjhatfdu2 的回复。

楼主这个场景要加锁,你只能对 「 where stauts= 未处理 」,或者「全表」加锁,加了之后多线程从并发变排队,形同单线程。
glacer
140 天前
id mod 线程数
MoYi123
140 天前
@nothingistrue
id=1 怎么来的? 当然是 select * from t where stats = 'ready' limit 10 查出来的啊,
每个 worker 先拉一个 todo list, 然后一个个去领任务, 成功领到了就开始干, 这有啥问题啊,

难道你用消息队列就能凭空变出来这个 id 了? 不也是这样查的
linyinma
140 天前
明明在应用层可以处理的,偏偏要扯到数据库,: 一个线程批量取,多个线程消费, 中间加个锁消费完了再去取.....
nothingistrue
140 天前
@MoYi123 #29 消息队列读写分离了,可以单独只对读那一瞬间加锁。10 楼说得明明白白。
MoYi123
140 天前
@nothingistrue 回答一个问题, 往队列里塞数据的进程是不是单点? 或者你要怎么加锁?

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

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

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

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

© 2021 V2EX