前阵子从支付宝转账10000元到余额宝,这是日常生活的一件普通小事,但作为互联网研发人员的职业病,我就思考支付宝扣除1万之后,如果系统挂掉怎么办,这时余额宝账户并没有增加10000,数据就会出现不一致状况了。这样的场景在各个类型的系统中都能找到相似的影子,比如在电商系统中,当有用户下单后,除了在订单表插入一条记录外,对应商品表的这个商品数量也必须减1;在搜索广告系统中,当用户点击某广告后,除了在点击事件表中增加一条记录外,还得去商家账户表中找到这个商家并扣除广告费等等,相信大家或多或多少都能碰到相似情景。这些问题本质上都可以抽象为当一个表数据更新后,怎么保证另一个表的数据也必须要更新成功的问题,也就是事务。

本地事务

事务是为了保证同一个事务中的操作同时成功或同时失败的一种机制。还是以支付宝转账余额宝为例,假设有支付宝账户表:A(id,userId,amount),余额宝账户表:B(id,userId,amount),用户的userId是1,那么可以将从支付宝转账1万块钱到余额宝的动作分为以下两步:

1)支付宝表扣除1万。

复制代码
update A set amount = amount - 10000 where userId = 1;
复制代码

2)余额宝表增加1万。

复制代码
update B set amount = amount + 10000 where userId = 1;
复制代码

如何确保支付宝余额宝收支平衡呢?有人说这个很简单嘛,可以用事务解决。

复制代码
Begin transaction    update A set amount = amount - 10000 where userId = 1;     update B set amount = amount + 10000 where userId = 1; End transactioncommit;
复制代码

非常正确!如果你使用Spring的话一个@Transaction注解就能搞定上述事务功能。

复制代码
@Transactional(rollbackFor=Exception.class) public void update() {     updateATable(); // 更新A表    updateBTable(); // 更新B表}
复制代码

如果系统规模较小,数据表都在一个数据库实例上,上述本地事务方式可以很好地运行,但是如果系统规模较大,比如在上面的场景中,支付宝账户表和余额宝账户表显然不会在同一个数据库实例上,他们往往分布在不同的物理节点上,这时本地事务已经失去用武之地。

既然本地事务失效,分布式事务自然就登上舞台。

分布式事务—两阶段提交协议