PHP实现利用MySQL保存session的方法


Posted in PHP onAugust 23, 2014

session是PHP程序设计中服务器端用来保存用户信息的一个变量,具有非常广泛的应用价值。本文实例讲述了PHP实现利用MySQL保存session的方法。分享给大家供大家参考之用。具体步骤如下:

本文实例的实现环境为:

PHP 5.4.24
MySQL 5.6.19
OS X 10.9.4/Apache 2.2.26

一、代码部分

1.SQL语句:

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;

2.PHP部分代码:

<?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();
}

二、简介

1.使用MySQL保存session,需要保存三个关键性的数据:session id、session数据、session生命期。

2.考虑到session的使用方式,没必要使用InnoDB引擎,MyISAM引擎可以获得更好的性能。如果环境允许,可以尝试使用MEMORY引擎。

3.保存session数据的列,有需要的话,可以使用utf8或utf8mb4字符集;保存session id的列则没有必要,一般情况使用ascii字符集就可以了,可以节约存储成本。

4.保存session生命期的列,可以根据工程需要进行设计。比如datetime类型、timestamp类型、int类型。对于datetime、int类型可以保存session生成时间或过期时间。

5.如果有必要可以扩展session表的列并修改读、写函数以支持(维护)相关列来保存诸如用户名等信息。

6.当前版本,只要通过session_set_save_handler注册自定义的会话维护函数就可以,不需要在其之前使用session_module_name('user')函数。

7.当read函数获取数据并返回,PHP会自动对其进行反序列化,一般情况请不要对数据进行更改。

8.PHP传递给write函数的date参数是序列化之后的session数据,直接保存即可,一般情况请不要对数据进行更改。

9.按照本段代码的逻辑,PHP配置选项关于会话生命期的设置已经不再有效,这个值可以自行维护,不一定需要通过get_cfg_var获取。

10.sessionMysqlId()函数是为了避免大用户量、多台Web服务器情况下的碰撞,一般情况PHP自动生成的session id是可以满足用户要求的。

三、需求

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

四、参考函数及概念部分:

1 http://cn2.php.net/manual/zh/function.session-set-save-handler.php
2 http://cn2.php.net/manual/zh/session.idpassing.php
3 http://cn2.php.net/manual/zh/pdo.connections.php
4 http://cn2.php.net/manual/zh/pdo.prepared-statements.php
5 http://dev.mysql.com/doc/refman/5.1/zh/sql-syntax.html#insert

希望本文所述实例对大家PHP程序设计有所帮助。

PHP 相关文章推荐
php数据库连接
Oct 09 PHP
php self,$this,const,static,-&amp;gt;的使用
Oct 22 PHP
PHP __autoload()方法真的影响性能吗?
Mar 30 PHP
php启动时候提示PHP startup的解决方法
May 07 PHP
CodeIgniter中使用cookie的三种方式详解
Jul 18 PHP
PHP屏蔽过滤指定关键字的方法
Nov 03 PHP
php中socket通信机制实例详解
Jan 03 PHP
PHP实现的oracle分页函数实例
Jan 25 PHP
屏蔽PHP默认设置中的Notice警告的方法
May 20 PHP
php微信浏览器分享设置以及回调详解
Aug 01 PHP
PHP基于pdo的数据库操作类【可支持mysql、sqlserver及oracle】
May 21 PHP
微信公众平台开发教程⑤ 微信扫码支付模式介绍
Apr 10 PHP
ThinkPHP后台首页index使用frameset时的注意事项分析
Aug 22 #PHP
ThinkPHP模板替换与系统常量及应用实例教程
Aug 22 #PHP
ThinkPHP调试模式与日志记录概述
Aug 22 #PHP
ThinkPHP连接数据库及主从数据库的设置教程
Aug 22 #PHP
ThinkPHP实例化模型的四种方法概述
Aug 22 #PHP
ThinkPHP中的create方法与自动令牌验证实例教程
Aug 22 #PHP
ThinkPHP基本的增删查改操作实例教程
Aug 22 #PHP
You might like
在任意字符集下正常显示网页的方法一
2007/04/01 PHP
php开发留言板的CRUD(增,删,改,查)操作
2012/04/19 PHP
ThinkPHP页面跳转success与error方法概述
2014/06/25 PHP
详解Laravel设置多态关系模型别名的方式
2019/10/17 PHP
Sample script that displays all of the users in a given SQL Server DB
2007/06/16 Javascript
javascript 异步页面查询实现代码(asp.net)
2010/05/26 Javascript
如何创建一个JavaScript弹出DIV窗口层的效果
2013/09/25 Javascript
JavaScript知识点总结(十六)之Javascript闭包(Closure)代码详解
2016/05/31 Javascript
node.js缺少mysql模块运行报错的解决方法
2016/11/13 Javascript
实现点击下箭头变上箭头来回切换的两种方法【推荐】
2016/12/14 Javascript
用jQuery实现优酷首页轮播图
2017/01/09 Javascript
jquery,js简单实现类似Angular.js双向绑定
2017/01/13 Javascript
AngularJS中的拦截器实例详解
2017/04/07 Javascript
Google 爬虫如何抓取 JavaScript 的内容
2017/04/07 Javascript
nodejs中安装ghost出错的原因及解决方法
2017/10/23 NodeJs
Vue项目全局配置页面缓存之按需读取缓存的实现详解
2018/08/01 Javascript
基于vue2.0的活动倒计时组件countdown(附源码下载)
2018/10/09 Javascript
vue 实现移动端键盘搜索事件监听
2019/11/06 Javascript
浅谈关于vue中scss公用的解决方案
2019/12/02 Javascript
python脚本实现查找webshell的方法
2014/07/31 Python
python定时执行指定函数的方法
2015/05/27 Python
Python2.7+pytesser实现简单验证码的识别方法
2017/12/29 Python
python下PyGame的下载与安装过程及遇到问题
2019/08/04 Python
matplotlib绘制多个子图(subplot)的方法
2019/12/03 Python
pytorch 实现在预训练模型的 input上增减通道
2020/01/06 Python
使用python实现学生信息管理系统
2021/02/25 Python
瑜伽国际:Yoga International
2018/04/18 全球购物
Geekbuying波兰:购买中国电子产品
2019/10/20 全球购物
意大利和国际最佳时尚品牌:Drestige
2019/12/28 全球购物
Servlet的实例是在生命周期什么时候创建的?配置servlet最重要的是什么?
2012/05/30 面试题
《走一步再走一步》教学反思
2014/02/15 职场文书
企业诚信承诺书
2014/05/23 职场文书
大学生党课心得体会
2016/01/07 职场文书
python3实现常见的排序算法(示例代码)
2021/07/04 Python
python 管理系统实现mysql交互的示例代码
2021/12/06 Python
浅析python中特殊文件和特殊函数
2022/02/24 Python