秒杀系统的数据库中的库存加减操作是最为关键的点。12年天猫双十一的超卖事件,对平台的负面影响是非常巨大的。
数据库里做库存扣减,简单的可以用以下SQL来说明:
update stock_table set inventory=inventory-1 where item_id=xx and inventory>0
该SQL的含义是,对于指定商品在库存充足情况下扣减库存,该语句在MySQL数据库中执行可以保证其原子性(隔离级别大于等于Read-Committed)。
在秒杀业务场景下,对某个商品进行高并发扣减库存时,MySQL将会成为高并发业务的瓶颈:
高并发场景下的数据库瓶颈
MySQL使用WAL(write ahead log)方式来实现数据的持久化,即事务提交后,被更新数据写到硬盘后,才能返回成功。而硬盘的读写是整个服务中最耗时的操作,也是秒杀系统的瓶颈所在。
为了提高性能,针对数据库的优化就提到整个写库操作之前,即引入组提交机制,同时提交多个事务,这样多个写操作只会有一次写硬盘操作,对于该组事务,耗时仍然是一次硬盘,但提升了整个系统的吞吐量。
组提交机制并不是万能的,若事务之间存在冲突(比如针对同一商品进行库存修改),那么基于悲观锁进行并发控制的MySQL,只会排队执行,即后一个事务要等前一个事务提交完成后才能执行。使用扣减库存的SQL举例如下:
找到并对商品记录加锁 --> 判断库存余量 --> 修改库存余量 --> 写盘 --> 释放锁
针对同一个热点商品的多个并发事务,在上面加锁和释放锁之间的这段操作是无法做到并发执行的,此时系统的瓶颈就是磁盘IO。在大量并发事务都在争抢行锁的情况下,情况会进一步恶化,系统负载会突增,再加上锁冲突检测等额外代价,可能系统的整体吞吐会降低至个位数。
超卖风险
数据库一般会建立主备,在主库和从库间进行数据同步时会有一定的延迟,而一般系统中会采用从库读,主库写的方式。在高并发场景下,若主库数据未实时同步到从库,这样就会有超卖的风险。例如主库宕机后,部分binlog未及时同步到从库,此时从库切换为主库,恢复未同步成功的binlog,即已经扣减的库存,此时就会有超卖风险。