Cache MySQL query results with PHP WinCache
In November 2011, I wrote a post about MySQL query caching with PHP/Zend_Cache, and I recently stumbled upon a blog post caching MySQL query results in memcached by “KutuKupret”. This made me wonder if the same would be easily done with the Windows Cache Extension for PHP.
Yes, and it turns out to be pretty easy as well! You can cache and store the MySQL query result in your web server RAM memory, utilizing PHP WinCache. Or another PHP opcode cache like Opcache for that matter. This’ll increase database and PHP performance.
Here is a simple PHP, WinCache and MySQL example.
Well, you almost can’t call this an example or demonstration, because I use only one record in the table. But here goes… Don’t forget to use SQL_NO_CACHE to disable MySQL’s own caching mechanism.
Setup a simple table in your MySQL database
CREATE TABLE winc ( personID int NOT NULL AUTO_INCREMENT, PRIMARY KEY( personID ), FirstName varchar( 15 ), LastName varchar( 15 ), Age int );
Code language: PHP (php)
INSERT INTO winc ( FirstName, LastName, Age ) VALUES ( 'Memory', 'Cache', '100' );
Code language: JavaScript (javascript)
PHP script to cache MySQL query results in WinCache’s memory
Now create a simple PHP script to access the MySQL database, run the query, store the query in WinCache‘s ucache, and return the result. PHP code taken from KutuKupret his earlier mentioned blogpost and modified for this case.
All we have to do is generate a unique key for our query result and store that key (with its result of course) in the WinCache memory. We use an md5 -hash for generating a unique key, wincache_ucache_add for adding the key/value to the WinCache memory and wincache_ucache_get to get a result if the key is found.
/** * WinCache Extension for PHP * store MySQL query result in Wincache user cache (ucache) * http://www.php.net/manual/en/function.wincache-ucache-add.php * * 14-02-2013 - jan@saotn.nl, www.saotn.org Sysadmins of the North * Twitter: @Jan_Reilink * Donate: https://www.paypal.me/jreilink */ $dbhost = 'mysql.example.com'; $dbuser = 'examplecom'; $dbpass = 'pass_word'; $conn = mysql_connect( $dbhost, $dbuser, $dbpass ) or die ( 'Error connecting to mysql' ); $dbname = 'examplecom'; mysql_select_db( $dbname ); $key = md5( "SELECT * FROM winc where FirstName='Memory'" ); $get_result = array(); $get_result = wincache_ucache_get( $key ); if ( $get_result ) < echo "FirstName: " . $get_result['FirstName'] . "\n"; echo "LastName: " . $get_result['LastName'] . "\n"; echo "Age: " . $get_result['Age'] . "\n"; echo "Retrieved From Cache\n"; > else < // Run the query and get the data from the database then cache it // Disable MySQL Query Cache with SQL_NO_CACHE for testing! $query = "SELECT * FROM winc where FirstName='Memory';"; $result = mysql_query( $query ); $row = mysql_fetch_array( $result ); echo "FirstName: " . $row[1] . "\n"; echo "LastName: " . $row[2] . "\n"; echo "Age: " . $row[3] . "\n"; echo "Retrieved from the Database\n"; // Store the result of the query for 30 seconds wincache_ucache_add( $key, $row, 30 ); mysql_free_result( $result ); > // Did you like this code or article? Then please donation to // https://paypal.me/jreilink. It'll be used for coffee, thanks! :) ?>
Code language: PHP (php)
First time access, the query will be access directly from the MySQL database and displayed to the browser.
FirstName: Memory LastName: Cache Age: 100 Retrieved from the Database
Code language: JavaScript (javascript)
Reload your browser, and now the query is pulled from WinCache’s memory and displayed to the browser.
FirstName: Memory LastName: Cache Age: 100 Retrieved From Cache
time wget -q -O - dev.example.org/dev/mysqlc.php FirstName: Memory LastName: Cache Age: 100 Retrieved from the Database real 0m0.222s user 0m0.000s sys 0m0.000s
Code language: JavaScript (javascript)
time wget -q -O - dev.example.org/dev/mysqlc.php FirstName: Memory LastName: Cache Age: 100 Retrieved From Cache real 0m0.035s user 0m0.000s sys 0m0.000s
By storing MySQL query result in memory, we get a nice performance gain! (:
Что использовать при кешировании запросов MySQL в PHP
Есть необходимость сделать в PHP кеширование выполняемых запросов к MySQL. Но встроенное кеширование в MySQL не подходит по ряду причин. Одна из главных — сброс кеша при любом изменении таблицы — это критично, так как разрабатываемая система многопользовательская. Я уже задумывался, чтобы хранить результаты тяжелых запросов в файлах на сервере и при очередном выполнении запроса (с привязкой к пользователю системы) проверять, если уже такой запрос был, то брать из файла. Но в этом случае придется решать проблему именно отслеживания изменений в таблице.
Можете посоветовать какое-то оптимальное кеширование SQL запросов со стороны PHP? Или может есть ещё какое-то интересное решение?
Заранее благодарен за ответы!
Еще почитайте про redis. Сам его использую для кеширования. Может лучше подойдет вашему проекту, чем мемкеш.
У вас ложная информация по поводу главной причины, которая не дает Вам использовать нормальный вариант, но не это главное. Делайте таблицы-агрегаты. Посмотрите запросы и постройте индексы. Заполняйте их данными после перевода договоров в стабильные статусы. Если у вас есть лишняя оперативка — лучше отдать ее правильно спроектированной БД, чем скармливать бесполезному в этом случае мемкешу, например.
Договора заканчиваются, периоды закрываются, эти данные становятся статикой и хорошо берутся из агрегатов. Вариант с кешем хорош при частых обращениях к одинаковым данным. Ваши пользователи чаще загружают один и тот же договор, или смотрят последовательно разные? Наверняка последнее, а вы забьете оперативку договорами, которые уже не нужны пользователю. Повторюсь — лучше забить эту оперативку индексами.
Но для общего развития изучить все, об чем выше уже рассказали — мегаполезно.
Спасибо за ответ! Да, я и хочу попробовать именно агрегирование финальных данных, которые уже с большой вероятностью не будут меняться, но пока изучаю тему, поэтому все впереди))
А что делать, если сайт является новостным и каждый блок на главной странице делает по несколько запросов? Как лучше в данном случае оптимизировать вывод? Таким образом я пришел к memcache и локальному хранению данных в файлах. Нагрузка упала, но при тестирование через apache инструмент ab, показывает не очень хорошие результаты.
Одна из главных — сброс кеша при любом изменении таблицы — это критично, так как разрабатываемая система многопользовательская. Я уже задумывался, чтобы хранить результаты тяжелых запросов в файлах на сервере и при очередном выполнении запроса (с привязкой к пользователю системы) проверять, если уже такой запрос был, то брать из файла. Но в этом случае придется решать проблему именно отслеживания изменений в таблице.
— Получается немного нелогично. С одной стороны Вас беспокоит сброс кэша при любом изменении таблицы, с другой стороны Вы говорите что на кэш изменения в таблице влияют и их надо отслеживать.
Один из относительно легких вариантов для кэша, это memory таблица в mysql, куда складируются либо все данные которые часто выбираются либо ИД для их выбора из основных таблиц. Плюс по сравнению с 3rd-party решением тут то, что закэшить можно не прогоняя лишние данные через php и сеть, просто insert into select from — в большинстве случаев. Это во-первых. А во-вторых, триггерами при изменениях основной таблицы можно настроить апдейт кэширующей, более гибко чем просто тупой кэш запросов.
Ну и наконец если база относительно небольшая, то innodb + большой размер пула/кэша — тогда вообще все в памяти будет постоянно считай.
Ну да, волнует сброс кеша, так как при определенном количестве пользователей кеширование станет бесполезным, и даже сделает ситуацию хуже, так как эти пользователи будут работать с одной и той же таблицей, кеш будет сбрасываться для всех, и соответственно данные каждый раз все равно будут браться из базы, но при этом ещё будет результат запроса помещаться в кеш, и практически тут же сбрасываться.
Про Memory таблицу спасибо, почитаю, но, если я правильно понял, то в такой таблице фиксированный размер полей, и соответственно, для кеширования результатов, возвращающий разный набор полей необходимы свои таблицы?
Не фиксированный полей, а только поля с фиксированной длиной. Char/int — можно, varchar/longtext — нельзя и т.д…
В остальном — если речь о том, что Вам нужно вытаскивать определённые данные о пользователе, то вопрос в том — какие Вам данные о пользователе нужны? Может проще ввести в таблице пользователей поле типа text и аггрегированные данные (баланс там, очки, карма) хранить там? Вместо огорода с отдельными кэшами? Выборка из таблицы пользователей по ID будет не сильно медленнее чем кэши.
Нет, данные довольно сложные. Разрабатывается система документооборота. И запрос, который выбирает элементарно договора организации, получается сложный. Так как необходимо так же выбрать счета к договору, подсчитать сумму оплат по счетам, и прочие цифры. Договоров может быть весьма много, порядка 100 и больше. И чтобы выполнить такой запрос, БД требуется немаленькое время. Да и данные по объему выходят за рамки стандартных Char и Int. Поэтому хочется сохранять именно результат выборки для конкретного пользователя, чтобы потом просто по запросу его вытаскивать без обращения к БД лишний раз.