我正在尝试使用数据库存储细节的会话处理程序。
有一点是,如果当前正在使用会话数据,那么我不希望它们被加载(直到第一个脚本完成并更新数据)。
我有一个基本类来做这个看起来很有效,下面有一个小测试脚本: -
<?php
class MySessionHandler implements SessionHandlerInterface
{
private $db = null;
public function open($savePath, $sessionName)
{
$host = 'localhost';
$user = 'root';
$pass = '';
$name = 'em_sessions';
$this->db = new mysqli($host, $user, $pass, $name);
return true;
}
public function close()
{
return true;
}
public function read($session_key)
{
$this->db->autocommit(FALSE);
if($stmt = $this->db->prepare("SELECT data FROM sessions WHERE session_key = ? FOR UPDATE"))
{
$stmt->bind_param('s', $session_key);
$stmt->execute();
echo $stmt->error." - ".$stmt->errno."<br />";
$retry = 0;
while ($stmt->errno == 1213 and $retry++ < 20)
{
usleep(50);
$stmt->execute();
echo $stmt->error." - ".$stmt->errno."<br />";
}
$stmt->store_result();
$stmt->bind_result($data);
$stmt->fetch();
}
else
{
echo "Unable to prepare statement ".$this->db->error."<br />";
}
return $data;
}
public function write($session_key, $data)
{
$time = time();
if($stmt = $this->db->prepare("INSERT INTO sessions (set_time, data, session_key) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE set_time=VALUES(set_time), data=VALUES(data)"))
{
echo "Written(".time().")<br />";
$stmt->bind_param('iss', $time, $data, $session_key);
$stmt->execute();
$this->db->commit();
}
else
{
echo "Unable to prepare statement ".$this->db->error."<br />";
}
return true;
}
public function destroy($session_key)
{
if($stmt = $this->db->prepare("DELETE FROM sessions WHERE session_key = ?"))
{
$stmt->bind_param('s', $session_key);
$stmt->execute();
}
else
{
echo "Unable to prepare statement ".$this->db->error."<br />";
}
return true;
}
public function gc($maxlifetime)
{
if($stmt = $this->db->prepare("DELETE FROM sessions WHERE set_time < ?"))
{
$old = time() - $max;
$stmt->bind_param('s', $old);
$stmt->execute();
}
else
{
echo "Unable to prepare statement ".$this->db->error."<br />";
}
return true;
}
}
$handler = new MySessionHandler();
session_set_save_handler($handler, true);
session_start();
echo session_id()."<br />";
echo "<a href='http://localhost/TestArea/session_test.php'>Do Me</a><br />";
if (!array_key_exists('cnt', $_SESSION))
{
echo "No session cnt found so initialising<br />";
$_SESSION['cnt'] = 0;
}
echo "Page called ".++$_SESSION['cnt']." times (".time().")<br />";
sleep(2);
session_write_close();
sleep(2);
echo "Ended(".time().")<br />";
基本类使用FOR UPDATE选择会话详细信息以锁定行(会话ID是唯一键),关闭自动提交。当要保存会话详细信息时(即,脚本结束或调用session_write_close()时)将调用一个方法来使用ON DUPLICATE KEY UPDATE插入会话详细信息,然后执行提交以释放锁定。
测试脚本的想法是它加载会话数据,等待几秒钟,调用session_write_close()(它应该触发会话详细信息写入表,并释放锁定以便下次尝试抓住表格然后等待几秒钟再结束剧本。
这只是为了测试会话是否在需要时被锁定,然后使用相同的会话ID释放下一个脚本。
然而,在测试这个时我有一个奇怪的问题。如果我一次触发这个脚本的六个运行(页面上有一个链接 - 只需用鼠标中键点击该链接六次),那么第一个运行正常。但是在第一个脚本完成之前不会触发第二个脚本(即第二次出现的开始时间与第一次出现的结束时间相同)。这让我觉得不对。
第3次出现将更快开始。它们的开始时间将是先前出现的会话详细信息写入表中的时间。这就是我所期望的。
我的困惑在于我为什么要在脚本的第二次和后续出现之间产生差异。我真正使用后续处理的方式会很好,但第一个可能是一个更大的问题;也是最常发生的情况。
我可能错过了一些简单的事情。
会话数据表如下: -
CREATE TABLE IF NOT EXISTS `sessions` (
`set_time` int(11) NOT NULL,
`session_key` char(32) NOT NULL,
`data` longtext NOT NULL,
PRIMARY KEY (`session_key`),
KEY `set_time` (`set_time`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;