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 に答える