Зачем хранить сессии ($_SESSION) в базе данных

Стандартный способ хранения данных сессии подходит для большинства задач, с которым может столкнутся разработчик, иногда всё же приходится искать альтернативу.
Из-за того что, нaпример:

  • Cайт состоит из несколько частей, которые храняться на нескольких серверах и чтобы быть увереным, что сессия работает правильно необходимо хранить данные сессии в базе данных, общей для всех серверов.
  • Сайт находиться на одном сервере с другими сайтами и необходимо избежать проблем безопасности, которые с этим связаны.
  • Ваш сайт имеет очень большие запросы к скорости работы и поэтому вам требуется более быстрый способ хранения данных сессии.

Есть несколько приемов по ускорению чтения из базы данных можно использовать.

Средства PHP делают эту задачу элементарной.

Хранение данных сессии.

Прежде чем начнем хранить данные сессии в базе данных создадим таблицу. Вот пример для MySQL:

CREATE TABLE sessions (
id varchar(32) NOT NULL,
access int(10) unsigned,
data text,
PRIMARY KEY (id)
);
Все приведено в качестве примера, также можно добавить дополнительные поля, в которых будет храниться временные метки и т.п.

После того как вы создали таблицу, давайте посмотрим, как её использовать.

session_set_save_handler()

PHP имеет встроенную функцию, которая позволяет изменить механизм сессии, заданный по умолчанию. С её помощью вы можете задавать свои функции для различных задач механизма сессии. Нужно будет написать специально для .тих целей несколько функций. Для каждой будет приведен пример с описанием.

Функция session_set_save_handler() принимает шесть параметров, каждый из которых – это имя функции, которую вам придется написать. Эти функции будут отвечать за следующие задачи:

  • Открытие места хранения данных сессии
  • Закрытие места хранения данных сессии
  • Чтение данных сессии
  • Запись данных сессии
  • Уничтожение всех данных сессии
  • Удаление данных предыдущей сессии

Для удобства, используем следующие следующие имена для этих функций:

session_set_save_handler (‘_open’, ‘_close’, ‘_read’, ‘_write’, ‘_destroy’, ‘_clean’);

эта строчка должна стоять до вызова session_start(), но сами функции вы можете объявить в любом месте.

Преимущество данного способа в том, что в коде вы можете продолжать делать всё тоже самое, как и при стандартном механизме хранения данных сесиии. Массив $_SESSION всё тот же и ведет себя точно также, php всё также генерирует и передаёт ключ сессии. Всё что вам необходимо сделать – это добавить одну строчку, указанную выше.

Также очень важно понять, что конкретно делают функции, на случай непредвиденных ошибок.

_open() и _close()

Эти функции взяимосвязаны, они используются для открытия и закрытия места хранения данных сессии соответственно. Если вы храните данные сессии в файловой системе, эти функции открывают и закрывают файлы (скорее всего вам понадобится глобальная переменная управления файлом, чтобы другие функции могли её использовать).

Поскольку вы используете базу данных, _open() и _close() могут быть настолько просты, как, например:

function _open()
{
mysql_connect('127.0.0.1', 'myuser', 'mypass');
mysql_select_db('sessions');
}
function _close()
{
mysql_close();
}
?>

Такой вариант имеет недостаток: когда переменная управления базой данных не создана для функций mysql_select_db() и mysql_query(), MySQL использует последнее содинение, то есть если где нибудь в коде сайта вы используете mysql_select_db чтобы выбрать другую базу данных, запись в сессию может не произойте, потому что последнее соединение будет с другой базой данных. Такую ошибку будет очень сложно отследить, несмотря на это, встречается она достаточно часто.

Избежать такой ошибки можно двумя способами: вы можете использовать отдельное соединение для записи данных сессии или вы можете взять привычку исползовать mysql_select_db() перед каждой функцией, которой необходима определенная база данных, это включает себя как механизм хранения сессии, так и код сайта. Это всё, конечно, при условии, что вы используете больше одной базы данных в своём коде.
Также есть вариант, когда пишется полный путь к таблице с которой работаешь.
Например:
имя базы.имя таблицы.поле
он также не панацея, так как приходится организовать механизм хранения и выборки с подстановкой имени базы в нужный момент. Каждый делает так как ему удобно, в зависимости от ситуации.

