Цитата мудреца

Голосование

Система Orphus. Если вы заметили ошибку на сайте, нажмите сюда.
Загружается, подождите...
Начало сайта Материалы сайта Программы PHP-скрипты
Версия для слабовидящих
Версия для печати

Кэширование запросов MySQL

Здесь представлены некоторые мои разработки на PHP. Это в, основном, служебные скрипты, которые работают в составе других скриптов и не могут быть протестированы здесь непосредственно.

Кэширование запросов MySQL

Идея создания кэширования запросов к базе данных MySQL имела два корня: во-первых, в периоды наибольшей нагрузки на сервера, страницы отдавались достаточно медленно (время выполнения скрипта доходило до 20 секунд). Во-вторых, участились жалобы хостера на то, что мой акаунт создаёт недопустимые нагрузки на сервер БД. Очевидно, что в моменты пиковых нагрузок одни и те же страницы выдаются нескольким посетителям совершенно одинаковыми, несмотря на то, что там всё-таки присутствует информация, которая время от времени меняется. Всегда можно предположить частоту обновления той или иной информации. Таким образом, к каждому запросу к БД можно приставить некоторое число секунд, в продолжение которых запрос с большой вероятностью будет выдавать одни и те же результаты. Это и будет время жизни кэша, который организован нижеследующим классом.


Сейчас этот класс используется на сайте pritchi.ru. С момента его запуска замечаний от хостера о превышении нагрузки не было. Выполнение же скриптов не занимает больше 1,5 сек (в пики нагрузок).


Что же представляет собой этот класс и как он работает? Класс, при инициации его переменной, проверяет, есть ли в кэше результаты предыдущего выполненного запроса. Если есть, результаты выдаются из кэша, если нет — запрос выполняется, результаты записываются. В специальной директории создаются файлы с именем, состоящим из хэша запроса. Файл содержит результат запроса с дополнительной информацией по нему. К моменту вызова класса соединение с БД должно быть установлено.


Перед тем, как инициировать переменную класса, необходимо ввести в параметр 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 класса.



Код: Выделить всё
<?php
/*
 *----------------------------------------------------------------------
 * Модуль class.mysqlcache.php V1.0.1 Вт 14 Авг 2007
 * 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;
   }
}
?>
Ответить

Пред.След.

Вернуться в PHP-скрипты



Кто сейчас на сайте

Зарегистрированные пользователи: нет зарегистрированных пользователей