电商的账户系统
账户系统负责记录和管理用户账户的余额,这个余额可能受到用户充值或者退款等途径的
对于一个账户系统,包含的数据模型基本如下
包含了用户ID,账户余额,更新时间就可以了
对于账户系统,需要和其他有着密切的关联,至少和财务,订单,交易等系统有关联,账户系统内的数据至少可以自洽,至少所有用户的余额加起来,可以等于这个电商公司账户的总余额,而且可以和财务系统的数据对得上,而且为了可以对上账,往往一个电商系统会有一个对账系统,来矫正不同系统间的数据差异
所以,对不上账是我们需要面对的问题,而对不上账本质上就是冗余数据的一致性,这个冗余的数据,是在不同系统中记录的相同信息的数据,这种记录浪费了存储空间,而且不易保证其的一致性
但是如果不进行不一致性的保存,那么在交易之前,通过所有历史交易记录算一下余额,会导致降低其速度,性能满足不了交易的需求,所以会在账户中保存其当前余额,用存储空间换时间的设计
而且,我们还会保存一个流水系统,保存其所有的流水账,这个流水账系统,需要我们保证其只能新增,不可以修改或者删除,即时一个交易需要取消,也是记录一笔取消交易的流水,而不是删除交易流水
对账时候,也是以流水为准修正余额数据,保证后续能对得上帐
对于冗余数据的更新
我们保证记录流水和更新余额两个操作,要么都成功,要么都失败,不能有任何一笔交易出现,记录了流水但是没有更新余额
这就是事务机制
对于事务机制的使用,本质上很简单,在mysql中按照如下操作就可以了
mysql> begin; — 开始事务
Query OK, 0 rows affected (0.00 sec) mysql> insert into account_log …; — 写入交易流水 Query OK, 1 rows affected (0.01 sec) mysql> update account_balance …; — 更新账户余额 Query OK, 1 rows affected (0.00 sec) mysql> commit; # 提交事务 Query OK, 0 rows affected (0.01 sec) |
简单来说,只需要在开始前标记一个begin,然后执行后提交commit就可以了
这就可以保证两个操作在一个事务中执行了,这就是事务的原子性
而且在事务执行的过程中,正常的查询不会查询到事务的中间状态,只有事务结束后可以看到事务的修改,这就是一致性
而且每个事务在执行的过程中,中间的状态对于其他事务是不可见的,在事务A中,写入操作时候,其他事务是读取不到的,这就是事务的隔离性
只要事务提交成功,就会被持久化到磁盘,这就是持久性
然后是事务的隔离级别
不同的事务隔离级别不同
四种隔离级别RU RC RR SE,越来越重
RU级别没完全不隔离,事务中间状态可见
序列化则是太强,所以也不用
对于RC和RR两种
MySQL默认RR,可以避免脏读
两者唯一的区别在于,可重复读
就是在一个事务的过程中,能不能读到其他的事务修改后的数据
如果能读到变化,就是不可重复读,否则就是可重复读
那么在账户余额表中,整体的流程如下
我们先给账户余额表增加一个log_id属性,记录最后一笔交易的流水号
然后开启事务,查询并记录当前账户的曰和最后一笔交易的流水号
然后写入流水记录
然后更新账户余额,在更新的时候,指定流水号
然后检测余额的返回值,更新成功就提交事务
对于流水表和余额表的建表语句
基本如下
CREATE TABLE `account_log` (
`log_id` int NOT NULL AUTO_INCREMENT COMMENT ‘流水号’, `amount` int NOT NULL COMMENT ‘交易金额’, `timestamp` datetime NOT NULL COMMENT ‘时间戳’, `from_system` int NOT NULL COMMENT ‘转出系统编码’, `from_system_transaction_number` int DEFAULT NULL COMMENT ‘转出系统的交易号’, `from_account` int DEFAULT NULL COMMENT ‘转出账户’, `to_system` int NOT NULL COMMENT ‘转入系统编码’, `to_system_transaction_number` int DEFAULT NULL COMMENT ‘转入系统的交易号’, `to_account` int DEFAULT NULL COMMENT ‘转入账户’, `transaction_type` int NOT NULL COMMENT ‘交易类型编码’, PRIMARY KEY (`log_id`) ); CREATE TABLE `account_balance` ( `user_id` int NOT NULL COMMENT ‘用户ID’, `balance` int NOT NULL COMMENT ‘余额’, `timestamp` datetime NOT NULL COMMENT ‘时间戳’, `log_id` int NOT NULL COMMENT ‘最后一笔交易的流水号’, PRIMARY KEY (`user_id`) ); |
账户系统可以记录每个用户的余额,但是还需要记录账户的流水,流水是只能增加,任何情况下都不能修改和删除,每次交易的时候都需要将流水和余额放在一个事务中更新
事务具有原子性 一致性 隔离性 持久性 四种特性,也就是ACID,可以保证一个事务中更新要么都成功,奥秘都失败