В данной статье используется первый способ – отдельное соединение для механизма сессии. Назовем переменную $_sess_db, и тогда код нащих функций будет выглядеть следующим образом:

function _open()
{
global $_sess_db;
$_sess_db = mysql_connect('127.0.0.1', 'myuser', 'mypass');
mysql_select_db('sessions', $_sess_db);
}
function _close()
{
global $_sess_db;
mysql_close($_sess_db);
}
?>

Знак подчеркивания в начале имени переменной используется как указание, что данная переменная не должна изменяться в нигде в дальнейшем коде.

Внесем еще одно изменение. Большинство встроенных php функций возвращают TRUE если ошибок нет и FALSE если при выполнении возникла ошибка. Это очень удобный прием. Функция будет делать тоже самое:

function _open()
{
global $_sess_db;
if ($_sess_db = mysql_connect('127.0.0.1', 'myuser', 'mypass')) {
return mysql_select_db('sessions', $_sess_db);
}
return FALSE;
}
function _close()
{
global $_sess_db;
return mysql_close($_sess_db);
}
?>

Перейдём к следующей функции.

_read()
Эта функция вызывается когда необходимо записать данные в сессию. Это происходит сразу после вызова _open() который, в свою очередь, вызывается с помощью session_start().

PHP передает в _read() идентификато сессии:

function _read($id)
{
global $_sess_db;
$id = mysql_real_escape_string($id);
$sql = “SELECT data FROM sessions WHERE id = '$id'”;
if ($result = mysql_query($sql, $_sess_db)) {
if (mysql_num_rows($result)) {
$record = mysql_fetch_assoc($result);
return $record['data'];
}
}
return ' ';
}
?>

Обработчик, который PHP использует для сериализации данных, задается session.serialize_handler параметром конфигурации php. По умолчанию он имеет значение php.

_write()
Функция _write() вызывается когда необходимо записать данные в сессию, обычно в самом конце скрипта.

PHP передает идентификатор сессии и данные сессии. Вы можете не волноваться о формате данных, поскольку php сериализует эти данные, они представляют из себя строку. Несмотря на это вам необходимо убедиться, что строка не содержит опасных элементов прежде чем использовать её в запросе.

function _write($id, $data)
{
global $_sess_db;
$acess = time();
$id = mysql_real_escape_string($id);
$access = mysql_real_escape_string($access);
$data = mysql_real_escape_string($data);
$sql = “REPLACE INTO sessions VALUES ('$id','$access','$data')”;
return mysql_query($sql, $_sess_db);
}
?>

Используем REPLACE, потому что так мы делаем тоже что и используя INSERT, но в случаях когда передаваемый индентификатор сессии уже существует, REPLACE удалит старую запись прежде чем записывать новую. Таким образом отпадает необходимость проверки на наличие в таблице записи с передваемым идентификатором сессии. Необходимо учесть что REPLACE работает в MySQL, но в других типах баз данных такой команды может не быть.

_destroy()
Функция _destroy() вызывается когда PHP необходимо уничтожить все данные текущей сессии. Самый очевидный пример, когда вызывается session_destroy().
PHP передаёт в функцию идентификатор сессии.
function _destroy($id)
{

global $_sess_db;
$id = mysql_real_escape_string($id);
$sql = “DELETE FROM sessions WHERE id = '$id'”;
return $result = mysql_query($sql, $_sess_db);
}
?>

Функция _destroy() стирает лишь запись в базе данных, не затрагивая массив $_SESSION.

_clean()
Функция _clean() вызывается периодически для удаления старых записей в таблице сессий. Точнее, как часто эта функция вызывается установлено двумя параметрами конфигурации php: session.gc_probability и session.gc_divisor. Их значения по умолчанию 1 и 100 соответственно, что означает что вероятность вызова функции _clean() за сессию равно 1 поделить на 1000 = 0.1%.

