PHP利用MySQL保存session的实现思路及示例代码


Posted in PHP onSeptember 09, 2014

实现环境:

PHP 5.4.24
MySQL 5.6.19
OS X 10.9.4/Apache 2.2.26

一、代码

CREATE TABLE `session` (
`skey` char(32) CHARACTER SET ascii NOT NULL,
`data` text COLLATE utf8mb4_bin,
`expire` int(11) NOT NULL,
PRIMARY KEY (`skey`),
KEY `index_session_expire` (`expire`) USING BTREE
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
<?php
/*
* 连接数据库所需的DNS、用户名、密码等,一般情况不会在代码中进行更改,
* 所以使用常量的形式,可以避免在函数中引用而需要global。
*/
define('SESSION_DNS', 'mysql:host=localhost;dbname=db;charset=utf8mb4');
define('SESSION_USR', 'usr');
define('SESSION_PWD', 'pwd');
define('SESSION_MAXLIFETIME', get_cfg_var('session.gc_maxlifetime'));

//创建PDO连接
//持久化连接可以提供更好的效率
function getConnection() {
try {
$conn = new PDO(SESSION_DNS, SESSION_USR, SESSION_PWD, array(
PDO::ATTR_PERSISTENT => TRUE,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => FALSE
));
return $conn;
} catch (Exception $ex) {

}
}

//自定义的session的open函数
function sessionMysqlOpen($savePath, $sessionName) {
return TRUE;
}

//自定义的session的close函数
function sessionMysqlClose() {
return TRUE;
}
/*
* 由于一般不会把用户提交的数据直接保存到session,所以普通情况不存在注入问题。
* 且处理session数据的SQL语句也不会多次使用。因此预处理功能的效益无法体现。
* 所以,实际工程中可以不必教条的使用预处理功能。
*/
/*
* sessionMysqlRead()函数中,首先通过SELECT count(*)来判断sessionID是否存在。
* 由于MySQL数据库提供SELECT对PDOStatement::rowCount()的支持,
* 因此,实际的工程中可以直接使用rowCount()进行判断。
*/
//自定义的session的read函数
//SQL语句中增加了“expire > time()”判断,用以避免读取过期的session。
function sessionMysqlRead($sessionId) {
try {
$dbh = getConnection();
$time = time();

$sql = 'SELECT count(*) AS `count` FROM session '
. 'WHERE skey = ? and expire > ?';
$stmt = $dbh->prepare($sql);
$stmt->execute(array($sessionId, $time));
$data = $stmt->fetch(PDO::FETCH_ASSOC)['count'];
if ($data = 0) {
return '';
}

$sql = 'SELECT `data` FROM `session` '
. 'WHERE `skey` = ? and `expire` > ?';
$stmt = $dbh->prepare($sql);
$stmt->execute(array($sessionId, $time));
$data = $stmt->fetch(PDO::FETCH_ASSOC)['data'];
return $data;
} catch (Exception $e) {
return '';
}
}

//自定义的session的write函数
//expire字段存储的数据为当前时间+session生命期,当这个值小于time()时表明session失效。
function sessionMysqlWrite($sessionId, $data) {
try {
$dbh = getConnection();
$expire = time() + SESSION_MAXLIFETIME;

$sql = 'INSERT INTO `session` (`skey`, `data`, `expire`) '
. 'values (?, ?, ?) '
. 'ON DUPLICATE KEY UPDATE data = ?, expire = ?';
$stmt = $dbh->prepare($sql);
$stmt->execute(array($sessionId, $data, $expire, $data, $expire));
} catch (Exception $e) {
echo $e->getMessage();
}
}

//自定义的session的destroy函数
function sessionMysqlDestroy($sessionId) {
try {
$dbh = getConnection();

$sql = 'DELETE FROM `session` where skey = ?';
$stmt = $dbh->prepare($sql);
$stmt->execute(array($sessionId));
return TRUE;
} catch (Exception $e) {
return FALSE;
}
}

//自定义的session的gc函数
function sessionMysqlGc($lifetime) {
try {
$dbh = getConnection();

$sql = 'DELETE FROM `session` WHERE expire < ?';
$stmt = $dbh->prepare($sql);
$stmt->execute(array(time()));
$dbh = NULL;
return TRUE;
} catch (Exception $e) {
return FALSE;
}
}

//自定义的session的session id设置函数
/*
* 由于在session_start()之前,SID和session_id()均无效,
* 故使用$_GET[session_name()]和$_COOKIE[session_name()]进行检测。
* 如果此两者均为空,则表明session尚未建立,需要为新session设置session id。
* 通过MySQL数据库获取uuid作为session id可以更好的避免session id碰撞。
*/
function sessionMysqlId() {
if (filter_input(INPUT_GET, session_name()) == '' and
filter_input(INPUT_COOKIE, session_name()) == '') {
try {
$dbh = getConnection();
$stmt = $dbh->query('SELECT uuid() AS uuid');
$data = $stmt->fetch(PDO::FETCH_ASSOC)['uuid'];
$data = str_replace('-', '', $data);
session_id($data);
return TRUE;
} catch (Exception $ex) {
return FALSE;
}

}
}

//session启动函数,包括了session_start()及其之前的所有步骤。
function startSession() {
session_set_save_handler(
'sessionMysqlOpen',
'sessionMysqlClose',
'sessionMysqlRead',
'sessionMysqlWrite',
'sessionMysqlDestroy',
'sessionMysqlGc');
register_shutdown_function('session_write_close');
sessionMysqlId();
session_start();
}

二、简介

使用MySQL保存session,需要保存三个关键性的数据:session id、session数据、session生命期。
考虑到session的使用方式,没必要使用InnoDB引擎,MyISAM引擎可以获得更好的性能。如果环境允许,可以尝试使用MEMORY引擎。
保存session数据的列,有需要的话,可以使用utf8或utf8mb4字符集;保存session id的列则没有必要,一般情况使用ascii字符集就可以了,可以节约存储成本。
保存session生命期的列,可以根据工程需要进行设计。比如datetime类型、timestamp类型、int类型。对于datetime、int类型可以保存session生成时间或过期时间。
如果有必要可以扩展session表的列并修改读、写函数以支持(维护)相关列来保存诸如用户名等信息。
当前版本,只要通过session_set_save_handler注册自定义的会话维护函数就可以,不需要在其之前使用session_module_name('user')函数。
当read函数获取数据并返回,PHP会自动对其进行反序列化,一般情况请不要对数据进行更改。
PHP传递给write函数的date参数是序列化之后的session数据,直接保存即可,一般情况请不要对数据进行更改。
按照本段代码的逻辑,PHP配置选项关于会话生命期的设置已经不再有效,这个值可以自行维护,不一定需要通过get_cfg_var获取。
sessionMysqlId()函数是为了避免大用户量、多台Web服务器情况下的碰撞,一般情况PHP自动生成的session id是可以满足用户要求的。
没了

三、需求

当用户量非常大,需要多台服务器提供应用的时候,使用MySQL存储会话相对使用会话文件具有一定的优越性。比如具有最小的存储开销,比如可以避免文件共享带来的复杂性,比如可以更好的避免发生碰撞,比如相比会话文件共享具有更好的性能。总体上来说,当访问量剧增的时候,如果使用数据库保存会话带来的问题是线性增长的,那么使用会话文件带来的问题几乎是爆炸性的。好吧,换一个更直白的说法吧:如果您的应用用户量不大,其实让PHP自己处理session就好了,没必要考虑MySQL。

PHP 相关文章推荐
PHP伪静态写法附代码
Jun 20 PHP
php抽奖小程序的实现代码
Jun 18 PHP
php 字符串压缩方法比较示例
Jan 23 PHP
php ctype函数中文翻译和示例
Mar 21 PHP
PHP数据的提交与过滤基本操作实例详解
Nov 11 PHP
CodeIgniter开发实现支付宝接口调用的方法示例
Nov 14 PHP
PHP多维数组排序array详解
Nov 21 PHP
php中上传文件的的解决方案
Sep 25 PHP
php二维数组按某个键值排序的实例讲解
Feb 15 PHP
使用composer安装使用thinkphp6.0框架问题【视频教程】
Oct 01 PHP
PHP中类与对象功能、用法实例解读
Mar 27 PHP
tp5.1 框架数据库高级查询技巧实例总结
May 25 PHP
字符串长度函数strlen和mb_strlen的区别示例介绍
Sep 09 #PHP
thinkphp在模型中自动完成session赋值示例代码
Sep 09 #PHP
PHP使用Mysql事务实例解析
Sep 08 #PHP
PHP闭包实例解析
Sep 08 #PHP
PHP数组排序之sort、asort与ksort用法实例
Sep 08 #PHP
php实现的常见排序算法汇总
Sep 08 #PHP
php实现最简单的MVC框架实例教程
Sep 08 #PHP
You might like
发布一个迷你php+AJAX聊天程序[聊天室]提供下载
2007/07/21 PHP
收藏的PHP常用函数 推荐收藏保存
2010/02/21 PHP
PHP中使用php5-ffmpeg撷取视频图片实例
2015/01/07 PHP
PHP中返回引用类型的方法
2015/04/03 PHP
php ucwords() 函数将字符串中每个单词的首字符转换为大写(实现代码)
2016/05/12 PHP
微信公众号开发之获取位置信息php代码
2018/06/13 PHP
Windows服务器中PHP如何安装redis扩展
2019/09/27 PHP
jquery 图片上传按比例预览插件集合
2011/05/28 Javascript
jQuery学习笔记(4)--Jquery中获取table中某列值的具体思路
2013/04/10 Javascript
textarea不能通过maxlength属性来限制字数的解决方法
2014/09/01 Javascript
jquery插件uploadify多图上传功能实现代码
2016/08/12 Javascript
jQuery事件绑定用法详解
2016/09/08 Javascript
js获取元素的标签名实现方法
2016/10/08 Javascript
jQuery实现复选框的全选和反选
2017/02/02 Javascript
jQuery简单获取DIV和A标签元素位置的方法
2017/02/07 Javascript
vue中appear的用法
2017/08/17 Javascript
利用JS如何计算字符串所占字节数示例代码
2017/09/13 Javascript
JS实现预加载视频音频/视频获取截图(返回canvas截图)
2017/10/09 Javascript
jQuery实现table表格信息的展开和缩小功能示例
2018/07/21 jQuery
Vue 路由间跳转和新开窗口的方式(query、params)
2019/12/25 Javascript
javascript中contains是否包含功能实现代码(扩展字符、数组、dom)
2020/04/07 Javascript
[43:58]DOTA2-DPC中国联赛定级赛 LBZS vs SAG BO3第一场 1月8日
2021/03/11 DOTA
windows下wxPython开发环境安装与配置方法
2014/06/28 Python
python关键字and和or用法实例
2015/05/28 Python
pip 安装库比较慢的解决方法(国内镜像)
2019/10/06 Python
使用Pandas的Series方法绘制图像教程
2019/12/04 Python
Python实现JS解密并爬取某音漫客网站
2020/10/23 Python
英国在线自行车店:Merlin Cycles
2018/08/20 全球购物
拥有超过850家商店的美国在线派对商店:Party City
2018/10/21 全球购物
英国折扣零售连锁店:QD Stores
2018/12/08 全球购物
综合办公室主任岗位职责
2014/04/13 职场文书
小班教师个人总结
2015/02/05 职场文书
2015年教师节新闻稿
2015/07/17 职场文书
2016年大学生党员公开承诺书
2016/03/24 职场文书
学会Python数据可视化必须尝试这7个库
2021/06/16 Python
Springboot集成阿里云OSS上传文件系统教程
2021/06/28 Java/Android