PHP对于超卖秒杀问题的处理方案

1 mysql悲观锁

BEGIN;
SELECT * FROM 'TEST' WHERE id = 1 FOR UPDATE;
COMMIT;

在数据处理过程中,将数据锁定,禁止此操作之外的任何数据操作,保证数据处理唯一性,需要数据库层面提供锁机制。

缺点:因为一次只能处理一条数据,所以多用户同时操作时,会出现等待问题,用户体验感极差。

2 mysql乐观锁

SELECT version FROM test;
#省略,这里写业务逻辑
UPDATE test SET number=number-1,version=version+1 WHERE id=1 AND version=#{version};

在代码层面通过版本号保证数据不会冲突,处理数据时先查询得到version,修改数据时再将version对比,如果两者相同,则说明数据在此期间没有被修改过。

缺点:还是需要数据库对所有请求进行处理,其中还包括了数据查询修改,所以很消耗数据库资源。

3 PHP+队列

系统将请求不作处理,全部存入队列中,在特定时间下,通过脚本从队列一条一条提取请求,并对其进行数据处理。

缺点:因为是一条一条处理,所以并发性不高,处理数据缓慢。

4 PHP+Redis分布式锁

#查询job是否存在
redis> EXISTS job
(integer) 0

#设置job
redis> SETNX job "php"
(integer) 1

#尝试覆盖job,失败
redis> SETNX JOB "python"
(integer) 0

#查看job
redis> GET job
"php"


SETNX 与 SET 是有区别的,前者只能存储一次,后者能无限覆盖存储
Redis分布式锁就是借此实现的

$expire = 10;  //有效时间
$key = '123456';
$value = time() + $expire;
$status = true;
while($status)
{
    //当有1000条数据进行到此时,只有一条数据会被保存成功,其余都是失败。
    $lock = $redis->setnx($key, $value);
    //失败会进入循环
    if(empty($lock))
    {
        $value = $redis->get($key);
        if($value < time())
        {
            $redis->del($key);
        }
    //成功则进入业务处理
    }else{
        $status = false;
        //这里写业务逻辑
    }
}

缺点:还是并发性不高的问题。

5 PHP+Redis乐观锁

<?php
$redis = new Redis();
$redis -> connect("127.0.0.1",6379);
//乐观锁监视number,若数据处理时number变化,事务机制将回滚数据
$redis -> watch('number');
$number = $redis -> get('number');
$n=100;
if($number >= $n){
    exit('秒杀结束');
}
//开启事务
$redis -> multi();
//将key中储存的数字值增一,如果key不存在,那么key的值会先被初始化为0,然后再执行INCR操作。
$redis -> incr('number'); 
$res = $redis -> exec(); //成功1失败0
if($res){
    //秒杀成功
    incTude ' db.php';
    $sq1="update products set store=store-1 where id=1";
    if ($mod->exec($sq1)){
        echo "秒杀完成" ;
    }
}e1se{
    exit('抢购失败');
}

相比前几个,有以下优点:

1 选用内存数据库抢购,速度极快。

2 速度快并发自然不存在

3 悲观锁会迅速增加系统资源

4 比队列强,队列会使你的内存数据库资源瞬间爆棚。

,