Кеширование запросов MySQL
При развитии сайта, может возникнуть проблема избыточной нагрузки на сервер баз данных MySQL. Это может вызвать некоторые неудобства. Во-первых, страницы сайта будут отдаваться медленно. Во-вторых, вряд ли хостеру понравится, что Вы чрезмерно нагружаете сервер.
В моменты пиковых нагрузок бывает так, что одни и те же страницы отдаются нескольким посетителям одинаковыми, несмотря на то, что время от времени эта информация может изменяться. И проблему большой нагрузки на сервер MySQL может помочь решить кеширование запросов.
Не так давно столкнувшись с подобной проблемой, пришлось порыться в сети в поисках вариантов реализации кеширования. Мой выбор остановился на приведенном ниже классе, разработанном .
Всегда можно предположить частоту обновления той или иной информации. Таким образом, к каждому запросу к БД можно приставить некоторое число секунд, в продолжение которых запрос с большой вероятностью будет выдавать одни и те же результаты. Это и будет время жизни кэша, который организован нижеследующим классом.
Что же представляет собой этот класс и как он работает? Класс, при инициации его переменной, проверяет, есть ли в кэше результаты предыдущего выполненного запроса. Если есть, результаты выдаются из кэша, если нет — запрос выполняется, результаты записываются. В специальной директории создаются файлы с именем, состоящим из хэша запроса. Файл содержит результат запроса с дополнительной информацией по нему. К моменту вызова класса соединение с БД должно быть установлено.
Перед тем, как инициировать переменную класса, необходимо ввести в параметр CachePath путь к папке, где будут хранится файлы кэша. В противном случае будет попытка создать файл в корневой папке сервера, а это не получится.
Параметр Debug, установленный в TRUE, позволит при обнаружении ошибки в синтаксисе запроса, выдавать сообщения на экран. Сообщения выдаются в виде фразы Error from query, после чего следует текст запроса в кавычках и текстовое сообщение о типе ошибки. После чего работа скрипта прекращается. Если же параметр установлен в FALSE, то информация об ошибке записывается в параметрах errno и error, конструктор класса возвращает FALSE, а вызывающему скрипту предоставляется возможность самостоятельно обрабатывать появившуюся ошибку.
Теперь о выполнении. Инициация переменной класса происходит с передачей двух параметров: строки sql-запроса и числового параметра, указывающего количество секунд, в течение которых можно пользоваться предыдущим результатом запроса, если таковой имелся. Если результат выполнения этой операции не FALSE, то можно пользоваться информацией, полученной классом. Выполнение запроса осуществляется следующим образом:
$query='SELECT * FROM `Table`';
$cache=new MySQLCache($query, 600);
Для получения информации от запроса, использованы методы класса, которые очень похожи на стандартные функции доступа к результатам запроса mysql, но без префикса:
- num_fields() — Количество полей в запросе
- field_name($num) — Название указанной колонки результата запроса
- fetch_field($num) — Информация о колонке из результата запроса в виде объекта
- field_len($num) — Длина указанного поля
- field_type($num) — Тип указанного поля результата запроса
- field_flags($num) — Флаги указанного поля результата запроса
- num_rows() — Количество рядов результата запроса
- fetch_row() — Обрабатывает ряд результата запроса и возвращает неассоциативный массив
- fetch_assoc() — Обрабатывает ряд результата запроса и возвращает ассоциативный массив.
Подробное описание каждой из функций можно найти в описании таких же стандартных функций MySQL.
Для примера, теперь вместо выражения
$row=mysql_fetch_assoc($SQLResult);
Вы должны ставить
$row=$cache->fetch_assoc();
В качестве дополнительной информации, в папке с файлами кэша находится файл !peak.txt. Он содержит информацию о пиковой нагрузке на сервер БД, т.е. информацию о запросе, который выполнялся дольше всех. Файл содержит 4 строчки: время выполнения в секундах, дату выполнения, строку запроса и скрипт, вызвавший этот запрос. Для того, чтобы сбросить информацию о пиковой нагрузке, достаточно удалить этот файл. К этой информации можно получить доступ и программным путём: вся она становится доступной через свойство Peak класса.
/*
*----------------------------------------------------------------------
* Модуль class.mysqlcache.php
* Copyright (C) Андрей Якушев, 2007. http://avy.ru
*----------------------------------------------------------------------
* MySQLCache - class
* Класс предназначен для кэширования результатов MySQL-запросов SELECT.
* В специальной директории создаются файлы с именем, состоящим из
* хэша запроса.
* Файл содержит результат запроса с дополнительной информацией по нему.
* К моменту вызова класса соединение с БД должно быть установлено.
*----------------------------------------------------------------------
*/class MySQLCache{
//Путь к директории кэш-файлов
var $CachePath=''; //Необходимо ввести полный путь
//Имя файла с информацией о пиковой нагрузке
var $PeakFilename='!peak.txt';
//Флаг, при установке которого ошибки запросов выводятся на экран
var $Debug=true;
//Флаг, указывающий, что данные выдаются из кэша
var $FromCache=false;
//Дата формирования данных
var $DataDate=0;
//Численный код ошибки выполнения последней операции с MySQL
var $errno=0;
//Строка ошибки последней операции с MySQL
var $error='';
//Информация о пиковой нагрузке
var $Peak=array(
0, //Время выполнения
'', //Дата выполнения
'', //Запрос
'', //Вызвавший скрипт
);
//Номер следующей выдаваемой строки
var $NextRowNo=0;
//Массив результатов запроса
var $ResultData=array(
'fields'=>array(),
'data'=>array(),
);
/*
*--------------------------------------------------------------------------
* Конструктор
* Принимает в качестве параметра запрос SELECT
* и время валидности предыдущего запроса в секундах, если такой существует.
* Возвращает логическое значение результата запроса.
* Если запрос не SELECT, то возвращает результат выполнения этого запроса.
* Это просто заглушка; никакие атрибуты класса при этом не затронутся.
*--------------------------------------------------------------------------
*/
function MySQLCache($query, $valid=10){
if ($this->CachePath==''){
$this->CachePath=dirname(__FILE__);
}
$query=trim($query);
if (!eregi('^SELECT', $query)){
return mysql_query($query);
}
$filename=$this->CachePath.'/'.md5($query).'.txt';
/* Попытка чтения кэш-файла */
if ((@$file=fopen($filename, 'r')) && filemtime($filename)>(time()-$valid)){
flock($file, LOCK_SH);
$serial=file_get_contents($filename);
$this->ResultData=unserialize($serial);
$this->DataDate=filemtime($filename);
$this->FromCache=true;
fclose($file);
return true;
}
if ($file){
fclose($file);
}
/* Выполнение запроса */
$time_start=microtime(true);
@ $SQLResult=mysql_query($query);
$time_end=microtime(true);
$this->DataDate=time();
$time_exec=$time_end-$time_start;
/* Обработка ошибки запроса */
if (!$SQLResult){
if ($this->Debug){
die('Error from query "'.$query.'": '.mysql_error());
}else{
$this->errno=mysql_errno();
$this->error=mysql_error();
return false;
}
}
/* Проверка пиковой нагрузки */
$peak_filename=$this->CachePath.'/'.$this->PeakFilename;
if (@$file=fopen($peak_filename, 'r')){
flock($file, LOCK_SH);
$fdata=file($peak_filename);
foreach ($fdata as $key=>$value){
$this->Peak[$key]=trim($value);
}
$this->Peak[0]=floatval($this->Peak[0]);
}
if ($file){
fclose($file);
}
if ($time_exec>$this->Peak[0]){
$this->Peak=array(
$time_exec,
date('r'),
$query,
$_SERVER['SCRIPT_FILENAME'],
);
$file=fopen($peak_filename, 'w');
flock($file, LOCK_EX);
fwrite($file, implode("\n", $this->Peak));
fclose($file);
}
/* Получение названия полей */
$nf=mysql_num_fields($SQLResult);
for ($i=0; $i<$nf; $i++){
$this->ResultData['fields'][$i]=mysql_fetch_field($SQLResult, $i);
}
/* Получение данных */
$nr=mysql_num_rows($SQLResult);
for ($i=0; $i<$nr; $i++){
$this->ResultData['data'][$i]=mysql_fetch_row($SQLResult);
}
/* Запись кэша */
$file=fopen($filename, 'w');
flock($file, LOCK_EX);
fwrite($file, serialize($this->ResultData));
fclose($file);
return true;
}
/*** Количество полей в запросе ***/
function num_fields(){
return sizeof($this->ResultData['fields']);
}
/*** Название указанной колонки результата запроса ***/
function field_name($num){
if (isset($this->ResultData['fields'][$num])){
return $this->ResultData['fields'][$num]->name;
}else{
return false;
}
}
/*** Информация о колонке из результата запроса в виде объекта ***/
function fetch_field($num){
if (isset($this->ResultData['fields'][$num])){
return $this->ResultData['fields'][$num];
}else{
return false;
}
}
/*** Длина указанного поля ***/
function field_len($num){
if (isset($this->ResultData['fields'][$num])){
return $this->ResultData['fields'][$num]->max_length;
}else{
return false;
}
}
/*** Тип указанного поля результата запроса ***/
function field_type($num){
if (isset($this->ResultData['fields'][$num])){
return $this->ResultData['fields'][$num]->type;
}else{
return false;
}
}
/*** Флаги указанного поля результата запроса ***/
function field_flags($num){
if (!isset($this->ResultData['fields'][$num])){
return false;
}
$result=array();
if ($this->ResultData['fields'][$num]->not_null){
$result[]='not_null';
}
if ($this->ResultData['fields'][$num]->primary_key){
$result[]='primary_key';
}
if ($this->ResultData['fields'][$num]->unique_key){
$result[]='unique_key';
}
if ($this->ResultData['fields'][$num]->multiple_key){
$result[]='multiple_key';
}
if ($this->ResultData['fields'][$num]->blob){
$result[]='blob';
}
if ($this->ResultData['fields'][$num]->unsigned){
$result[]='unsigned';
}
if ($this->ResultData['fields'][$num]->zerofill){
$result[]='zerofill';
}
if ($this->ResultData['fields'][$num]->binary){
$result[]='binary';
}
if ($this->ResultData['fields'][$num]->enum){
$result[]='enum';
}
if ($this->ResultData['fields'][$num]->auto_increment){
$result[]='auto_increment';
}
if ($this->ResultData['fields'][$num]->timestamp){
$result[]='timestamp';
}
return implode(' ', $result);
}
/* Количество рядов результата запроса */
function num_rows(){
return sizeof($this->ResultData['data']);
}
/* Обрабатывает ряд результата запроса и возвращает неассоциативный массив */
function fetch_row(){
if (($this->NextRowNo+1)>$this->num_rows()){
return false;
}
$this->NextRowNo++;
return $this->ResultData['data'][$this->NextRowNo-1];
}
/* Обрабатывает ряд результата запроса и возвращает ассоциативный массив */
function fetch_assoc(){
if (($this->NextRowNo+1)>$this->num_rows()){
return false;
}
for ($i=0; $i<$this->num_fields(); $i++){
$result[$this->ResultData['fields'][$i]->name]=
$this->ResultData['data'][$this->NextRowNo][$i];
}
$this->NextRowNo++;
return $result;
}
}
?>
05 Июн 2019 at 2:27
Вау, круто!