使用laravel解决库存超出的几个方案
数据库字段

1.错误的示范
/**
* 错误示范
* Create by Peter Yang
* 2021-06-08 10:57:59
* @return string
*/
function test1()
{
//商品id
$id = request()->input('id');
$product = Product::where('id', $id)->firstOrFail();
if ($product->num <= 0) {
return "卖光啦!!";
}
//库存减1
$product->decrement('num');
return "success";
}
使用go模拟并发
package main
import (
"fmt"
"github.com/PeterYangs/tools/http"
"sync"
)
func main() {
client := http.Client()
wait := sync.WaitGroup{}
for i := 0; i < 50; i++ {
wait.Add(1)
go func(w *sync.WaitGroup) {
defer w.Done()
res, _ := client.Request().GetToString("http://www.api/test1?id=1")
fmt.Println(res)
}(&wait)
}
wait.Wait()
}
在数据库中查看库存

库存已超出
2.redis原子锁
/**
* redis原子锁
* Create by Peter Yang
* 2021-06-08 11:00:31
*/
function test2()
{
//商品id
$id = request()->input('id');
$lock = \Cache::lock("product_" . $id, 10);
try {
//最多等待5秒,5秒后未获取到锁,则抛出异常
$lock->block(5);
$product = Product::where('id', $id)->firstOrFail();
if ($product->num <= 0) {
return "卖光啦!!";
}
//库存减1
$product->decrement('num');
return 'success';
}catch (LockTimeoutException $e) {
return '当前人数过多';
} finally {
optional($lock)->release();
}
}
库存正常

3.mysql悲观锁
/**
* mysql悲观锁
* Create by Peter Yang
* 2021-06-08 11:00:47
*/
function test3()
{
//商品id
$id = request()->input('id');
try {
\DB::beginTransaction();
$product = Product::where('id', $id)->lockForUpdate()->first();
if ($product->num <= 0) {
return "卖光啦!!";
}
//库存减1
$product->decrement('num');
\DB::commit();
return "success";
} catch (\Exception $exception) {
}
}
库存正常

4.mysql乐观锁
/**
* mysql乐观锁
* Create by Peter Yang
* 2021-06-08 11:00:47
*/
function test4()
{
//商品id
$id = request()->input('id');
$product = Product::where('id', $id)->first();
if ($product->num <= 0) {
return "卖光啦!!";
}
//修改前检查库存和之前是否一致,不一致说明已经有变动,则放弃更改
$res = \DB::update('UPDATE `product` SET num = num -1 WHERE id = ? AND num=?', [$id, $product->num]);
if (!$res) {
return '当前人数过多';
}
return 'success';
}
库存正常

优化乐观锁
修改库存的sql修改为
\DB::update('UPDATE `product` SET num = num -1 WHERE id = ? AND num-1 >= 0', [$id]);
5.redis存储库存
/**
* 库存放在redis
* Create by Peter Yang
* 2021-06-15 15:18:31
* @return string
*/
function test5()
{
//商品id
$id = request()->input('id');
$num = Redis::command('get', ['product_' . $id]);
if ($num <= 0) {
return "卖完啦!";
}
//减库存
$re = Redis::command('decrby', ['product_' . $id, 1]);
//减多了回滚
if ($re < 0) {
Redis::command('incrby', ['product_' . $id, 1]);
return "卖完啦!";
}
return 'success';
}
本作品采用《CC 协议》,转载必须注明作者和本文链接
本帖由系统于 4年前 自动加精
关于 LearnKu
高认可度评论:
现在很需要用 大佬
应该加一个把数量 push 到
redis的list上, 这样子就不用加锁了.把数据库设置成无符号
偏个题,PHP 作为一门图灵完备语言,模拟并发完全可以自己语言内解决,可以看这篇 数据库不使用悲观锁导致问题的一种复现方式
6
如果用悲观锁的话,当并发量大的时候,虽然数据会保证准确性,但是会造成堵塞、响应会变慢,体验不好。
库存字段无符号unsigned,不就少了个num>0的条件吗
如果使用乐观锁或者悲观锁,虽然能保证数量正确,但是会有大量的请求返回
当前人数过多。测试请求数量10000有9896个返回当前人数过多。测试使用的是memcached数量没有啥问题,redis还没有测试。建议如果并发量大的话不要使用悲观锁或者乐观锁。可以说很完整
目前使用的是5,后台生产商品的库存redis,API消费库存。
这种秒杀环境还是老老实实的用redis吧,
redis 原子锁好评 :+1:
一般来说并发都不能在数据库层面去解决的,redis首选 :smile:
redis 原子锁可以
单机单库存的情况,无符号类型,直接 update t set stock=stock-1 where id=1 就解决了。还搞那么复杂花里胡骚的。
从数据库实现,我这个最牛逼。不接受反驳 function test3()
{
超卖超卖! 是一个很综合的话题,面试高发问题! 总结下思路: 1:redis 锁 2:mysql本身机制 a:读锁 b:写锁
我都是用redis lua脚本去实现的扣库存和事务