PHP MySQL session_set_save_handler竞争条件

I have written a simple session_set_save_handler using a MySQL innoDB table as the storage backend. MySQL is 5.5.

Code looks like so (very simplified, but hopefully illustrates the flow of code), I am using RedBean ORM for interacting with the database, but the commands should be clear in showing what was being written, read or deleted.:

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;
    }
}

The problem is that even with the locking in place, if I send multiple requests to the application (for example, using AJAX), the race condition still occurs and the data stored in the session is lost. I have pinned it down to session_regenerate_id(TRUE), which deletes the previous session to being the cause of the issue. However, even with the row locking, the issue still occurs.

I have looked at this page, as well as some code from that author, but that uses MyISAM tables which implements table locking.

Can anyone shed some light as to why the locking is not making a difference and perhaps offer some solutions to this problem?

I think your best option is to only regenerate the session ID when you know the request is a full-page request, rather than an AJAX request.

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

The above assumes the javascript library you're using sets the header.