我使用 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 表。
任何人都可以阐明为什么锁定没有影响,并可能为这个问题提供一些解决方案吗?