2

我使用 MySQL innoDB 表作为存储后端编写了一个简单的session_set_save_handler 。MySQL是5.5。

代码看起来像这样(非常简化,但希望能说明代码流),我使用RedBean ORM与数据库交互,但命令应该清楚地显示正在写入、读取或删除的内容。:

class MySession{

    private $_database;  

    public function  __construct($database){
       //database object is injected using dependency injection then assigned to private var

       //Hook up handlers
       session_set_save_handler(
        array($this, "open"),
        array($this, "close"),
        array($this, "read"),
        array($this, "write"),
        array($this, "destroy"),
        array($this, "garbageCollection"));
       }

       //Register the shutdown function
       register_shutdown_function('session_write_close'); 

       //start
       session_start();

       //Regenerate session id
       //(NOTE: in production code, the id is regenerated every 10 minutes,
       //but regenerating allows us to trigger the race condition for demonstration.
       session_regenerate_id(TRUE);

    public function open($savePath, $sessionName){
        $this->_database->exec('SET TRANSACTION ISOLATION LEVEL SERIALIZABLE');
        $this->_database->begin(); //Begin Transaction
        return true;
    }   

    public function close(){
        $this->_database->commit(); //Commit the transaction
        return true;
    }

    public function read($id){

        $this->_database->exec('SELECT * FROM session WHERE identifier = ? LOCK IN SHARE MODE', array($id));
        $session = $this->_database->findOne('session', 'identifier = ?', array($id));
        return $session->data;
    }

    public function write($id, $sessionData){

        $this->_database->exec('SELECT * FROM session WHERE identifier = ? FOR UPDATE', array($id));
        $session = $this->_database->findOne('session', 'identifier = ?', array($id));

        //We need to create a new session
        if ($session == NULL){
            $session = $this->_database->dispense('session');
        }

        $this->_database->store($session);
        return TRUE;
    }

    public function destroy($id){
        $this->_database->exec('SELECT * FROM session WHERE identifier = ? FOR UPDATE', array($id));
        $session = $this->_database->findOne('session', 'identifier = ?', array($id));

        if ($session != NULL){
            $this->_database->trash($session);
        }
        return TRUE;
    }

    public function garbageCollection($maxlifetime){
        $old = ... //Calculate the time for expired sessions

        $this->_database->exec("DELETE from session WHERE last_activity < ?", array($old));
        return TRUE;
    }
}

问题是即使锁定到位,如果我向应用程序发送多个请求(例如,使用 AJAX),竞争条件仍然会发生并且存储在会话中的数据会丢失。我已将其固定到session_regenerate_id(TRUE),这会删除上一个会话作为问题的原因。但是,即使使用行锁定,问题仍然存在。

我查看了这个页面,以及该作者的一些代码,但它使用了实现表锁定的 MyISAM 表。

任何人都可以阐明为什么锁定没有影响,并可能为这个问题提供一些解决方案吗?

4

1 回答 1

1

我认为您最好的选择是仅在您知道请求是整页请求而不是 AJAX 请求时才重新生成会话 ID。

if (empty($_SERVER['HTTP_X_REQUESTED_WITH'])) {
    session_regenerate_id(TRUE);
}

以上假设您使用的 javascript 库设置了标题。

于 2012-05-21T02:09:40.223 回答