PHP MySQL session_set_save_handler 竞争条件

标签 php mysql session locking race-condition

我写了一个简单的session_set_save_handler使用 MySQL innoDB 表作为存储后端。 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 中的数据会丢失。我已将其归结为 session_regenerate_id(TRUE),这会删除导致问题的前一个 session 。但是,即使使用行锁定,问题仍然存在。

我看过这个page ,以及一些code来自那个作者,但是它使用了实现表锁定的 MyISAM 表。

谁能阐明为什么锁定没有产生影响,并可能提供一些解决此问题的方法?

最佳答案

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

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

以上假定您使用的 javascript 库设置了 header 。

关于PHP MySQL session_set_save_handler 竞争条件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10273957/

相关文章:

php - 货币美元符号 : Better to concatenate in an SQL query, 还是使用 PHP?

mysql - 如何在存储过程中使用事务?

mysql - 内连接后只读 (MySQL)

php - Codeigniter 3 session 不适用于 PHP 7.1.4

session - Powershell脚本杀死.exe终端服务器断开的 session

php - 使用 .htaccess 将 allow_url_fopen 添加到我的 php.ini

Javascript id 作为 href 中的参数

php - 警告 : mysqli_fetch_array() expects parameter 2 to be long, 中给出的对象

php - Eloquent 地在约会表中获取唯一 patient_id 的列表

php - 在 session Codeigniter 中访问数组