Implementation environment:
PHP 5.4.24
MySQL 5.6.19
OS X 10.9.4/Apache 2.2.26
1. Code
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(); }
2. Introduction
To use MySQL to save a session, three key data need to be saved: session id, session data, and session lifetime.
Considering the way the session is used, there is no need to use the InnoDB engine, the MyISAM engine can achieve better performance. If the environment permits, you can try to use the MEMORY engine.
If necessary, you can use the utf8 or utf8mb4 character set for the column that saves the session data; it is not necessary for the column that saves the session id. Generally, you can use the ascii character set, which can save storage costs.
The column that saves the session life cycle can be designed according to project needs. For example, datetime type, timestamp type, int type. For datetime and int types, the session generation time or expiration time can be saved.
If necessary, you can extend the columns of the session table and modify the read and write functions to support (maintain) relevant columns to save information such as user names.
In the current version, you only need to register a custom session maintenance function through session_set_save_handler. There is no need to use the session_module_name('user') function before it.
When the read function obtains the data and returns it, PHP will automatically deserialize it. In general, please do not change the data.
The date parameter passed by PHP to the write function is the session data after serialization, which can be saved directly. In general, please do not change the data.
According to the logic of this code, the PHP configuration option's session lifetime setting is no longer valid. This value can be maintained by itself and does not necessarily need to be obtained through get_cfg_var.
The sessionMysqlId() function is to avoid collisions when there are a large number of users and multiple Web servers. Generally, the session id automatically generated by PHP can meet user requirements.
No more
3. Demand
When the number of users is very large and multiple servers are required to provide applications, using MySQL to store sessions has certain advantages over using session files. For example, it has minimal storage overhead, can avoid the complexity caused by file sharing, can better avoid collisions, and has better performance than session file sharing. Generally speaking, when the number of visits increases sharply, if the problems caused by using the database to save sessions increase linearly, then the problems caused by using session files are almost explosive. Well, let's put it in a more straightforward way: If your application has a small number of users, you can actually let PHP handle the session by itself. There is no need to consider MySQL.
For this problem, array storage can be used, and finally output into separated strings, which is a multi-string operation.
I can send you a sample code.
$gb_DBname="charles_friend";//Database name
$gb_DBuser="charles_friend";//Database user name
$gb_DBpass="wxyzoui";//Database password
$gb_DBHOSTname=" localhost";//The name or IP address of the host
$SESS_DBH="";
$SESS_LIFE=get_cfg_var("session.gc_maxlifetime");//Get the maximum validity period of the session.
function sess_open($save_path,$session_name){
global $gb_DBHOSTname,$gb_DBname,$gb_DBuser,$gb_DBpass,$SESS_DBH;
if(!$SESS_DBH=mysql_pconnect($gb_DBHOSTname,$gb_DBuser,$ gb_DBpass)){
echo "