V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
beego
xpyusrs
V2EX  ›  Go 编程语言

高并发情况下如何保证金额加减的一致性

  •  
  •   xpyusrs · 202 天前 · 3681 次点击
    这是一个创建于 202 天前的主题,其中的信息可能已经有所发展或是发生改变。
    因为要做流水, 所以得先查询余额再做加减
    语言 golang, 数据库框架 GORM
    22 条回复    2022-01-16 07:44:02 +08:00
    thinkershare
        1
    thinkershare  
       202 天前
    开启事务, 按照需求选择乐观或者悲观并发策略. 或者考虑 EventSourcing
    Yoock
        2
    Yoock  
       202 天前
    并发量是多大?
    niubee1
        3
    niubee1  
       202 天前
    那要看你是在单节点实现高并发还是多节点实现高并发,如果都交给数据库那就开事务,用数据库去解决一致性问题,但是性能不容乐观,如果要多节点实现高并发,那么分布式事务的 CAP 问题,要么用现成的分布式事务方案,要么自己实现 2 阶段提交( 2PC )或者 3 阶段提交( 3PC ),或者更近一步实现 Paxos 算法。这个问题很多现成方案,真没有必要自己再想当然的搞一套了,因为 CAP 三个问题你只能搞定其中两个,别自出机杼了
    xpyusrs
        4
    xpyusrs  
    OP
       202 天前
    @Yoock 单机, 只是考虑到这种情况, 怕偶尔出现一次数据加减不成功, 因为我在本机模拟了二个协程, 数据库却只加了一次
    xpyusrs
        5
    xpyusrs  
    OP
       202 天前
    @niubee1 单节点的, 简单一点最好, 实在麻烦的话我先用 go 的互斥锁用着, 发现这个也能满足需求, 就是性能差了点
    niubee1
        6
    niubee1  
       202 天前
    @xpyusrs 单节点的你要靠谱还不如直接用数据库事务好了,你用 go 的互斥锁那是硬生生的把法拉利开成了拖拉机
    ClarkAbe
        7
    ClarkAbe  
       202 天前 via Android   ❤️ 1
    我没用事务也是直接 Golang 互斥锁,因为我们业务关系所以一直都是 3k 人左右具体看学校新生多不多.....然后我没人分配了一把锁....好处是他不用二次刷卡,不用返回错误,就相当于加个高级点的行锁.....而且基本也就等个 100-200ms 就好了,内存之前单独写了个 main 测试过好像整体占用就 17M 左右...坏处是没有骚操作,在其他人面前没得吹......但是稳就行....另可慢几百毫秒也不可错一分
    dayeye2006199
        8
    dayeye2006199  
       202 天前
    最简单就是交给数据库去处理事务

    数据库事务设计出来就是处理这样的业务场景的,为啥大家第一反应想不到用它呢?
    ericgui
        9
    ericgui  
       202 天前
    @ClarkAbe 嗯不错,不要 over engineering ,不要过早优化,够用就行,关键要稳
    Chad0000
        10
    Chad0000  
       202 天前 via iPhone   ❤️ 6
    加上版本号,读出来后带上金额一起更新。如果 miss 就是有新版本,再次读。那还可以把这个版本号写入日志。

    update account set money += 5, version = 2 where id=1 and version = 1
    xuanbg
        11
    xuanbg  
       202 天前
    如果在代码逻辑中先读出余额再加减,那么数据库事务的隔离级别就很关键,而且可能需要分布式锁。如果直接在 sql 更新语句中加减,那就不需要事务。
    securityCoding
        12
    securityCoding  
       202 天前 via Android
    有 redis 集群吗? 用 lua 来做,数据库异步落地
    dooonabe
        13
    dooonabe  
       202 天前
    update table set price = price - #{x} where price >= #{x}
    chenzheyu
        14
    chenzheyu  
       202 天前
    对待钱的问题,宁肯慢也要保证不错
    timethinker
        15
    timethinker  
       202 天前
    悲观锁:

    在一个事务内,查询余额使用 SELECT .. FOR UPDATE 获取锁,这样其他事务既无法读取,也无法写入,但是要注意死锁的情况,顺序编排要一致。

    乐观锁:

    在一个事务内,更新余额使用 UPDATE account SET ..., version = @version + 1 WHERE id = @id AND version = @version ,这样更新失败的话返回的影响行数为 0 ,可以凭此判断是否成功。
    justRua
        16
    justRua  
       202 天前
    版本号实现乐观锁,自旋一定次数(比如 10 次),超过十次未更新成功就是失败
    BigMountain
        17
    BigMountain  
       202 天前
    涉及金额的强一致不应该用 Redis 来保证的
    Joker123456789
        18
    Joker123456789  
       202 天前
    并发问题就是加锁,没别的
    back0893
        19
    back0893  
       202 天前
    加锁..最简单
    pengtdyd
        20
    pengtdyd  
       202 天前
    分布式锁
    MoYi123
        21
    MoYi123  
       201 天前
    update money_table set money = money + 100 where id = 1 returning money - 100;
    用 PostgreSQL 的 returning,可以一句 sql 完成查询和修改

    Mysql 也有类似功能,不过比较麻烦.
    akriafly01
        22
    akriafly01  
       169 天前
    乐观锁 ➕ 自旋重试
    关于   ·   帮助文档   ·   API   ·   FAQ   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   4200 人在线   最高记录 5497   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 34ms · UTC 09:25 · PVG 17:25 · LAX 02:25 · JFK 05:25
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.