mysql商品库存扣减问题总结

文章讨论内容

秒杀类的问题一直都是web领域比较热点的问题,一个超高并发的网站须要考虑从产品、前端优化、站点部署及后端服务等等全部环节进行考虑。mysql所能抗住的写压力是必定的,高并发的web站点,你须要在数据持久化以前控制好压力,而不是把全部的请求都落到数据服务这一层。今天我不在这篇文章里讨论秒杀总体设计的问题(我也没这个资格),咱们讨论的是如何在流速已经获得控制的状况下,如何利用mysql更安全、高效的解决这个问题。php

从网上能够看到各类各样的实现方案,如今针对这些方案及其优缺点和理解误区进行讨论。html

常见写法安全性及效率分析

假设咱们的商品表的schema是下面这样的:前端

CREATE TABLE `goods` (
  `id` int(10) NOT NULL AUTO_INCREMENT COMMENT '自增id',
  `name` varchar(256) NOT NULL DEFAULT '' COMMENT '商品名称',
  `available` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '库存剩余量',
  `stock` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '总库存量',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='商品表'

  

设置为字段无符号解决 mysql

num = select available from goods where id = xx ; if(num > 0){ affectRows = udpate goods set available = available - 1 where id = xx ; if(affectRows == 1){ return ok ; }else{ return fatal ; } }

这种作法你们的想法是咱们将库存字段设置成无符号类型,这样当库存字段在sql执行时候被置为负数的时候mysql就会报错,那么affectRow就会是0或者能够捕获到这个异常,从而实现并发下的数据安全。web

解法释义

实际上这段代码是危险的,由于在不一样版本的mysql和配置下,这段代码的表现彻底不一样。具体的状况会出现3种不一样的结果:sql

  • 1.代码正常运行,执行update的时候报错
  • 2.代码最终执行结果出现了 -1
  • 3.最终update操做以后,available变成了一个很大的数目

为何会出现这三种状况呢?数据库

我想在学习开始学习计算机的时候都讲过计算机的加减法计算方法。后端

思考一下,无符号2 减去 无符号3 在计算机中的运算是什么样的?
2 - 3 = 2 + (-3)
假设咱们的计算机是4位的,2的补码表示:0010,-3的补码表示为1011
那么加和的结果是
0010
1011 + 
------
1111 =
1111解释为有符号数是多少呢? -1
1111解释为无符号数是多少呢? 15

  

因此呢? 安全

若是mysql不作任何处理的话,你的无符号数减法的结果不会报错,最终你算出来的库存仍是一个很是大的值(可怕)。
可是幸运的是mysql 后来的版本帮你作了这件事情(具体哪一个版本我也不清楚),因此若是是mysql作了无符号检测的话,若是减出的结果是负值,会报错,这是大多数人期待的结果。
-1这种状况是须要你设置一下sqlmode的,这也是会出现的状况。并发

解法总结

  • 这个办法不少人用的时候没问题,那只能说明多是机缘巧合,可是对于业务代码而言,不能靠碰运气,须要消除不肯定性、缩小迁移成本。
  • 若是你想采用这种办法,辛苦你把大家msyql相应的版本及配置搞清楚,肯定无符号在你所在的版本会出现什么结果。

select for update

解法释义

读取时候就开始加排他锁也是网上常见的办法之一,具体实现以下:

begin tran ;
num = select avaliable from goods where id = xxx for update;
if (num >= 0){
affectNum = udpate goods set available = available - 1 where id = xx ;
commit ;
return affectNum ;
}else{
rollback ;
}

  


该解法在用户读取的时候对相应的数据加排他锁,保证本身在更新的时候该行的数据不会被别的进程更改.全部写请求及排他锁加锁都会被阻塞。 

想一想这样的状况,A进程执行过程当中,出现死机的状况致使commit/rollback请求没有被发送到mysqlserver,那么全部请求都会锁等待。

解法总结

  • 低流量能够采用这种办法来保证数据的安全性
  • 性能低下,平均须要发送4次mysql请求,同时会形成全部同类请求锁等待。

    常见问题

  • select for udpate 须要在显式的指定在事务代码块执行,否则不会起做用。不少网友都理所固然的人为select for update直接就能够加排他锁
  • 排他锁的释放是在rollback/commit 动做完成才会释放,不是在update操做以后。mysql innodb执行两段锁协议,加锁阶段只加锁,解锁阶段只解锁。

采用事务,先查后写再查,确保没问题

解法释义

这时候的available设置为有符号类型,解决方案一的问题

begin tran ;
num = select available from goods where id = xx ;
if(num > 0){
   //实际须要关心这里的返回值,这里不考虑
   udpate goods set available = available - 1 where id = xx ;
   num_afterupdate = select available from goods where id = xx ;
   if(num_afterupdate < 0 ){
       rollback ;
   }else{
       commit ;
   }
}


这种解法区分于第一种的办法在于,加了事务、available类型更改、采用了更新后确认的形式,尝试解决问题。 

咱们都知道数据库的事务隔离级别有4种:
RU,RC,RR,Serializable。
咱们常见的innodb中RR模式是能够保证可重复读,意思是在同一个事务内部,屡次读取的结果是一致的。那么最后一次的读取对于RR隔离级别其实是无效的。
RC模式下,这个代码是可用的,每次请求能够确保本身的进程不会超发。

解法总结

  • RR、RC模式下结果不一致.RR下不可保证安全、RC能够。
  • 性能不高,一次业务请求到mysql的转化为 1 : 5。
  • 这种解法就像老奶奶锁门,老是不放心本身到底锁了没有,走了几步再回来看看,实际上有些时候是徒劳。

update语句增长available查询条件

解法释义

udpate goods set available = available - 1 where id = xx and available - 1 >= 0 ;


你们有的另外一个误区是单条语句不是事务,实际上单条sql也是一个事务。
问题的关键就集中在怎么证实这句的安全性的。
咱们都知道update操做对于id为主键索引的状况下,是会对数据加行锁。
其实update操做在mysql内部也是一个先查后改的过程,这个过程若是是原子的,那么能够保证update语句是串行的,那咱们就来看一下update语句在mysql内部的执行过程。
update执行过程

那么对于上面这个语句,同样遵循两段锁协议。
update执行的过程,会去查询知足条件的行并加锁,这个加锁是innodb作的,那么就能够保证别的事务必须等到该事务执行完了以后才能得到锁,此时拿到最新数据。

解法总结

  • 语句安全、效率最优(个人认知里)

采用设置库存而不是扣减库存

这几天我把相似的文章几乎翻了一遍,惟一看到批评个人上一条作法的是个人那个作法是不具有幂等性的。

  • 所谓幂等性就是,同一个用户对同一链接的访问不会产生反作用。好比上一条的方案,若是记录用户的操做和扣减库存不是原子操做的话,就有可能出现的问题是,库存扣减成功了,可是用户记录失败了,那么用户重复请求,就会出现屡次减库存的问题。

那么他们的解法是这样的,采用设置而不是扣减,代码以下:

num_old = select available from goods where id = xx and available >= 1 ;

num_new = num_old - 1 ;

update goods set num=num_new where id=xx and num=num_old ;

  

这段代码也是安全的,采用的是乐观所的理念来完成的操做。 

总结

  • 上面的作法,最后两个是相对安全的,可是你的库存字段仍是要设置为无符号,关因而否幂等,要看结合请求看,不是单个扣减块代码。
  • 较真是一个学习的过程,只有较真才能把这些概念搞清楚。若是你须要彻底弄懂这些内容,可能你须要对mysql锁、事务、mvcc这些概念都作一下预习。
  • 感谢工做过程当中小伙伴们的努力,让咱们把问题追查的更清楚。

引用

 

固然这个过程当中我也是看了一些比较经典的mysql书籍,也推荐你看一下:

1.《高性能MySQL(第3版)》       https://u.jd.com/DyajuZ

2.《MySQL技术内幕:InnoDB存储引擎(第2版)》  https://u.jd.com/BsVLNm

 

 

有什么问题均可以扫码一块儿交流,这篇文章很早以前在本身搭建的一个博客地址上写的,由于coidng.me 的域名再也不使用了,因此没法维护。

相关文章
相关标签/搜索