ストレージ バックエンドとして 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 テーブルを使用しています。
ロックが違いを生まない理由について誰かが光を当て、おそらくこの問題の解決策を提供できますか?