Так как функция _write() записывает в таблицу для каждой записи точное время последнего доступа в колонку access, это может быть использовано чтобы определить какие записи удалять. PHP передаёт максимальное количество секунд после которых сессиия считается просроченной:

function _clean($max)
{
global $_sess_db;
$old = time() - $max;
$old = mysql_real_escape_string($old);
$sql = “DELETE FROM sessions WHERE access < '$old' ”;
return $result = mysql_query($sql, $_sess_db);
}
?>

Количество секунд которые PHP передаёт в эту функцию – это значение параметра session.gc_maxlifetime из конфигурации PHP. Вы можете изменять это значение в случае необходимости.

Как ведите все довольно просто ).

  • PHP Сессии и Google Page Rank
  • phpbb3 Warning includes/auth/auth_.php
  • Проблемы с кодировкой передаваемых данных из базы в flash (UTF-8)
  • SEO правила
  • Интеграция авторизации phpbb3
  • Фильтрация данных в PHP
  • 5 комментариев на “Зачем хранить сессии ($_SESSION) в базе данных”

    1. Дмитрий:

      Огромное спасибо за статью, очень конкретно написано.

      Я бы все так и хотел уточнить на примере скажем оzon .ru

      Я зашёл на сайт (стартует сессия и записывается номер ее в базу).
      Я хожу по ozоn смотрю книги (старт сессия — проверяется что я уже есть и все)

      Выбираю книгу -> перехожу на страницу корзины (старт сессии (я есть) и заносится в базу мой выбор)

      Выбираю количество -> обновить (старт сессии- и добавление количества товаров)
      Нажимаю кнопку оплатить (Старт сессии — извлечение данных о моем выборе, то есть там номер книги и количество ) и уже иду оплачивать. (старт сессии — сверка — удаление сессии из базы)

      Примерно я правильно написал?

    2. saintist:

      да все правильно поняли

    3. Дмитрий:

      Ещё «маленькое» уточнение. :)
      В сессиях хранятся временные ID коды пользователя, коды товаров и их количество, а cookies хранится информация об идентификаторе сессии и идентификационный номер пользователя который уже зарегистрирован и авторизовался а если он гость то он не имеет куков для авторизации (он сам выбирает регистрироваться или быть просто гостем) Правильно?

      Получается двойственное действие скрипта если гость — то работает все через сессию вплоть до оплаты. Но если он регистрированный пользователь то его сессия уже должна быть постоянной, но так как сессия меняется надо при авторизованном пользователе использовать его идентификационный номер (в куках).
      И опять присваивать временный код сессии и грубо объединять для работы сессию и личный код пользователя что бы он мог купить что то.

      В тексте сплошная тавтология — но может вы поймёте меня? :)

    4. saintist:

      все верно

      пока не авторизирован храним в куках

      авторизированный храним в сессии в БД, что дает для авторизированного пользователя хранить кроме текущей сессии еще и историю заказов и т.п.

    5. Vladimir:

      Сессии хранятся в базе, чтобы сопоставить аккаунт юзера с зашедшим пользователем, без повторного запроса пароля. Куки хранят session_id, а в базе хранится соответствие этого session_id с id пользователя. Хранить в куках Id пользователя нельзя, так как любой может вписать id админа и получить полный доступ. В связи с этим, необходимость хранения сессий в базе очень актуальна.
      Да, без таблиц можно обойтись. Например, если хранить помимо id пользователя какой-нибудь хэш.
      Допустим, хэш будет от id пользователя и его секретного кода (который будет генерироваться при регистрации). Кука будет вида: user_id|hash. В авторизации будем брать данные юзера, генерировать хэш и сверять с тем, что в куках. Если все ок — то пускаем пользователя под этими данными. Такой способ возможен, но не так гибок: нельзя сосчитать количество онлайн, определить онлайновость пользователя. Можно, конечно, обновлять таймштамп в таблице пользователя. Но если пользователей много, это приведет к многочисленным блокировкам.
      Короче без сессионной таблицы можно обойтись в малопосещаемых сайтах.

    Оставить комментарий