Blogger Information
Blog 33
fans 0
comment 1
visits 43068
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template
高并发导致幻影读(Phantom read)的小小解决方案
萝卜温的博客
Original
1787 people have browsed it

情景:假设数据库有个值为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,适当添加索引等!

锁代码的原文链接

Statement of this Website
The copyright of this blog article belongs to the blogger. Please specify the address when reprinting! If there is any infringement or violation of the law, please contact admin@php.cn Report processing!
All comments Speak rationally on civilized internet, please comply with News Comment Service Agreement
0 comments
Author's latest blog post