两阶段提交遇到无法恢复也无法回滚的事务该怎么办? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
tabris17
V2EX    MongoDB

两阶段提交遇到无法恢复也无法回滚的事务该怎么办?

  •  
  •   tabris17 2016-02-15 10:17:49 +08:00 6154 次点击
    这是一个创建于 3592 天前的主题,其中的信息可能已经有所发展或是发生改变。
    以 A 账户向 B 相互转账为例。
    在账户余额有限制的情况下,比如余额必须大于 0 且小于 100 。
    当事务处理到 pedding 状态,完成了 A 账户的扣款,向 B 账户打款时,发现 B 帐户余额会超过 100 元,打款失败,然后回滚事务,将 A 账户之前的扣款还回时发现, A 账户余额已经发生变动,还款会导致余额超过 100 。
    如果采取先打款再扣款的方式,则会碰到余额不足的情况。

    碰到这种情况,该如何处理?事务挂起,然后手工干预?
    27 条回复    2016-02-16 12:41:15 +08:00
    zacard
        1
    zacard  
       2016-02-15 10:31:05 +08:00
    A 打款给 B 前就应该检查 B 是否超过上限,而不是先扣款
    tabris17
        2
    tabris17  
    OP
       2016-02-15 10:39:45 +08:00
    @zacard 数据无法同步,这种检查意义不大,你无法保证『检查 B 账户余额』和『给 B 账户打款』的两个操作之间, B 账户余额不发生变化
    wy315700
        3
    wy315700  
       2016-02-15 10:46:19 +08:00
    加锁
    pelloz
        4
    pelloz  
       2016-02-15 10:49:22 +08:00
    排它锁?
    noli
        5
    noli  
       2016-02-15 10:49:59 +08:00   1
    “ A 向 B 转账”(记为 T1 )的事务完成或者中止之前能够修改 A 帐户余额?
    我不确定 T1 还能不能叫事务……
    incompatible
        6
    incompatible  
       2016-02-15 10:51:53 +08:00 via iPhone
    @tabris17
    当然可以保证,给 A 账户和 B 账户加事务锁就可以。

    另外,多问一句:从你提到“两阶段提交”来推断, A 账户和 B 账户在同一个数据源下?所以用了分布式事务?
    EPr2hh6LADQWqRVH
        7
    EPr2hh6LADQWqRVH  
       2016-02-15 10:53:27 +08:00
    Mongodb 不支持事务这件事写在 Cons 里面可不是白写的
    请选用支持事务的数据库比如 postgresql , 或者自己实现软事务
    EPr2hh6LADQWqRVH
        8
    EPr2hh6LADQWqRVH  
       2016-02-15 10:56:21 +08:00
    tabris17
        9
    tabris17  
    OP
       2016-02-15 10:56:51 +08:00
    能想到的是,在账户的 document 上增加一个锁标志字段,在对 document 操作前都手动检查一下锁标志位
    tabris17
        10
    tabris17  
    OP
       2016-02-15 11:14:02 +08:00
    zacard
        11
    zacard  
       2016-02-15 11:24:20 +08:00
    @tabris17 所有对余额的操作都检查啊。。。可以借用 mongodb 的原子增减做检查
    tabris17
        12
    tabris17  
    OP
       2016-02-15 11:30:59 +08:00
    @zacard

    没用。

    A 向 B 转账事务操作:
    1. 检查 B 账户余额接受金额后是否会超出 100 的限额;
    2. 检查通过则对 A 账户进行扣款;
    3. 向 B 账户打款。

    然而 1 和 3 操作之间,可能有其他的操作(比如 B 账户充值)会对 B 账户余额进行操作,第一步的检查白做
    xiaolanglang
        13
    xiaolanglang  
       2016-02-15 11:33:41 +08:00
    @tabris17 排他锁,在转账事务完成 /超时之前,阻塞其他所有的针对两个账户余额变动的操作
    tabris17
        14
    tabris17  
    OP
       2016-02-15 11:37:44 +08:00
    @gy911201 阻塞的排他锁很麻烦,由于事务要分别获得 document A 和 document B 两个文档的锁,所以会造成死锁,还要自己实现死锁检测
    Mirana
        15
    Mirana  
       2016-02-15 11:38:59 +08:00
    A 预提交了之后是会被锁住的
    incompatible
        16
    incompatible  
       2016-02-15 11:50:13 +08:00 via iPhone
    @tabris17
    抱歉,一开始没有看到是在 mongodb 节点下。
    我的建议是: mongodb 完全不是为这种场景设计的,请考虑改用 mysql 或者 postgresql 来实现你的账户余额功能。这些支持 acid 的数据库很容易就可以支持你主贴中的业务场景,完全无需自己实现两阶段提交(事实上两阶段提交本来也不是用来干这个的,它通常用来实现多数据源的分布式事务)
    tabris17
        17
    tabris17  
    OP
       2016-02-15 11:56:05 +08:00
    @incompatible

    确实,这个称作模拟事务日志比较恰当。
    如果 mongo 要实现事务,除了模拟事务日志外,为了避免脏读,不可重复读,幻影行,还要实现 MVCC ,为了数据一致性,还要实现行(文档)锁、页锁、集合锁……
    north521
        18
    north521  
       2016-02-15 13:14:01 +08:00
    A->B 操作没完成之前, A 不能修改,否则不叫事务
    breeswish
        19
    breeswish  
       2016-02-15 13:44:46 +08:00
    就这个例子而言,可以在 pending 后进行检查,如果检查失败则回滚( MongoDB 文档 Rollback Operations 下的 Transactions in Pending State )。

    你担心检查完和修改记录之间有其他操作,这个和两阶段递交没关系,你想做的是确保一次只有一个事务占用资源。可以实现一个锁。对于这个问题而言,可以在查询中增加条件解决(显然能解决的问题不如锁那么多):

    db.accounts.update(
    { _id: t.destination, pendingTransactions: { $ne: t._id }, balance: { $lt: 100 - t.value} },
    { $inc: { balance: t.value }, $push: { pendingTransactions: t._id } }
    )

    注意增加了一个 balance: { $lt: 100 - t.value} 条件。 update 失败( 0 affected )直接 rollback 即可
    breeswish
        20
    breeswish  
       2016-02-15 13:46:11 +08:00
    (补:纯 MongoDB 的话可以利用 Tailable Cursor 实现锁
    hantsy
        21
    hantsy  
       2016-02-15 13:47:22 +08:00
    看成 Two phase transcation 了。。。
    zacard
        22
    zacard  
       2016-02-15 13:49:37 +08:00
    @tabris17 1 和 3 操作之间如果有对余额修改(充值),要么会检查出此次充值失败,要么就会检查出这次转账失败。原子性来保证这一点。
    tabris17
        23
    tabris17  
    OP
       2016-02-15 13:56:59 +08:00
    @zacard mongo 原子性之针对单个文档的操作,这里涉及到事务隔离
    palmers
        24
    palmers  
       2016-02-15 15:23:01 +08:00
    能不能使用第三方账户转账呢? 优先检查 B 账户余额 满足立即转账否则直接退回, 第三方账户肯定可以控制不会在转账期余额被操作 这样可以吗?
    tianice
        25
    tianice  
       2016-02-15 22:29:30 +08:00
    mongo 支持原子操作,并不支持事务。也不能叫两阶段提交,两阶段提交数据库在第一次预提交成功之后,必须保证事务最终能提交或回滚。不能说预提交成功之后,让你回滚你滚不回去,让你提交提交不了,不能出现这两种情况,否则预提交没有意义。
    tianice
        26
    tianice  
       2016-02-15 22:30:58 +08:00
    涉及到钱的业务还是用关系型数据库吧,至少要保证事务一致
    future0906
        27
    future0906  
       2016-02-16 12:41:15 +08:00
    @tabris17
    先冻结 A 100 块,这一百块不能交易,转出;转账给 B ,不过成功的话,直接扣除,不成功解除冻结。

    银行信用卡也是采用类似模型(预授权)
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     5197 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 34ms UTC 07:46 PVG 15:46 LAX 23:46 JFK 02:46
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86