Суть проблемы:запросы на "SELECT count(*)" создают просто неимоверную нагрузку на mysql когда количество фотографий в галерее превышает пару десятков тысяч
Время выполнения скрипта ооочень некрасиво возростает (видать не рассчитывал никто на такие нагрузки). Когдато раньше я писал про такую вот проблему - там решение было через кеш кроном. Сильно вдумываццо не стал и решил написать свой велосипед. Сейчас тестирую - вроде работает.
Решение в теории:Чтобы не ганять по 100 раз count по огромным таблицам нужно делать простое кеширование гдето в базе результатов запроса, чтоб потом обращаццо к ним напрямую через select where не пересчитывая каждый раз таблицы. Желательно для этого не использовать никаких кронов и прочей ерунды.
Решение на практике:Решение пишу от конкретной таблицы неособо заморачиваясь префиксацией. Думаю и так будет понятно.
Структура таблицы БД:CREATE TABLE `sunph_gx_countcache` (
`query` text NOT NULL,
`date` decimal(10,0) NOT NULL,
`value` bigint(20) NOT NULL,
PRIMARY KEY (`query`(255))
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
Что здесь к чему:
-
query - здесь мы будем хранить текст запроса. Именно по тексту запроса будет происходить проверка есть у нас кеш или нет. Зачем? Для универсальности решения.
-
date - временная метка в формате date('U'). По ней будем определять устарел ли кеш и надо ли перекешировать.
-
value - собственно само кешируемое значение.
Класс-обработчик:<?php
class api_querycache{
private $time_limit;
private $time_now;
private $time_die;
private $result;
function __construct($query, $timelimit = 30){
global $CONFIG;
$this->result = 0;
$this->time_limit = $timelimit*60;
$this->time_now = date('U');
$this->time_die = $this->time_now+$this->time_limit;
$query_add = mysql_real_escape_string($query, $CONFIG['LINK_ID']);
$sql_query = "SELECT
sunph_gx_countcache.value,
sunph_gx_countcache.`date`
FROM
sunph_gx_countcache
WHERE
sunph_gx_countcache.query = '$query_add' AND
sunph_gx_countcache.`date` > '$this->time_now'";
$base = cpg_db_query($sql_query);
$size = mysql_num_rows($base);
if ($size == 1){
$tmp = mysql_fetch_object($base);
mysql_free_result($base);
$this->result = $tmp->value;
}else {
mysql_free_result($base);
$base = cpg_db_query($query);
$size = mysql_num_rows($base);
if ($size == 1){
$tmp = mysql_fetch_row($base);
mysql_free_result($base);
$this->result = $tmp[0];
$sql_query = "delete from sunph_gx_countcache
where
sunph_gx_countcache.query = '$query_add'";
$base = cpg_db_query($sql_query);
mysql_free_result($base);
$sql_query = "insert into sunph_gx_countcache
(`query`, `date`, `value`)
values ('$query_add', '$this->time_die', '$this->result')";
$base = cpg_db_query($sql_query);
mysql_free_result($base);
}
}
return TRUE;
}
function result(){
return $this->result;
}
}
?>
Его надо приинклудить хоть даже вверху
init.inc.php (у меня для своих классов специальная папка и файл-инициализатор который прикручен вверху init.inc.php)
Что здесь к чему:
- $query - запрос по которому нужно провести кеширование или взять данные из кеша. запрос на каурт, сум и т.д. тоесть возвращающий 1x1 таблицу.
- $timelimit - лимит по времени между кешами. в минутах.
Как это работает:
Сначала в таблице каша ищеццо был ли результат запроса ($query) прокеширован ранее и не истёк ли срок его жизни (`date`>date('U')).
Если вернулась строка размерностью 1 (кеш есть) то запоминаем значение и радуемся.
Если вернулся 0 (или не единица - мало ли какой там глюк произошел) - необходимо перекешировать. Для этого выполняеццо запрос ($query) и если размерность результата 1 происходит кешированье. Сначала удаляюццо все записи где запрос равен кешируемому запросу, после чего в базу заносиццо новый кеш запроса.
Примерение на практике:Прокешируем, например, один из запросов в index.php.
Старый вариант:
$result = cpg_db_query("SELECT count(*) FROM {$CONFIG['TABLE_PICTURES']} as p, {$CONFIG['TABLE_ALBUMS']} as a WHERE p.aid = a.aid AND approved='YES' AND category = {$subcat['cid']}" . $album_filter);
$nbEnr = mysql_fetch_array($result);
mysql_free_result($result);
$pic_count = $nbEnr[0];
Новый вариант:
$sql_query = "SELECT count(*) FROM {$CONFIG['TABLE_PICTURES']} as p, {$CONFIG['TABLE_ALBUMS']} as a WHERE p.aid = a.aid AND approved='YES' AND category = {$subcat['cid']}" . $album_filter;
$gx_tmp = new api_querycache($sql_query);
$pic_count = $gx_tmp->result();
В общем вот так вот
Жду комментариев, может критики, а может вообще тумаков надаёте и выгоните.
З.Ы.: на безопасность подобный марахай увы не тестил.