php-doc-en/reference/mysqlnd_qc/quickstart.xml

1722 lines
53 KiB
XML
Raw Normal View History

<?xml version="1.0" encoding="utf-8"?>
<!-- $Revision: 321951 $ -->
<chapter xml:id="mysqlnd-qc.quickstart" xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Quickstart and Examples</title>
<para>
The mysqlnd query cache plugin is easy to use.
This quickstart will demo typical use-cases, and provide practical advice on getting
started.
</para>
<para>
It is strongly recommended to read the reference sections in addition to the
quickstart. It is safe to begin with the quickstart.
However, before using the plugin in mission critical
environments we urge you to read additionally the background information from the
reference sections.
</para>
<para>
Most of the examples use the <link linkend="ref.mysqli">mysqli</link> extension
because it is the most feature complete PHP MySQL extension. However, the plugin
can be used with any PHP MySQL extension that is using the
<link linkend="book.mysqlnd">mysqlnd</link> library.
</para>
<section xml:id="mysqlnd-qc.quickstart.concepts" xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Architecture and Concepts</title>
<para>
The query cache plugin is implemented as a PHP extension.
It is written in C and operates under the hood of PHP. During the
startup of the PHP interpreter, it gets registered as a
<link linkend="book.mysqlnd">mysqlnd</link> plugin to replace selected
mysqlnd C methods. Hereby, it can change the behaviour of any
PHP MySQL extension (<link linkend="ref.mysqli">mysqli</link>,
<link linkend="ref.pdo-mysql">PDO_MYSQL</link>,
<link linkend="ref.mysql">mysql</link>) compiled to use the
mysqlnd library without changing the extensions API. This makes
the plugin compatible with each and every PHP MySQL application.
Because existing APIs are not changed, it is almost transparent
to use. Please, see the
<link linkend="mysqlnd.plugin">mysqlnd plugin API description</link>
for a discussion of the advantages of the plugin architecture and
a comparison with proxy based solutions.
</para>
<para>
<emphasis role="bold">Transparent to use</emphasis>
</para>
<para>
At PHP run time PECL/mysqlnd_qc can proxy queries send from PHP
(<link linkend="book.mysqlnd">mysqlnd</link>) to the MySQL server.
It then inspects the statement string to find whether it shall cache
its results. If so, result set is cached using a storage handler and
further executions of the statement are served from the cache for
a user-defined period. The Time to Live (TTL) of the cache entry
can either be set globally or on a per statement basis.
</para>
<para>
A statement is either cached if the plugin is instructed to cache all
statements globally using a or, if the query string starts with the SQL hint
(<literal>/*qc=on*/</literal>). The plugin is capable of caching any
query issued by calling appropriate API calls of any of the existing
PHP MySQL extensions.
</para>
<para>
<emphasis role="bold">Flexible storage: various storage handler</emphasis>
</para>
<para>
Various storage handler are supported to offer different scopes for cache
entries. Different scopes allow for different degrees in sharing cache
entries among clients.
</para>
<para>
<itemizedlist>
<listitem>
<para>
<literal>default</literal> (built-in): process memory, scope: process, one or more web requests depending on PHP deployment model used
</para>
</listitem>
<listitem>
<para>
<literal>APC</literal>: shared memory, scope: single server, multiple web requests
</para>
</listitem>
<listitem>
<para>
<literal>SQLite</literal>: memory or file, scope: single server, multiple web requests
</para>
</listitem>
<listitem>
<para>
<literal>MEMCACHE</literal>: main memory, scope: single or multiple server, multiple web requests
</para>
</listitem>
<listitem>
<para>
<literal>user</literal> (built-in): user-defined - any, scope: user-defined - any
</para>
</listitem>
</itemizedlist>
</para>
<para>
Support for the <literal>APC</literal>, <literal>SQLite</literal> and <literal>
MEMCACHE</literal> storage handler has to be enabled at compile time. The <literal>
default</literal> and <literal>user</literal> handler are built-in. It is possible
to switch between compiled-in storage handlers on a per query basis at run time.
However, it is recommended to pick one storage handler and use it for all cache entries.
</para>
<para>
<emphasis role="bold">Built-in slam defense to avoid overloading</emphasis>
</para>
<para>
To avoid overload situations the cache plugin has a built-in slam defense mechanism.
If a popular cache entries expires many clients using the cache entries will try
to refresh the cache entry. For the duration of the refresh many clients may
access the database server concurrently. In the worst case, the database server
becomes overloaded and it takes more and more time to refresh the cache entry, which
in turn lets more and more clients try to refresh the cache entry. To prevent
this from happening the plugin has a slam defense mechanism. If slam defense is
enabled and the plugin detects an expired cache entry it extends the life time
of the cache entry before it refreshes the cache entry. This way other concurrent
accesses to the expired cache entry are still served from the cache for a certain
time. The other concurrent accesses to not trigger a concurrent refresh. Ideally,
the cache entry gets refreshed by the client which extended the cache entries lifespan
before other clients try to refresh the cache and potentially cause an overload
situation.
</para>
<para>
<emphasis role="bold">Unique approach to caching</emphasis>
</para>
<para>
PECL/mysqlnd_qc has a unique approach to caching result sets that is superior
to application based cache solutions. Application based solutions first fetch
a result set into PHP variables. Then, the PHP variables are serialized for
storage in a persistent cache, and then unserialized when fetching. The mysqlnd
query cache stores the raw wire protocol data sent from MySQL to PHP in its cache
and replays it, if still valid, on a cache hit. This way, it saves an extra
serialization step for a cache put that all application based solutions have to
do. It can store the raw wire protocol data in the cache without having to
serialize into a PHP variable first and deserializing the PHP variable for storing
in the cache again.
</para>
</section>
<section xml:id="mysqlnd-qc.quickstart.configuration">
<title>Setup</title>
<para>
The plugin is implemented as a PHP extension. See also the
<link linkend="mysqlnd-qc.installation">installation instructions</link> to
install the
<link xlink:href="&url.pecl.package;mysqlnd_qc">PECL/mysqlnd_qc</link> extension.
</para>
<para>
Compile or configure the PHP MySQL extension (<link linkend="ref.mysqli">mysqli</link>,
<link linkend="ref.pdo-mysql">PDO_MYSQL</link>,
<link linkend="ref.mysql">mysql</link>) that you plan to use with support
for the <link linkend="book.mysqlnd">mysqlnd</link> library. PECL/mysqlnd_qc
is a plugin for the mysqlnd library. To use the plugin with any of the existing PHP
MySQL extensions (APIs), the extension has to use the mysqlnd library.
</para>
<para>
Then, load the extension into PHP and activate the plugin in the PHP configuration
file using the PHP configuration directive named
<link linkend="ini.mysqlnd-qc.enable-qc">mysqlnd_qc.enable_qc</link>.
</para>
<para>
<example>
<title>Enabling the plugin (php.ini)</title>
<programlisting role="ini">
<![CDATA[
mysqlnd_qc.enable_qc=1
]]>
</programlisting>
</example>
</para>
</section>
<section xml:id="mysqlnd-qc.quickstart.caching">
<title>Caching queries</title>
<para>
There are four ways to trigger caching of a query.
<itemizedlist>
<listitem>
<simpara>Use of SQL hints on a per query basis</simpara>
</listitem>
<listitem>
<simpara>
User supplied callbacks to decide on a per query basis, for example, using <function>mysqlnd_qc_is_select</function>
</simpara>
</listitem>
<listitem>
<simpara>
<function>mysqlnd_set_cache_condition</function> for rule based automatic per query decisions
</simpara>
</listitem>
<listitem>
<simpara>
<literal><link linkend="mysqlnd-qc.configuration">mysqlnd_qc.cache_by_default = 1</link></literal>
to cache all queries blindly
</simpara>
</listitem>
</itemizedlist>
</para>
<para>
Use of SQL hints and
<literal><link linkend="mysqlnd-qc.configuration">mysqlnd_qc.cache_by_default = 1</link></literal>
are explained below. Please, refer to the function reference on
<function>mysqlnd_qc_is_select</function> for a description of using a callback and,
<function>mysqlnd_qc_set_cache_condition</function> on how to set rules for automatic
caching.
</para>
<para>
A SQL hint is a SQL standards compliant
comment. As a SQL comment it is ignored by the database. A statement is considered
eligible for caching if it either begins with the SQL hint enabling caching
or it is a <literal>SELECT</literal> statement.
</para>
<para>
An individual query which shall be cached must begin with the SQL hint
<literal>/*qc=on*/</literal>. It is recommended to use the PHP constant
<literal><link linkend="mysqlnd-qc.constants">MYSQLND_QC_ENABLE_SWITCH</link></literal>
instead of using the string value.
</para>
<para>
<itemizedlist>
<listitem>
<para>
not eligible for caching and not cached: <literal>INSERT INTO test(id) VALUES (1)</literal>
</para>
</listitem>
<listitem>
<para>
not eligible for caching and not cached: <literal>SHOW ENGINES</literal>
</para>
</listitem>
<listitem>
<para>
eligible for caching but uncached: <literal>SELECT id FROM test</literal>
</para>
</listitem>
<listitem>
<para>
eligible for caching and cached: <literal>/*qc=on*/SELECT id FROM test</literal>
</para>
</listitem>
</itemizedlist>
</para>
<para>
The examples <literal>SELECT</literal> statement string is prefixed with the
<literal><link linkend="mysqlnd-qc.constants">MYSQLND_QC_ENABLE_SWITCH</link></literal>
SQL hint to enable caching of the statement. The SQL hint must be given at
the very beginning of the statement string to enable caching.
</para>
<para>
<example>
<programlisting role="ini">
<![CDATA[
mysqlnd_qc.enable_qc=1
]]>
</programlisting>
<programlisting role="php">
<![CDATA[
<?php
/* Connect, create and populate test table */
$mysqli = new mysqli("host", "user", "password", "schema", "port", "socket");
$mysqli->query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT)");
$mysqli->query("INSERT INTO test(id) VALUES (1), (2)");
/* Will be cached because of the SQL hint */
$start = microtime(true);
$res = $mysqli->query("/*" . MYSQLND_QC_ENABLE_SWITCH . "*/" . "SELECT id FROM test WHERE id = 1");
var_dump($res->fetch_assoc());
$res->free();
printf("Total time uncached query: %.6fs\n", microtime(true) - $start);
/* Cache hit */
$start = microtime(true);
$res = $mysqli->query("/*" . MYSQLND_QC_ENABLE_SWITCH . "*/" . "SELECT id FROM test WHERE id = 1");
var_dump($res->fetch_assoc());
$res->free();
printf("Total time cached query: %.6fs\n", microtime(true) - $start);
?>
]]>
</programlisting>
&examples.outputs.similar;
<screen>
<![CDATA[
array(1) {
["id"]=>
string(1) "1"
}
Total time uncached query: 0.000740s
array(1) {
["id"]=>
string(1) "1"
}
Total time cached query: 0.000098s
]]>
</screen>
</example>
</para>
<para>
If nothing else is configured, as it is the case in the quickstart example,
the plugin will use the built-in <literal>default</literal> storage handler.
The <literal>default</literal> storage handler uses process memory to hold a cache entry.
Depending on the PHP deployment model, a PHP process may serve one or more
web requests. Please, consult the web server manual for details.
Details make no difference for the examples given in the quickstart.
</para>
<para>
The query cache plugin will cache all queries regardless if
the query string begins with the SQL hint which enables caching or not,
if the PHP configuration directive
<literal><link linkend="mysqlnd-qc.configuration">mysqlnd_qc.cache_by_default</link></literal>
is set to <literal>1</literal>. The setting
<literal><link linkend="mysqlnd-qc.configuration">mysqlnd_qc.cache_by_default</link></literal>
is evaluated by the core of the query cache plugins.
Neither the built-in nor user-defined storage handler can overrule the setting.
</para>
<para>
The SQL hint <literal>/*qc=off*/</literal> can be used to disable caching
of individual queries if
<literal><link linkend="mysqlnd-qc.configuration">mysqlnd_qc.cache_by_default = 1</link></literal>
It is recommended to use the PHP constant
<literal><link linkend="mysqlnd-qc.constants">MYSQLND_QC_DISABLE_SWITCH</link></literal>
instead of using the string value.
</para>
<para>
<example>
<programlisting role="ini">
<![CDATA[
mysqlnd_qc.enable_qc=1
mysqlnd_qc.cache_by_default=1
]]>
</programlisting>
<programlisting role="php">
<![CDATA[
<?php
/* Connect, create and populate test table */
$mysqli = new mysqli("host", "user", "password", "schema", "port", "socket");
$mysqli->query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT)");
$mysqli->query("INSERT INTO test(id) VALUES (1), (2)");
/* Will be cached although no SQL hint is present because of mysqlnd_qc.cache_by_default = 1*/
$res = $mysqli->query("SELECT id FROM test WHERE id = 1");
var_dump($res->fetch_assoc());
$res->free();
$mysqli->query("DELETE FROM test WHERE id = 1");
/* Cache hit - no automatic invalidation and still valid! */
$res = $mysqli->query("SELECT id FROM test WHERE id = 1");
var_dump($res->fetch_assoc());
$res->free();
/* Cache miss - query must not be cached because of the SQL hint */
$res = $mysqli->query("/*" . MYSQLND_QC_DISABLE_SWITCH . "*/SELECT id FROM test WHERE id = 1");
var_dump($res->fetch_assoc());
$res->free();
?>
]]>
</programlisting>
&examples.outputs;
<screen>
<![CDATA[
array(1) {
["id"]=>
string(1) "1"
}
array(1) {
["id"]=>
string(1) "1"
}
NULL
]]>
</screen>
</example>
</para>
<para>
PECL/mysqlnd_qc forbids caching of statements for which at least one
column from the statements result set shows no table name in its meta data by default.
This is usually the case for columns originating from SQL functions such as
<literal>NOW()</literal> or <literal>LAST_INSERT_ID()</literal>. The policy
aims to prevent pitfalls if caching by default is used.
</para>
<para>
<example>
<programlisting role="ini">
<![CDATA[
mysqlnd_qc.enable_qc=1
mysqlnd_qc.cache_by_default=1
]]>
</programlisting>
<programlisting role="php">
<![CDATA[
<?php
/* Connect, create and populate test table */
$mysqli = new mysqli("host", "user", "password", "schema", "port", "socket");
$mysqli->query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT)");
$mysqli->query("INSERT INTO test(id) VALUES (1)");
for ($i = 0; $i < 3; $i++) {
$start = microtime(true);
/* Note: statement will not be cached because of NOW() use */
$res = $mysqli->query("SELECT id, NOW() AS _time FROM test");
$row = $res->fetch_assoc();
/* dump results */
var_dump($row);
printf("Total time: %.6fs\n", microtime(true) - $start);
/* pause one second */
sleep(1);
}
?>
]]>
</programlisting>
&examples.outputs.similar;
<screen>
<![CDATA[
array(2) {
["id"]=>
string(1) "1"
["_time"]=>
string(19) "2012-01-11 15:43:10"
}
Total time: 0.000540s
array(2) {
["id"]=>
string(1) "1"
["_time"]=>
string(19) "2012-01-11 15:43:11"
}
Total time: 0.000555s
array(2) {
["id"]=>
string(1) "1"
["_time"]=>
string(19) "2012-01-11 15:43:12"
}
Total time: 0.000549s
]]>
</screen>
</example>
</para>
<para>
It is possible to enable caching for all statements including those
which has columns in their result set for which MySQL reports no table, such as
the statement from the example. Set
<link linkend="ini.mysqlnd-qc.cache-no-table"><literal>mysqlnd_qc.cache_no_table = 1</literal></link>
to enable caching of such statements. Please, note the difference in the
measured times for the above and below examples.
</para>
<para>
<example>
<programlisting role="ini">
<![CDATA[
mysqlnd_qc.enable_qc=1
mysqlnd_qc.cache_by_default=1
mysqlnd_qc.cache_no_table=1
]]>
</programlisting>
<programlisting role="php">
<![CDATA[
<?php
/* Connect, create and populate test table */
$mysqli = new mysqli("host", "user", "password", "schema", "port", "socket");
$mysqli->query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT)");
$mysqli->query("INSERT INTO test(id) VALUES (1)");
for ($i = 0; $i < 3; $i++) {
$start = microtime(true);
/* Note: statement will not be cached because of NOW() use */
$res = $mysqli->query("SELECT id, NOW() AS _time FROM test");
$row = $res->fetch_assoc();
/* dump results */
var_dump($row);
printf("Total time: %.6fs\n", microtime(true) - $start);
/* pause one second */
sleep(1);
}
?>
]]>
</programlisting>
&examples.outputs.similar;
<screen>
<![CDATA[
array(2) {
["id"]=>
string(1) "1"
["_time"]=>
string(19) "2012-01-11 15:47:45"
}
Total time: 0.000546s
array(2) {
["id"]=>
string(1) "1"
["_time"]=>
string(19) "2012-01-11 15:47:45"
}
Total time: 0.000187s
array(2) {
["id"]=>
string(1) "1"
["_time"]=>
string(19) "2012-01-11 15:47:45"
}
Total time: 0.000167s
]]>
</screen>
</example>
</para>
<note>
<para>
Although <link linkend="ini.mysqlnd-qc.cache-no-table"><literal>mysqlnd_qc.cache_no_table = 1</literal></link>
has been created for use with
<literal><link linkend="mysqlnd-qc.configuration">mysqlnd_qc.cache_by_default = 1</link></literal>
it is bound it. The plugin will evaluate the
<link linkend="ini.mysqlnd-qc.cache-no-table"><literal>mysqlnd_qc.cache_no_table</literal></link>
whenever a query is to be cached, no matter whether caching has been enabled using a
SQL hint or any other measure.
</para>
</note>
</section>
<section xml:id="mysqlnd-qc.per-query-ttl" xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Setting the TTL</title>
<para>
The default invalidation strategy of the query cache plugin is Time to Live
(<literal>TTL</literal>). The built-in storage handlers will use the default
<literal>TTL</literal> defined by the PHP configuration value
<literal><link linkend="mysqlnd-qc.configuration">mysqlnd_qc.ttl</link></literal>
unless the query string contains a hint for setting a different
<literal>TTL</literal>. The <literal>TTL</literal> is specified in seconds.
By default cache entries expire after <literal>30</literal> seconds
</para>
<para>
The example sets <literal>mysqlnd_qc.ttl=3</literal> to cache
statements for three seconds by default. Every second it updates
a database table record to hold the current time and executes
a <literal>SELECT</literal> statement to fetch the record from the
database. The <literal>SELECT</literal> statement is cached for
three seconds because it is prefixed with the SQL hint enabling
caching. The output verifies that the query results are taken
from the cache for the duration of three seconds before they
are refreshed.
</para>
<para>
<example>
<programlisting role="ini">
<![CDATA[
mysqlnd_qc.enable_qc=1
mysqlnd_qc.ttl=3
]]>
</programlisting>
<programlisting role="php">
<![CDATA[
<?php
/* Connect, create and populate test table */
$mysqli = new mysqli("host", "user", "password", "schema", "port", "socket");
$mysqli->query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id VARCHAR(255))");
for ($i = 0; $i < 7; $i++) {
/* update DB row */
if (!$mysqli->query("DELETE FROM test") ||
!$mysqli->query("INSERT INTO test(id) VALUES (NOW())"))
/* Of course, a real-life script should do better error handling */
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
/* select latest row but cache results */
$query = "/*" . MYSQLND_QC_ENABLE_SWITCH . "*/";
$query .= "SELECT id AS _time FROM test";
if (!($res = $mysqli->query($query)) ||
!($row = $res->fetch_assoc()))
{
printf("[%d] %s\n", $mysqli->errno, $mysqli->error);
}
$res->free();
printf("Wall time %s - DB row time %s\n", date("H:i:s"), $row['_time']);
/* pause one second */
sleep(1);
}
?>
]]>
</programlisting>
&examples.outputs.similar;
<screen>
<![CDATA[
Wall time 14:55:59 - DB row time 2012-01-11 14:55:59
Wall time 14:56:00 - DB row time 2012-01-11 14:55:59
Wall time 14:56:01 - DB row time 2012-01-11 14:55:59
Wall time 14:56:02 - DB row time 2012-01-11 14:56:02
Wall time 14:56:03 - DB row time 2012-01-11 14:56:02
Wall time 14:56:04 - DB row time 2012-01-11 14:56:02
Wall time 14:56:05 - DB row time 2012-01-11 14:56:05
]]>
</screen>
</example>
</para>
<para>
As can be seen from the example, any <literal>TTL</literal> based cache
can serve stale data. Cache entries are not automatically invalidated,
if underlying data changes. Applications using the default
<literal>TTL</literal> invalidation strategy must be able to work correctly
with stale data.
</para>
<para>
A user-defined cache storage handler can implement any invalidation strategy
to work around this limitation.
</para>
<para>
The default <literal>TTL</literal> can be overruled using the SQL hint
<literal>/*qc_tt=seconds*/</literal>. The SQL hint must be appear immediately
after the SQL hint which enables caching. It is recommended to use the PHP constant
<literal><link linkend="mysqlnd-qc.constants">MYSQLND_QC_TTL_SWITCH</link></literal>
instead of using the string value.
</para>
<para>
<example>
<programlisting role="php">
<![CDATA[
<?php
$start = microtime(true);
/* Connect, create and populate test table */
$mysqli = new mysqli("host", "user", "password", "schema", "port", "socket");
$mysqli->query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT)");
$mysqli->query("INSERT INTO test(id) VALUES (1), (2)");
printf("Default TTL\t: %d seconds\n", ini_get("mysqlnd_qc.ttl"));
/* Will be cached for 2 seconds */
$sql = sprintf("/*%s*//*%s%d*/SELECT id FROM test WHERE id = 1", MYSQLND_QC_ENABLE_SWITCH, MYSQLND_QC_TTL_SWITCH, 2);
$res = $mysqli->query($sql);
var_dump($res->fetch_assoc());
$res->free();
$mysqli->query("DELETE FROM test WHERE id = 1");
sleep(1);
/* Cache hit - no automatic invalidation and still valid! */
$res = $mysqli->query($sql);
var_dump($res->fetch_assoc());
$res->free();
sleep(2);
/* Cache miss - cache entry has expired */
$res = $mysqli->query($sql);
var_dump($res->fetch_assoc());
$res->free();
printf("Script runtime\t: %d seconds\n", microtime(true) - $start);
?>
]]>
</programlisting>
&examples.outputs.similar;
<screen>
<![CDATA[
Default TTL : 30 seconds
array(1) {
["id"]=>
string(1) "1"
}
array(1) {
["id"]=>
string(1) "1"
}
NULL
Script runtime : 3 seconds
]]>
</screen>
</example>
</para>
</section>
<section xml:id="mysqlnd-qc.pattern-based-caching" xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Pattern based caching</title>
<para>
An application has three options for telling PECL/mysqlnd_qc whether a particular
statement shall be used. The most basic approach is to cache all statements by
setting <literal><link linkend="mysqlnd-qc.configuration">
mysqlnd_qc.cache_by_default = 1</link></literal>. This approach is often of little
practical value. But it enables users to make a quick estimation about
the maximum performance gains from caching. An application designed to
use a cache may be able to prefix selected statements with the appropriate SQL
hints. However, altering an applications source code may not always be possible
or desired, for example, to avoid problems with software updates. Therefore,
PECL/mysqlnd_qc allows setting a callback which decides if a query is to be
cached.
</para>
<para>
The callback is installed with the <function>mysqlnd_qc_set_is_select</function>
function. The callback is given the statement string of every statement
inspected by the plugin. Then, the callback can decide whether to cache
the function. The callback is supposed to return &false;
if the statement shall not be cached. A return value of &true;
makes the plugin try to add the statement into the cache. The cache entry
will be given the default TTL (<literal><link linkend="mysqlnd-qc.configuration">
mysqlnd_qc.ttl</link></literal>). If the callback returns
a numerical value it is used as the TTL instead of the global default.
</para>
<para>
<example>
<programlisting role="ini">
<![CDATA[
mysqlnd_qc.enable_qc=1
mysqlnd_qc.collect_statistics=1
]]>
</programlisting>
<programlisting role="php">
<![CDATA[
<?php
/* callback which decides if query is cached */
function is_select($query) {
static $patterns = array(
/* true - use default from mysqlnd_qc.ttl */
"@SELECT\s+.*\s+FROM\s+test@ismU" => true,
/* 3 - use TTL = 3 seconds */
"@SELECT\s+.*\s+FROM\s+news@ismU" => 3
);
/* check if query does match pattern */
foreach ($patterns as $pattern => $ttl) {
if (preg_match($pattern, $query)) {
printf("is_select(%45s): cache\n", $query);
return $ttl;
}
}
printf("is_select(%45s): do not cache\n", $query);
return false;
}
/* install callback */
mysqlnd_qc_set_is_select("is_select");
/* Connect, create and populate test table */
$mysqli = new mysqli("host", "user", "password", "schema", "port", "socket");
$mysqli->query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT)");
$mysqli->query("INSERT INTO test(id) VALUES (1), (2), (3)");
/* cache put */
$mysqli->query("SELECT id FROM test WHERE id = 1");
/* cache hit */
$mysqli->query("SELECT id FROM test WHERE id = 1");
/* cache put */
$mysqli->query("SELECT * FROM test");
$stats = mysqlnd_qc_get_core_stats();
printf("Cache put: %d\n", $stats['cache_put']);
printf("Cache hit: %d\n", $stats['cache_hit']);
?>
]]>
</programlisting>
&examples.outputs.similar;
<screen>
<![CDATA[
is_select( DROP TABLE IF EXISTS test): do not cache
is_select( CREATE TABLE test(id INT)): do not cache
is_select( INSERT INTO test(id) VALUES (1), (2), (3)): do not cache
is_select( SELECT id FROM test WHERE id = 1): cache
is_select( SELECT id FROM test WHERE id = 1): cache
is_select( SELECT * FROM test): cache
Cache put: 2
Cache hit: 1
]]>
</screen>
</example>
</para>
<para>
The examples callback tests if a statement string matches a pattern.
If this is the case, it either returns &true; to cache
the statement using the global default TTL or an alternative TTL.
</para>
<para>
To minimize application changes the callback can put into and registered
in an auto prepend file.
</para>
</section>
<section xml:id="mysqlnd-qc.slam-defense" xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Slam defense</title>
<para>
A badly designed cache can do more harm than good. In the worst case a cache
can increase database server load instead of minimizing it. An overload situation
can occur if a highly shared cache entry expires (cache stampeding).
</para>
<para>
Cache entries are shared and reused to a different degree depending on
the storage used. The default storage handler stores cache entries in process memory.
Thus, a cache entry can be reused for the life-span of a process. Other PHP
processes cannot access it. If Memcache is used, a cache entry can be shared
among multiple PHP processes and even among multiple machines, depending on the
set up being used.
</para>
<para>
If a highly shared cache entry stored, for example, in Memcache expires, many
clients gets a cache miss. Many client requests can no longer be served from
the cache but try to run the underlying query on the database server. Until
the cache entry is refreshed, more and more clients contact the database server.
In the worst case, a total lost of service is the result.
</para>
<para>
The overload can be avoided using a storage handler which limits the reuse of cache
entries to few clients. Then, at the average, its likely that only a limited number
of clients will try to refresh a cache entry concurrently.
</para>
<para>
Additionally, the built-in slam defense mechanism can and should be used. If
slam defense is activated an expired cache entry is given an extended life time.
The first client getting a cache miss for the expired cache entry tries to
refresh the cache entry within the extended life time. All other clients requesting
the cache entry are temporarily served from the cache although the original
<literal>TTL</literal> of the cache entry has expired. The other clients will
not experience a cache miss before the extended life time is over.
</para>
<para>
<example>
<programlisting role="ini">
<![CDATA[
mysqlnd_qc.slam_defense=1
mysqlnd_qc.slam_defense_ttl=1
]]>
</programlisting>
</example>
</para>
<para>
The slam defense mechanism is enabled with the PHP configuration directive
<link linkend="ini.mysqlnd-qc.slam-defense"><literal>mysqlnd_qc.slam_defense</literal></link>.
The extended life time of a cache entry is set with
<link linkend="ini.mysqlnd-qc.slam-defense-ttl"><literal>mysqlnd_qc.slam_defense_ttl</literal></link>.
</para>
<para>
The function
<function>mysqlnd_qc_get_core_stats</function> returns an array of
statistics. The statistics <literal>slam_stale_refresh</literal> and
<literal>slam_stale_hit</literal> are incremented if slam defense takes place.
</para>
<para>
It is not possible to give a one-fits-all recommendation on the slam defense
configuration. Users are advised to monitor and test their setup and derive
settings accordingly.
</para>
</section>
<section xml:id="mysqlnd-qc.cache-candidates" xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Finding cache candidates</title>
<para>
A statement should be considered for caching if it is executed often and has
a long run time. Cache candidates are found by creating a list of statements
sorted by the product of the number of executions multiplied by the
statements run time. The function
<function>mysqlnd_qc_get_query_trace_log</function>
returns a query log which help with the task.
</para>
<para>
Collecting a query trace is a slow operation. Thus, it is disabled by default.
The PHP configuration directive
<link linkend="ini.mysqlnd-qc.collect-query-trace"><literal>mysqlnd_qc.collect_query_trace</literal></link>
is used to enable it. The functions trace contains one entry for every
query issued before the function is called.
</para>
<para>
<example>
<programlisting role="ini">
<![CDATA[
mysqlnd_qc.enable_qc=1
mysqlnd_qc.collect_query_trace=1
]]>
</programlisting>
<programlisting role="php">
<![CDATA[
<?php
/* connect to MySQL */
$mysqli = new mysqli("host", "user", "password", "schema", "port", "socket");
/* dummy queries to fill the query trace */
for ($i = 0; $i < 2; $i++) {
$res = $mysqli->query("SELECT 1 AS _one FROM DUAL");
$res->free();
}
/* dump trace */
var_dump(mysqlnd_qc_get_query_trace_log());
?>
]]>
</programlisting>
&examples.outputs;
<screen>
<![CDATA[
array(2) {
[0]=>
array(8) {
["query"]=>
string(26) "SELECT 1 AS _one FROM DUAL"
["origin"]=>
string(102) "#0 qc.php(7): mysqli->query('SELECT 1 AS _on...')
#1 {main}"
["run_time"]=>
int(0)
["store_time"]=>
int(25)
["eligible_for_caching"]=>
bool(false)
["no_table"]=>
bool(false)
["was_added"]=>
bool(false)
["was_already_in_cache"]=>
bool(false)
}
[1]=>
array(8) {
["query"]=>
string(26) "SELECT 1 AS _one FROM DUAL"
["origin"]=>
string(102) "#0 qc.php(7): mysqli->query('SELECT 1 AS _on...')
#1 {main}"
["run_time"]=>
int(0)
["store_time"]=>
int(8)
["eligible_for_caching"]=>
bool(false)
["no_table"]=>
bool(false)
["was_added"]=>
bool(false)
["was_already_in_cache"]=>
bool(false)
}
}
]]>
</screen>
</example>
</para>
<para>
Assorted information is given in the trace. Among them
timings and the origin of the query call. The origin property
holds a code backtrace to identify the source of the query.
The depth of the backtrace can be limited with
the PHP configuration directive
<link linkend="ini.mysqlnd-qc.query-trace-bt-depth"><literal>mysqlnd_qc.query_trace_bt_depth</literal></link>.
The default depth is <literal>3</literal>.
</para>
<para>
<example>
<programlisting role="ini">
<![CDATA[
mysqlnd_qc.enable_qc=1
mysqlnd_qc.collect_query_trace=1
]]>
</programlisting>
<programlisting role="php">
<![CDATA[
<?php
/* connect to MySQL */
$mysqli = new mysqli("host", "user", "password", "schema", "port", "socket");
$mysqli->query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT)");
$mysqli->query("INSERT INTO test(id) VALUES (1), (2), (3)");
/* dummy queries to fill the query trace */
for ($i = 0; $i < 3; $i++) {
$res = $mysqli->query("SELECT id FROM test WHERE id = " . $mysqli->real_escape_string($i));
$res->free();
}
$trace = mysqlnd_qc_get_query_trace_log();
$summary = array();
foreach ($trace as $entry) {
if (!isset($summary[$entry['query']])) {
$summary[$entry['query']] = array(
"executions" => 1,
"time" => $entry['run_time'] + $entry['store_time'],
);
} else {
$summary[$entry['query']]['executions']++;
$summary[$entry['query']]['time'] += $entry['run_time'] + $entry['store_time'];
}
}
foreach ($summary as $query => $details) {
printf("%45s: %5dms (%dx)\n",
$query, $details['time'], $details['executions']);
}
?>
]]>
</programlisting>
&examples.outputs.similar;
<screen>
<![CDATA[
DROP TABLE IF EXISTS test: 0ms (1x)
CREATE TABLE test(id INT): 0ms (1x)
INSERT INTO test(id) VALUES (1), (2), (3): 0ms (1x)
SELECT id FROM test WHERE id = 0: 25ms (1x)
SELECT id FROM test WHERE id = 1: 10ms (1x)
SELECT id FROM test WHERE id = 2: 9ms (1x)
]]>
</screen>
</example>
</para>
</section>
<section xml:id="mysqlnd-qc.cache-efficiency" xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Measuring cache efficiency</title>
<para>
PECL/mysqlnd_qc offers three ways to measure the cache efficiency.
The function
<function>mysqlnd_qc_get_normalized_query_trace_log</function>
returns statistics aggregated by the normalized query string,
<function>mysqlnd_qc_get_cache_info</function>
gives storage handler specific information which includes a list
of all cached items, depending on the storage handler. Additionally, the
core of PECL/mysqlnd_qc collects high-level summary statistics aggregated
per PHP process. The high-level statistics are returned by
<function>mysqlnd_qc_get_core_stats</function>.
</para>
<para>
The functions
<function>mysqlnd_qc_get_normalized_query_trace_log</function>
and
<function>mysqlnd_qc_get_core_stats</function>
will not collect data unless data collection has been
enabled through their corresponding PHP configuration directives. Data collection
is disabled by default for performance considerations. It is configurable with the
<link linkend="ini.mysqlnd-qc.time-statistics">>mysqlnd_qc.time_statistics</link>
option, which determines if timing information should be collected.
Collection of time statistics is enabled by default
but only performed if data collection as such has been enabled.
Recording time statistics causes extra system calls. In most cases,
the benefit of the monitoring outweighs any potential performance penalty of
the additional system calls.
</para>
<para>
<example>
<programlisting role="ini">
<![CDATA[
mysqlnd_qc.enable_qc=1
mysqlnd_qc.collect_statistics=1
]]>
</programlisting>
<programlisting role="php">
<![CDATA[
<?php
/* connect to MySQL */
$mysqli = new mysqli("host", "user", "password", "schema", "port", "socket");
$mysqli->query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT)");
$mysqli->query("INSERT INTO test(id) VALUES (1), (2), (3)");
/* dummy queries */
for ($i = 1; $i <= 4; $i++) {
$query = sprintf("/*%s*/SELECT id FROM test WHERE id = %d", MYSQLND_QC_ENABLE_SWITCH, $i % 2);
$res = $mysqli->query($query);
$res->free();
}
var_dump(mysqlnd_qc_get_core_stats());
?>
]]>
</programlisting>
&examples.outputs.similar;
<screen>
<![CDATA[
array(26) {
["cache_hit"]=>
string(1) "2"
["cache_miss"]=>
string(1) "2"
["cache_put"]=>
string(1) "2"
["query_should_cache"]=>
string(1) "4"
["query_should_not_cache"]=>
string(1) "3"
["query_not_cached"]=>
string(1) "3"
["query_could_cache"]=>
string(1) "4"
["query_found_in_cache"]=>
string(1) "2"
["query_uncached_other"]=>
string(1) "0"
["query_uncached_no_table"]=>
string(1) "0"
["query_uncached_no_result"]=>
string(1) "0"
["query_uncached_use_result"]=>
string(1) "0"
["query_aggr_run_time_cache_hit"]=>
string(2) "28"
["query_aggr_run_time_cache_put"]=>
string(3) "900"
["query_aggr_run_time_total"]=>
string(3) "928"
["query_aggr_store_time_cache_hit"]=>
string(2) "14"
["query_aggr_store_time_cache_put"]=>
string(2) "40"
["query_aggr_store_time_total"]=>
string(2) "54"
["receive_bytes_recorded"]=>
string(3) "136"
["receive_bytes_replayed"]=>
string(3) "136"
["send_bytes_recorded"]=>
string(2) "84"
["send_bytes_replayed"]=>
string(2) "84"
["slam_stale_refresh"]=>
string(1) "0"
["slam_stale_hit"]=>
string(1) "0"
["request_counter"]=>
int(1)
["process_hash"]=>
int(1929695233)
}
]]>
</screen>
</example>
</para>
<para>
For a quick overview, call
<function>mysqlnd_qc_get_core_stats</function>. It delivers
cache usage, cache timing and traffic related statistics. Values are aggregated
on a per process basis for all queries issued by any PHP MySQL API call.
</para>
<para>
Some storage handler, such as the default handler, can report cache entries,
statistics related to the entries and meta data for the underlying query through the
<function>mysqlnd_qc_get_cache_info</function>
function. Please note, that the information returned depends
on the storage handler. Values are aggregated on a per process basis.
</para>
<para>
<example>
<programlisting role="ini">
<![CDATA[
mysqlnd_qc.enable_qc=1
]]>
</programlisting>
<programlisting role="php">
<![CDATA[
<?php
/* connect to MySQL */
$mysqli = new mysqli("host", "user", "password", "schema", "port", "socket");
$mysqli->query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT)");
$mysqli->query("INSERT INTO test(id) VALUES (1), (2), (3)");
/* dummy queries to fill the query trace */
for ($i = 1; $i <= 4; $i++) {
$query = sprintf("/*%s*/SELECT id FROM test WHERE id = %d", MYSQLND_QC_ENABLE_SWITCH, $i % 2);
$res = $mysqli->query($query);
$res->free();
}
var_dump(mysqlnd_qc_get_cache_info());
?>
]]>
</programlisting>
&examples.outputs.similar;
<screen>
<![CDATA[
array(4) {
["num_entries"]=>
int(2)
["handler"]=>
string(7) "default"
["handler_version"]=>
string(5) "1.0.0"
["data"]=>
array(2) {
["Localhost via UNIX socket
3306
root
test|/*qc=on*/SELECT id FROM test WHERE id = 1"]=>
array(2) {
["statistics"]=>
array(11) {
["rows"]=>
int(1)
["stored_size"]=>
int(71)
["cache_hits"]=>
int(1)
["run_time"]=>
int(391)
["store_time"]=>
int(27)
["min_run_time"]=>
int(16)
["max_run_time"]=>
int(16)
["min_store_time"]=>
int(8)
["max_store_time"]=>
int(8)
["avg_run_time"]=>
int(8)
["avg_store_time"]=>
int(4)
}
["metadata"]=>
array(1) {
[0]=>
array(8) {
["name"]=>
string(2) "id"
["orig_name"]=>
string(2) "id"
["table"]=>
string(4) "test"
["orig_table"]=>
string(4) "test"
["db"]=>
string(4) "test"
["max_length"]=>
int(1)
["length"]=>
int(11)
["type"]=>
int(3)
}
}
}
["Localhost via UNIX socket
3306
root
test|/*qc=on*/SELECT id FROM test WHERE id = 0"]=>
array(2) {
["statistics"]=>
array(11) {
["rows"]=>
int(0)
["stored_size"]=>
int(65)
["cache_hits"]=>
int(1)
["run_time"]=>
int(299)
["store_time"]=>
int(13)
["min_run_time"]=>
int(11)
["max_run_time"]=>
int(11)
["min_store_time"]=>
int(6)
["max_store_time"]=>
int(6)
["avg_run_time"]=>
int(5)
["avg_store_time"]=>
int(3)
}
["metadata"]=>
array(1) {
[0]=>
array(8) {
["name"]=>
string(2) "id"
["orig_name"]=>
string(2) "id"
["table"]=>
string(4) "test"
["orig_table"]=>
string(4) "test"
["db"]=>
string(4) "test"
["max_length"]=>
int(0)
["length"]=>
int(11)
["type"]=>
int(3)
}
}
}
}
}
]]>
</screen>
</example>
</para>
<para>
It is possible to further break down the granularity of statistics
to the level of the normalized statement string.
The normalized statement string is the statements string with all parameters
replaced with question marks. For example, the two statements
<literal>SELECT id FROM test WHERE id = 0</literal> and
<literal>SELECT id FROM test WHERE id = 1</literal> are normalized into
<literal>SELECT id FROM test WHERE id = ?</literal>. Their both
statistics are aggregated into one entry for
<literal>SELECT id FROM test WHERE id = ?</literal>.
</para>
<para>
<example>
<programlisting role="ini">
<![CDATA[
mysqlnd_qc.enable_qc=1
mysqlnd_qc.collect_normalized_query_trace=1
]]>
</programlisting>
<programlisting role="php">
<![CDATA[
<?php
/* connect to MySQL */
$mysqli = new mysqli("host", "user", "password", "schema", "port", "socket");
$mysqli->query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT)");
$mysqli->query("INSERT INTO test(id) VALUES (1), (2), (3)");
/* dummy queries to fill the query trace */
for ($i = 1; $i <= 4; $i++) {
$query = sprintf("/*%s*/SELECT id FROM test WHERE id = %d", MYSQLND_QC_ENABLE_SWITCH, $i % 2);
$res = $mysqli->query($query);
$res->free();
}
var_dump(mysqlnd_qc_get_normalized_query_trace_log());
?>
]]>
</programlisting>
&examples.outputs.similar;
<screen>
<![CDATA[
array(4) {
[0]=>
array(9) {
["query"]=>
string(25) "DROP TABLE IF EXISTS test"
["occurences"]=>
int(0)
["eligible_for_caching"]=>
bool(false)
["avg_run_time"]=>
int(0)
["min_run_time"]=>
int(0)
["max_run_time"]=>
int(0)
["avg_store_time"]=>
int(0)
["min_store_time"]=>
int(0)
["max_store_time"]=>
int(0)
}
[1]=>
array(9) {
["query"]=>
string(27) "CREATE TABLE test (id INT )"
["occurences"]=>
int(0)
["eligible_for_caching"]=>
bool(false)
["avg_run_time"]=>
int(0)
["min_run_time"]=>
int(0)
["max_run_time"]=>
int(0)
["avg_store_time"]=>
int(0)
["min_store_time"]=>
int(0)
["max_store_time"]=>
int(0)
}
[2]=>
array(9) {
["query"]=>
string(46) "INSERT INTO test (id ) VALUES (? ), (? ), (? )"
["occurences"]=>
int(0)
["eligible_for_caching"]=>
bool(false)
["avg_run_time"]=>
int(0)
["min_run_time"]=>
int(0)
["max_run_time"]=>
int(0)
["avg_store_time"]=>
int(0)
["min_store_time"]=>
int(0)
["max_store_time"]=>
int(0)
}
[3]=>
array(9) {
["query"]=>
string(31) "SELECT id FROM test WHERE id =?"
["occurences"]=>
int(4)
["eligible_for_caching"]=>
bool(true)
["avg_run_time"]=>
int(179)
["min_run_time"]=>
int(11)
["max_run_time"]=>
int(393)
["avg_store_time"]=>
int(12)
["min_store_time"]=>
int(7)
["max_store_time"]=>
int(25)
}
}
]]>
</screen>
</example>
</para>
<para>
The source distribution of PECL/mysqlnd_qc contains a directory
<literal>web/</literal> in which web based monitoring
scripts can be found which give an example how to write a cache monitor.
Please, follow the instructions given in the source.
</para>
<para>
Since PECL/mysqlnd_qc 1.1.0 it is possible to write statistics into a
log file. Please, see <literal><link linkend="ini.mysqlnd-qc.collect-statistics-log-file">
mysqlnd_qc.collect_statistics_log_file</link></literal>.
</para>
</section>
<section xml:id="mysqlnd-qc.set-user-handlers" xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Beyond TTL: user-defined storage</title>
<para>
The query cache plugin supports the use of user-defined storage handler.
User-defined storage handler can use arbitrarily complex invalidation
algorithms and support arbitrary storage media.
</para>
<para>
All user-defined storage handlers have to provide a certain interface.
The functions of the user-defined storage handler will be called by the
core of the cache plugin. The necessary interface consists of seven
public functions. Both procedural and object oriented user-defined storage
handler must implement the same set of functions.
</para>
<para>
Please check the example for details.
</para>
<para>
<example>
<programlisting role="php">
<![CDATA[
<?php
/* Enable default caching of all statements */
ini_set("mysqlnd_qc.cache_by_default", 1);
/* Procedural user defined storage handler functions */
$__cache = array();
function get_hash($host_info, $port, $user, $db, $query) {
global $__cache;
printf("\t%s(%d)\n", __FUNCTION__, func_num_args());
return md5(sprintf("%s%s%s%s%s", $host_info, $port, $user, $db, $query));
}
function find_query_in_cache($key) {
global $__cache;
printf("\t%s(%d)\n", __FUNCTION__, func_num_args());
if (isset($__cache[$key])) {
$tmp = $__cache[$key];
if ($tmp["valid_until"] < time()) {
unset($__cache[$key]);
$ret = NULL;
} else {
$ret = $__cache[$key]["data"];
}
} else {
$ret = NULL;
}
return $ret;
}
function return_to_cache($key) {
/*
Called on cache hit after cached data has been processed,
may be used for reference counting
*/
printf("\t%s(%d)\n", __FUNCTION__, func_num_args());
}
function add_query_to_cache_if_not_exists($key, $data, $ttl, $run_time, $store_time, $row_count) {
global $__cache;
printf("\t%s(%d)\n", __FUNCTION__, func_num_args());
$__cache[$key] = array(
"data" => $data,
"row_count" => $row_count,
"valid_until" => time() + $ttl,
"hits" => 0,
"run_time" => $run_time,
"store_time" => $store_time,
"cached_run_times" => array(),
"cached_store_times" => array(),
);
return TRUE;
}
function query_is_select($query) {
printf("\t%s('%s'): ", __FUNCTION__, $query);
$ret = FALSE;
if (stristr($query, "SELECT") !== FALSE) {
/* cache for 5 seconds */
$ret = 5;
}
printf("%s\n", (FALSE === $ret) ? "FALSE" : $ret);
return $ret;
}
function update_query_run_time_stats($key, $run_time, $store_time) {
global $__cache;
printf("\t%s(%d)\n", __FUNCTION__, func_num_args());
if (isset($__cache[$key])) {
$__cache[$key]['hits']++;
$__cache[$key]["cached_run_times"][] = $run_time;
$__cache[$key]["cached_store_times"][] = $store_time;
}
}
function get_stats($key = NULL) {
global $__cache;
printf("\t%s(%d)\n", __FUNCTION__, func_num_args());
if ($key && isset($__cache[$key])) {
$stats = $__cache[$key];
} else {
$stats = array();
foreach ($__cache as $key => $details) {
$stats[$key] = array(
'hits' => $details['hits'],
'bytes' => strlen($details['data']),
'uncached_run_time' => $details['run_time'],
'cached_run_time' => (count($details['cached_run_times']))
? array_sum($details['cached_run_times']) / count($details['cached_run_times'])
: 0,
);
}
}
return $stats;
}
function clear_cache() {
global $__cache;
printf("\t%s(%d)\n", __FUNCTION__, func_num_args());
$__cache = array();
return TRUE;
}
/* Install procedural user-defined storage handler */
if (!mysqlnd_qc_set_user_handlers("get_hash", "find_query_in_cache",
"return_to_cache", "add_query_to_cache_if_not_exists",
"query_is_select", "update_query_run_time_stats", "get_stats", "clear_cache")) {
printf("Failed to install user-defined storage handler\n");
}
/* Connect, create and populate test table */
$mysqli = new mysqli("host", "user", "password", "schema", "port", "socket");
$mysqli->query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT)");
$mysqli->query("INSERT INTO test(id) VALUES (1), (2)");
printf("\nCache put/cache miss\n");
$res = $mysqli->query("SELECT id FROM test WHERE id = 1");
var_dump($res->fetch_assoc());
$res->free();
/* Delete record to verify we get our data from the cache */
$mysqli->query("DELETE FROM test WHERE id = 1");
printf("\nCache hit\n");
$res = $mysqli->query("SELECT id FROM test WHERE id = 1");
var_dump($res->fetch_assoc());
$res->free();
printf("\nDisplay cache statistics\n");
var_dump(mysqlnd_qc_get_cache_info());
printf("\nFlushing cache, cache put/cache miss");
var_dump(mysqlnd_qc_clear_cache());
$res = $mysqli->query("SELECT id FROM test WHERE id = 1");
var_dump($res->fetch_assoc());
$res->free();
?>
]]>
</programlisting>
&examples.outputs.similar;
<screen>
<![CDATA[
query_is_select('DROP TABLE IF EXISTS test'): FALSE
query_is_select('CREATE TABLE test(id INT)'): FALSE
query_is_select('INSERT INTO test(id) VALUES (1), (2)'): FALSE
Cache put/cache miss
query_is_select('SELECT id FROM test WHERE id = 1'): 5
get_hash(5)
find_query_in_cache(1)
add_query_to_cache_if_not_exists(6)
array(1) {
["id"]=>
string(1) "1"
}
query_is_select('DELETE FROM test WHERE id = 1'): FALSE
Cache hit
query_is_select('SELECT id FROM test WHERE id = 1'): 5
get_hash(5)
find_query_in_cache(1)
return_to_cache(1)
update_query_run_time_stats(3)
array(1) {
["id"]=>
string(1) "1"
}
Display cache statistics
get_stats(0)
array(4) {
["num_entries"]=>
int(1)
["handler"]=>
string(4) "user"
["handler_version"]=>
string(5) "1.0.0"
["data"]=>
array(1) {
["18683c177dc89bb352b29965d112fdaa"]=>
array(4) {
["hits"]=>
int(1)
["bytes"]=>
int(71)
["uncached_run_time"]=>
int(398)
["cached_run_time"]=>
int(4)
}
}
}
Flushing cache, cache put/cache miss clear_cache(0)
bool(true)
query_is_select('SELECT id FROM test WHERE id = 1'): 5
get_hash(5)
find_query_in_cache(1)
add_query_to_cache_if_not_exists(6)
NULL
]]>
</screen>
</example>
</para>
</section>
</chapter>
<!-- Keep this comment at the end of the file
Local variables:
mode: sgml
sgml-omittag:t
sgml-shorttag:t
sgml-minimize-attributes:nil
sgml-always-quote-attributes:t
sgml-indent-step:1
sgml-indent-data:t
indent-tabs-mode:nil
sgml-parent-document:nil
sgml-default-dtd-file:"~/.phpdoc/manual.ced"
sgml-exposed-tags:nil
sgml-local-catalogs:nil
sgml-local-ecat-files:nil
End:
vim600: syn=xml fen fdm=syntax fdl=2 si
vim: et tw=78 syn=sgml
vi: ts=1 sw=1
-->