回答思路:
是指用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。
第一种:插入之前先查询,在保存数据的接口中,我们防止产生重复的数据,一般会在 insert 前先根据 name 或 code 字段 select 一下数据,如果该数据已存在,则执行 update 操作,如果不存在,才执行 insert 操作
缺点:该方案可能是我们平时在防止产生重复数据时,使用最多的方案。但是该方案不适用于并发场景。因为你查询的时候没有,准备插入,但是此时别的线程正好往里面插入就会有问题
第二种:数据库加悲观锁,比如在支付场景中,用户 A 的账号余额有 150 元,想转出 100 元,正常情况下用户 A 的余额只剩 50 元。为了解决这个问题,可以加悲观锁,通过id将用户 A 的那行数据锁住,在同一时刻只允许一个请求获得锁,更新数据,其他的请求则等待
缺点:需要特别注意的是:如果使用的是 MySQL 数据库,存储引擎必须用 innodb。因为它才支持事务。此外,这里 id 字段一定要是主键或者唯一索引,不然会锁住整张表。
第三种:数据库加乐观锁,既然悲观锁有性能问题,为了提升接口性能,我们可以使用乐观锁。需要在表中增加一个 timestamp 或者 version 字段,这里以 version 字段为例
在更新数据之前先查询一下数据:select id, amount, version from user id=123;
如果数据存在,假设查到的 version 等于 1,再使用 id 和 version 字段作为查询条件更新数据:update user set amount=amount+100, version=version+1 where id=123 and version=1;
第四种:加唯一索引,绝大数情况下,为了防止重复数据的产生,我们都会在表中加唯一索引,这是一个非常简单,并且有效的方案。加了唯一索引之后,第一次请求数据可以插入成功。但后面的相同请求,插入数据时会报 Duplicate entry ‘002’ for key 'order.un_code 异常,表示唯一索引有冲突。
第五种:加防重表,有时候表中并非所有的场景都不允许产生重复的数据,只有某些特定场景才不允许。这时候,直接在表中加唯一索引,显然是不太合适的。该表可以只包含两个字段:id 和 唯一索引。唯一索引可以是多个字段比如:name、code 等组合起来的唯一标识,例如:susan_0001。
第六种:根据状态机,很多时候业务表是有状态的,比如订单表中有:1-下单、2-已支付、3-完成、4-撤销等状态。如果这些状态的值是有规律的,按照业务节点正好是从小到大,我们就能通过它来保证接口的幂等性。
缺点:该方案仅限于要更新的表有状态字段,并且刚好要更新状态字段的这种特殊情况,并非所有场景都适用。