我写了一个简单的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/