Достаточно часто встречающаяся ситуация - постраничный вывод товаров, отобранных по определенным параметрам. Как в таких случаях снизить количество запросов к базе и вообще ненужные расчеты?
В некоторых местах нарочно привел методы "в лоб", не включая свой взгляд на оптимизацию кода.
@content[]#условия запросов данных, например "dlina > $form:dlina AND ves = $form:ves AND cena < $form:cena"$zapros[условия запроса]#вычисляем количество строк, согласно запросу$tovarCount[^int:sql{SELECT COUNT(id) FROM tovar WHERE $zapros}]#выводим верхнюю постраничную навигацию^pager[$tovarCount;$limit_;page]#выбираем строки, согласно запросу$tovar[^table::sql{SELECT id FROM tovar WHERE $zapros LIMIT ^eval($limit_*($page_-1)), $limit_}]^tovar.menu{^show_tovar[$tovar.id]}[<br>]#выводим нижнюю постраничную навигацию^pager[$tovarCount;$limit_;page]@show_tovar[_id]$_tovar[^table::sql{SELECT * FROM tovar WHERE id = $_id}]
ID: $_tovar.id<br>
Название: $_tovar.name<br>
Длина: $_tovar.dlina<br>
Вес: $_tovar.ves<br>
Цена: $_tovar.cena<br>
+ вместо pager можно посмотреть на для построения постраничной навигации из примеров, там сразу будут для вас рассчитаны значения для limit и offset и не надо будет такие страшные конструкции выносить в часто-читаемое место.
P.S. ух какая древность там выложена. надо будет обновить при случае.
@content[]#условия запросов данных, например "dlina > $form:dlina AND ves = $form:ves AND cena < $form:cena"$zapros[условия запроса]#выбираем строки, согласно запросу$tovar[^table::sql{SELECT SQL_CALC_FOUND_ROWS id FROM tovar WHERE $zapros LIMIT ^eval($limit_*($page_-1)), $limit_}]#вычисляем общее количество строк, согласно запросу$tovarCount[^int:sql{SELECT FOUND_ROWS()}]#выводим верхнюю постраничную навигацию^pager[$tovarCount;$limit_;page]^tovar.menu{^show_tovar[$tovar.id]}[<br>]#выводим нижнюю постраничную навигацию^pager[$tovarCount;$limit_;page]@show_tovar[_id]$_tovar[^table::sql{SELECT id, name, dlina, ves, cena, text FROM tovar WHERE id = $_id}]
ID: $_tovar.id<br>
Название: $_tovar.name<br>
Длина: $_tovar.dlina<br>
Вес: $_tovar.ves<br>
Цена: $_tovar.cena<br>
Подход верный, но можно добавить кеширование для каждого товара. Тогда на страницах со списком товаров надо получать список id (как сейчас) и доставать инфу о товаре по одному: из кеша, если есть; из базы, если нет в кеше + тут же класть в кеш.
На страницах с товаром тоже самое только для одного id (товара). Т.е. товар -- объект, который писан в базе, но информация о нём может быть атомарно закеширована.
Важно учесть, что скорость работы с кешем (диском) может быть меньше, чем запрос в базу данных. А в случае с интернет магазином и большим количеством товаров кеш будет занимать много места на диске и, по сути, быть избыточным звеном.
Я даже думал кешировать страницы целиком, но запросы всегда разные и на все случаи жизни не напасешься.
Каждый товар в отдельности закешировать можно, только как определить ту грань, когда кешировать уже нужно? И не даст ли дополнительную нагрузку в .menu{} проверка условия ^if(def /cache/$tovar.id), если этих файлов тысячи?
Данные и из базы и из кеша достаются не большие, но часто.
У парсера есть встроенный класс кеширования, который сам проверяет наличие файлов, их валидность и время жизни.
Грань определять не надо. Запрос данных происходит при обращении к объекту (товару), а будут они из кеша или из базы знает сам объект.
В menu оно, действительно, будет перебирать файлы. Но тут надо определиться, что сложнее и дороже по ресурсам в вашей системе -- тяжёлый запрос из базы или чтение кеша с диска.
Если запросы лёгкие, то кеширование не нужно. Можно саму базу и запросы оптимизировать.
Опять же, можно в разных местах доставать инфу о товаре по-разному: если список, то одним запросом для всех товаров (такой вариант уже предлагали), если товар -- один запрос на товар. Но мне такая схема не нравится, они не гибкая, отсутствует объектная модель.
Не совсем про навигацию, но: @show_tovar (б-р-р название) можно переписать так, чтобы он учитывал тип переданного параметра и ожидал объект или строку/число.
Тогда ^show_tovar(123) сходит в базу и достанет товар по ID, а ^show_tovar[$table.fields] для строки уже имеющейся таблицы просто выведет значения.
Это универсальный, но не красивый подход.
Лучше так: @getGoods[…] — выбирает список товаров, возвращает таблицу; @getOneOfGoods[ID] — выбирает товар по ID, возвращает хэш; @printOneOfGoods[hash] — выводит поля товара.
Таким образом будут разделены данные и их представление, и метод вывода полей не будет ходить в базу, а заниматься строго своими обязанностями, понятия не имея о том, где и как хранятся товары. Ровно как и методы выборки не будут ничего знать о том, как данные форматируются и куда выводятся.
Посмотрите на код, если вперемешку идут запросы к базе (написанные снова и снова для одних и тех же действий), постобработка и вывод — это сигнал о том, что стоит навести порядок.
... но и выбор правильных движков для таблиц, настройка сервера, индексы и пр. безусловно имеют значение.
Скажем, комбинация innodb-таблиц, кеш запросов, разумная денормализация данных и правильная настройка сервера делают использование кеша на стороне Парсера практически бессмысленным. Другое дело, что не все имеют собственный сервер или в состоянии грамотно настроить MySQL на виртуальных машинах. :)
Не совсем, есть моменты которые надо максимально избегать...
Например при выборке данных за определенный период, не делать преобразование дат на стороне mysql, а отдавать именно правильную дату. Избегать любых вычеслений на стороне сервера, например для новостей. Новости вносятся в базу заранее и показываются, только тогда когда дата публикации прошла. Можно это сделать через
WHERE dPublish <= now()
Но такой запрос не попадет в кеш при самых правильных настройках, но если заменить now() на текущую дату в текстовом(числовом) значении, все поменяется в лучшую сторону.
Приведённый пример описывает список (коллекцию) как самодостаточный класс с минимальным функционалом: вывести всё, добавить запись.
Но ведь у конкретной записи может быть много методов. На примере товара: создать (добавить) товар, удалить, изменить его свойства, перенести в другую категорию и т.д. В этом случае теряется контекст конкретного списка, поэтому для товара должен быть свой класс, формирующий объект и предоставляющий все необходимые методы (в которых, не забываем, надо разделить логику и представление).