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

Голосование

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

Версия для печати

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

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

Андрей, благодарю за данный скрипт.

Пока что одна проблема у меня с переводом его на рельсы mysqli - в fields названия полей не прописываются, туда пишется NULL
При этом я в вашем скрипте поменял методы, дабы не путать их с mysqli. (это для себя, дабы не запутаться, хотя думаю, что всем тоже будет удобно)

Вот Код срипта с моими коментариями в том месте, где у меня не получается...
Код: Выделить всё
<?php
/**
* Модуль class.mysqlcache.php V1.0.1 Вт 14 Авг 2007
* Copyright (C) Андрей Якушев, 2007. http://avy.ru
* ---------------------------------------------------------------------
* MySQLCache - class
* Класс предназначен для кэширования результатов MySQL-запросов SELECT.
* В специальной директории создаются файлы с именем, состоящим из
* хэша запроса.
* Файл содержит результат запроса с дополнительной информацией по нему.
* К моменту вызова класса соединение с БД должно быть установлено.
* ---------------------------------------------------------------------
*/

class MySqlCache extends base {
   //Путь к директории кэш-файлов
   var $CachePath = CACHE;     //Необходимо ввести полный путь

   //Имя файла с информацией о пиковой нагрузке
   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
* и время валидности предыдущего запроса ($valid) в секундах, если такой существует.
* Возвращает логическое значение результата запроса.
* Если запрос не SELECT, то возвращает результат выполнения этого запроса.
* Это просто заглушка; никакие атрибуты класса при этом не затронутся.
*--------------------------------------------------------------------------
*/
function MySQLCache($query, $valid = 5)   {
   parent::__construct();
   if ($this->CachePath == '')   {
         $this->CachePath = dirname(__FILE__);
      }
      $query = trim($query);
      if (!eregi('^SELECT', $query))
      {
         return $this->query($query);      /////////////////////////////////////////////
         //   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 = $this->query($query);      //////////mysqli_result Object/////////////////
               //   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 = $SQLResult->field_count;      ///////////////////////////////
      //mysql_num_fields($SQLResult);
      for ($i = 0; $i < $nf; $i++)
      {
         $this->ResultData['fields'][$i] = $SQLResult->fetch_fields($i);      ////////////////////////////
        ///// ВОТ ТУТ Я НЕ ПОЛУЧАЮ НАЗВАНИЕ ПОЛЯ В РЕЗУЛЬТАТЕ //////////////
         //   mysql_fetch_field($SQLResult, $i);
      }
      /* Получение данных */
      $nr = $SQLResult->num_rows;   ////////////////////////////////////////
      //   mysql_num_rows($SQLResult);
      for ($i = 0; $i < $nr; $i++)
      {
         $this->ResultData['data'][$i] = $SQLResult->fetch_row();   /////////////////////////////////
         //   mysql_fetch_row($SQLResult);
      }
      /* Запись кэша */
      $file = fopen($filename, 'w');
      flock($file, LOCK_EX);
      fwrite($file, serialize($this->ResultData));
      fclose($file);
      return true;
   }

   // Количество полей в запросе
   function cache_num_fields()
   {
      return sizeof($this->ResultData['fields']);
   }

   // Название указанной колонки результата запроса
   function cache_field_name($num)
   {
      if (isset($this->ResultData['fields'][$num]))
      {
         return $this->ResultData['fields'][$num]->name;
      }
      else
      {
         return false;
      }
   }

   // Информация о колонке из результата запроса в виде объекта
   function cache_fetch_field($num)
   {
      if (isset($this->ResultData['fields'][$num]))
      {
         return $this->ResultData['fields'][$num];
      }
      else
      {
         return false;
      }
   }

   // Длина указанного поля
   function cache_field_len($num)
   {
      if (isset($this->ResultData['fields'][$num]))
      {
         return $this->ResultData['fields'][$num]->max_length;
      }
      else
      {
         return false;
      }
   }

   // Тип указанного поля результата запроса
   function cache_field_type($num)
   {
      if (isset($this->ResultData['fields'][$num]))
      {
         return $this->ResultData['fields'][$num]->type;
      }
      else
      {
         return false;
      }
   }

   // Флаги указанного поля результата запроса
   function cache_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 cache_num_rows()
   {
      return sizeof($this->ResultData['data']);
   }

   /* Обрабатывает ряд результата запроса и возвращает неассоциативный массив */
   function cache_fetch_row()
   {
      if (($this->NextRowNo+1) > $this->cache_num_rows())
      {
         return false;
      }
      $this->NextRowNo++;
      return $this->ResultData['data'][$this->NextRowNo - 1];
   }

   /* Обрабатывает ряд результата запроса и возвращает ассоциативный массив */
   function cache_fetch_assoc()
   {
      if (($this->NextRowNo + 1) > $this->cache_num_rows())
      {
         return false;
      }
      for ($i = 0; $i < $this->cache_num_fields(); $i++)
      {
         $result[$this->ResultData['fields'][$i]]->name =   // убрано в конце ->name
            $this->ResultData['data'][$this->NextRowNo][$i];
      }
      $this->NextRowNo++;
      return $result;
   }
}
?>
Ответить


Андрей, все получилось, я немного паниковал, но вот что сделал:

Код: Выделить всё
      /* Получение названия полей */
      $nf = $SQLResult->field_count;      ///////////////////////////////
      //mysql_num_fields($SQLResult);
      for ($i = 0; $i < $nf; $i++)
      {
        $this->ResultData['fields'][$i] = $SQLResult->fetch_field();      ////////////////////////////
         //   mysql_fetch_field($SQLResult, $i);
      }

за слешами стоят рельсы от mysql-движка
новый вариант уже на движке mysqli
Ответить


Может глупый вопрос, а может и нет...

Андрей, у меня вопрос:
А не создаст ли скрипт практически полную копию БД, коли будет работать достаточно длительное время и как этого избежать???
Ответить


По поводу первого - ничего не могу сказать. Тестировал только под MySQL.
По поводу второго - создаст. Обязательно создаст, и при чём размером большим, чем сама база.
Я избегаю этого следующим образом: крон запускает периодически скрипт, который удаляет файлы из папки кэша, возраст которых больше часа.
Ответить


скрипт для Cron`a

хм, Андрей, я так и предполагал.
Судя по всему вы хоститесь на выделеном сервере, что позволяет себе спокойно делать крон-задачи.
А если обычный пользователь, который хочет пользоваться скриптом не имет такой возможности (не прелдоставляет провайдер на легких тарифах такую услугу, а оч.нужно), то как эту задачку вы бы решили через всеми любимый ПХП? :)

ЗЫ. я тоже над этим подумаю, но пока голова забита отладкой и системой аутефикации. :roll:
Ответить


Ну, сначала немного рекламы. :)
У меня не выделенный сервер. Я хощусь на Петерхосте. Там крон включается уже с тарифного плана "Диоген" (1500 руб./год).
Ну а уж если совсем нет возможности крона, то его можно эмулировать, как это сделано в phpBB:
в каждую страницу включается вызов скрипта кроноэмулятора. Тот, в свою очередь читает файл собственного сочинения, где он записывает дату предыдущего запуска той или иной задачи. И если срок перезапуска истёк, то он запускает нужную задачу, а потом делает пометку в этом файле, что выполнил её тогда-то.
Таким образом, любой человек, зашедший на сайт, может инициировать запуск той или иной задачи.
Минусы: задача может быть достаточно сложная и выполняться долго. Человек может не дождаться конца и отменить загрузку страницы. Отсюда есть два выхода: либо учесть возможность того, что задача будет выполнена не до конца, либо, если у Вас есть авторизация, то запускать крон только если заходите Вы. А Вы-то уж будете знать, что нужно дождаться выполнения скрипта до конца.
Ответить


Андрей, благодарствю, тоже смотрел в сторону Петерхоста, но на "Диогене" там нету крона :)
http://peterhost.ru/tariffs/diogenes

Так что наш выбор падает на следующий вариант, что на 1260 подороже, но зато все в наличии :)

Уклонились в сторону.... в пинципе пока вопросов больше нету, а так тоже сколнялся к тому же решению, но пока думаю, что все же у хостера возьму тариф "Сенека".
Ответить


Joomla

Hi.
Андрей, я не так хорошо знаю фреймворк Жумлы, но хочу попробовать твой механизм.
Подскажи пож-та как прикрутить твой обработчик к жумле (1.5) или хотя бы в каком направлении копать.

Заранее спасибо.

Удачи.
Ответить


korkunov, в любом движке должен быть модуль, который запускается при каждом показе страницы. Желательно, чтобы он не запускался во время каких-нибудь внутренних отработчиков. Нужно постараться вставить вызов скрипта как можно раньше в этом модуле.
Ответить


Андрей, бодрое время суток!

Я вот что подумал, а если взять и попробовать прикрутить к скрипту проверку времени создания файла запроса к БД.
В результате используя таймаут, который проставляется в классе можно из списка файлов будет сразу выделить те, что подлежат удалению и удалять их...
Хотя с другой стороны, не будет ли проблем с тем, что будут перекрывающиеся запросы на удаление...
Но их тогда можно просто подавить (возникающие ошибки).

Вообщем жду коментариев на мою мысль...
Ответить


След.

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



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

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