问题描述
在观看黑马的苍穹外卖系列教程中的用户取消订单功能代码开发时,我们注意到作者首先通过查询获取了被用户取消的订单的所有属性,并存储在ordersDB变量中
然而,在修改数据库时,作者并未直接对ordersDB变量进行操作,而是创建了一个新的Orders类型的orders变量,仅包含需要修改的属性,然后进行了update操作
详细代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
| @Data @Builder @NoArgsConstructor @AllArgsConstructor public class Orders implements Serializable {
public static final Integer PENDING_PAYMENT = 1; public static final Integer TO_BE_CONFIRMED = 2; public static final Integer CONFIRMED = 3; public static final Integer DELIVERY_IN_PROGRESS = 4; public static final Integer COMPLETED = 5; public static final Integer CANCELLED = 6;
public static final Integer UN_PAID = 0; public static final Integer PAID = 1; public static final Integer REFUND = 2;
private static final long serialVersionUID = 1L;
private Long id;
private String number;
private Integer status;
private Long userId;
private Long addressBookId;
private LocalDateTime orderTime;
private LocalDateTime checkoutTime;
private Integer payMethod;
private Integer payStatus;
private BigDecimal amount;
private String remark;
private String userName;
private String phone;
private String address;
private String consignee;
private String cancelReason;
private String rejectionReason;
private LocalDateTime cancelTime;
private LocalDateTime estimatedDeliveryTime;
private Integer deliveryStatus;
private LocalDateTime deliveryTime;
private int packAmount;
private int tablewareNumber;
private Integer tablewareStatus; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| public void userCancelById(Long id) throws Exception { Orders ordersDB = orderMapper.getById(id);
if (ordersDB == null) { throw new OrderBusinessException(MessageConstant.ORDER_NOT_FOUND); }
if (ordersDB.getStatus() > 2) { throw new OrderBusinessException(MessageConstant.ORDER_STATUS_ERROR); }
Orders orders = new Orders(); orders.setId(ordersDB.getId());
if (ordersDB.getStatus().equals(Orders.TO_BE_CONFIRMED)) { weChatPayUtil.refund( ordersDB.getNumber(), ordersDB.getNumber(), new BigDecimal(0.01), new BigDecimal(0.01));
orders.setPayStatus(Orders.REFUND); }
orders.setStatus(Orders.CANCELLED); orders.setCancelReason("用户取消"); orders.setCancelTime(LocalDateTime.now()); orderMapper.update(orders); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| <update id="update" parameterType="com.sky.entity.Orders"> update orders <set> <if test="cancelReason != null and cancelReason!='' "> cancel_reason=#{cancelReason}, </if> <if test="rejectionReason != null and rejectionReason!='' "> rejection_reason=#{rejectionReason}, </if> <if test="cancelTime != null"> cancel_time=#{cancelTime}, </if> <if test="payStatus != null"> pay_status=#{payStatus}, </if> <if test="payMethod != null"> pay_method=#{payMethod}, </if> <if test="checkoutTime != null"> checkout_time=#{checkoutTime}, </if> <if test="status != null"> status = #{status}, </if> <if test="deliveryTime != null"> delivery_time = #{deliveryTime} </if> </set> where id = #{id} </update>
|
原因分析
在Orders类中,属性众多,而update方法作为一个通用的数据库更新操作,可能涉及多个属性的修改
该方法在更新时,会根据传入的Orders对象中的属性值进行条件判断,若属性值非空则进行更新(有新数据就更新成最新数据,没有新数据就保持原样)
然而,在用户取消订单的场景中,实际需要修改的属性仅有三个:订单状态、取消原因和取消时间
所以有必要进行这么多次的判断和修改吗?
答案当然是没有
如果直接对ordersDB变量执行update操作,可能会引发以下两种问题
并发操作下的更新覆盖问题
在并发环境下,多个用户可能同时对同一组数据进行操作
我们用一个例子来说明这个问题
用户A正在执行id修改update操作,希望将id由1修改为2
用户B执行了用户ID换绑操作,希望将userId由LN修改为LTC
由于数据库操作的原子性,这两个操作在数据库层面是串行执行的
然而,如果直接对ordersDB变量进行update,那么即使后一个操作(用户ID换绑)先完成,其更改也可能被前一个操作(更改id)中的update语句所覆盖
1 2 3 4 5 6 7 8
| OrderDB: id: 1 number:3 status:1->2 #修改状态为2 userId: LN addressBookId: 3 orderTime:2024-11-15 13:05:06 checkTime:2024-11-15 13:05:06
|
1 2 3 4 5 6 7 8
| OrderDB: id: 1 number:3 status: 1 userId: LN ->LTC #修改userId addressBookId:3 orderTime:2024-11-15 13:05:07 checkTime:2024-11-15 13:05:07
|
1 2 3 4 5 6 7 8
| OrderDB: id: 1 number: 3 status: 1 userId: LTC addressBookId:3 orderTime:2024-11-15 13:05:08 checkTime:2024-11-15 13:05:08
|
1 2 3 4 5 6 7 8
| OrderDB: id: 1 number:3 status:2 userId: LN #重新以1的userld覆盖了刚才的更改 addressBookId:3 orderTime:2024-11-15 13:05:06 checkTime:2024-11-15 13:05:06
|
为了避免这种并发操作下的更新覆盖问题,作者选择创建一个新的Orders对象orders,仅包含需要修改的属性,并对其进行update操作。这样,即使存在并发操作,也只会影响需要修改的属性,而不会覆盖其他属性的更改。
增加的 Binlog 日志数量及性能影响
当选择直接修改现有的 ordersDB 数据库中的表结构或数据记录时
每次修改(无论是添加、删除还是更新数据)都会被记录在 MySQL 的二进制日志(binlog)中
这些日志对于数据恢复、复制和审计至关重要,然而,如果频繁地对大型表进行结构修改或大量数据更新,会导致 binlog 日志量显著增加
总结
在进行数据库更新操作时,通过创建新的对象并仅修改需要更新的属性,可以有效地避免并发操作下的更新覆盖问题,并减少不必要的binlog日志生成,从而优化数据库性能和稳定性