博客列表 >高并发导致幻影读(Phantom read)的小小解决方案

高并发导致幻影读(Phantom read)的小小解决方案

萝卜温的博客
萝卜温的博客原创
2018年05月08日 10:28:521780浏览

情景:假设数据库有个值为10,有三个脚本进程同时读取了10,A进程+10保存到数据库,B进程+20保存到数据库,C进程+30保存到数据,最终数据库中的值为40!实际上我们想要的值是10+10+20+30=70,也就是说一个进程读取出来的值要依赖于另外一个进程的执行结果,而不能是同时读取,所以这里必须是以同步方式运行三个进程(A -> B -> C或者其他顺序也行,看调度)。

解决方法:使用锁机制

<?php

/医院
 * LockSystem.php
 *
 * php锁机制
 *
 * Copy right (c) 2016 http://blog.csdn.net/CleverCode
 *
 * modification history:
 * --------------------
 * 2016/9/10, by CleverCode, Create
 *
 */

class LockSystem
{
    const LOCK_TYPE_DB = 'SQLLock';
    const LOCK_TYPE_FILE = 'FileLock';
    const LOCK_TYPE_MEMCACHE = 'MemcacheLock';

    private $_lock = null;                  //$type对应的锁实例
    private static $_supportLocks = array('FileLock', 'SQLLock', 'MemcacheLock');

    public function __construct($type, $options = array())
    {
        if(false == empty($type))
        {
            $this->createLock($type, $options);
        }
    }

    /*
     * 功能:创建$type对应的锁实例
     */
    public function createLock($type, $options=array())
    {
        if (false == in_array($type, self::$_supportLocks))
        {
            throw new Exception("没有这种类型<${type}>的锁!");
        }
        $this->_lock = new $type($options);
    }

    /*
     * 功能:获取锁
     */
    public function getLock($key, $timeout = ILock::EXPIRE)
    {
        if (false == $this->_lock instanceof ILock)
        {
            throw new Exception('该锁没有继承 ILock 接口!');
        }
        $this->_lock->getLock($key, $timeout);
    }

    /*
     * 功能:释放锁
     */
    public function releaseLock($key)
    {
        if (false == $this->_lock instanceof ILock)
        {
            throw new Exception('该锁没有继承 ILock 接口!');
        }
        $this->_lock->releaseLock($key);
    }
}

interface ILock
{
    const EXPIRE = 5;
    public function getLock($key, $timeout=self::EXPIRE);
    public function releaseLock($key);
}

/*
 * 文件锁
 */
class FileLock implements ILock
{
    private $_fp;
    private $_single;

    public function __construct($options)
    {
        if (isset($options['path']) && is_dir($options['path']))
        {
            $this->_lockPath = $options['path'].'/';
        }
        else
        {
            $this->_lockPath = '/tmp/';
        }

        $this->_single = isset($options['single'])?$options['single']:false;
    }

    public function getLock($key, $timeout=self::EXPIRE)
    {
        $file = md5(__FILE__.$key);
        is_file($this -> _lockPath . $file . '.lock') || touch($_SERVER['DOCUMENT_ROOT'] . $this -> _lockPath . $file . '.lock');
        $this->fp = fopen($_SERVER['DOCUMENT_ROOT'] . $this->_lockPath.$file.'.lock', "w+");
        if (true || $this->_single)
        {
            $op = LOCK_EX + LOCK_NB;
        }
        else
        {
            $op = LOCK_EX;
        }
        if (false == flock($this->fp, $op, $a))
        {
            throw new Exception('添加文件锁失败!');
        }

        return true;
    }

    public function releaseLock($key)
    {
        flock($this->fp, LOCK_UN);
        fclose($this->fp);
    }
}

/*
 * sql锁
 */
class SQLLock implements ILock
{
    public function __construct($options)
    {
        $this->_db = new mysql();
    }

    public function getLock($key, $timeout=self::EXPIRE)
    {
        $sql = "SELECT GET_LOCK('".$key."', '".$timeout."')";
        $res =  $this->_db->query($sql);
        return $res;
    }

    public function releaseLock($key)
    {
        $sql = "SELECT RELEASE_LOCK('".$key."')";
        return $this->_db->query($sql);
    }
}

/*
 * Memcache锁
 */
class MemcacheLock implements ILock
{
    public function __construct($options)
    {

        $this->memcache = new Memcache();
        $this -> memcache -> addServer('127.0.0.1', 11211);
    }

    public function getLock($key, $timeout=self::EXPIRE)
    {
        //$waittime, $totalWaittime, $time的单位都是useconds;$timeout的单位是秒
        $waitime = 20000;
        $totalWaitime = 0;
        $time = $timeout*1000000;

        //动作:在等待锁没有超时并且添加失败时,进入休眠状态
        while ($totalWaitime < $time && false == $this->memcache->add($key, 1, $timeout))
        {
            usleep($waitime);
            $totalWaitime += $waitime;
        }
        if ($totalWaitime >= $time)
            throw new Exception('等待锁超时,超时时间为:'.$timeout.'秒');

    }

    public function releaseLock($key)
    {
        $this->memcache->delete($key);
    }
}

缺点:会阻塞其他进程的运行,因此需要:使用缓存加快锁内部分的数据读写,优化sql,适当添加索引等!

锁代码的原文链接

上一条:快速排序下一条:冒泡排序
声明:本文内容转载自脚本之家,由网友自发贡献,版权归原作者所有,如您发现涉嫌抄袭侵权,请联系admin@php.cn 核实处理。
全部评论
文明上网理性发言,请遵守新闻评论服务协议