&reftitle.examples;
Basic usage
The Query Cache plugin supports caching of queries issued by the following user API calls:
mysqli
mysqli_querymysqli_real_query +
mysqli_store_result
PDO_MYSQL
PDO::query if
PDO::ATTR_EMULATE_PREPARES = 1 (default setting)
mysql
mysql_query
A query which shall be cached must begin with the SQL hint
/*qc=on*/. It is recommended to use the PHP constant
MYSQLND_QC_ENABLE_SWITCH
instead of using the string value.
uncached:
SELECT id FROM test
cached:
/*qc=on*/SELECT id FROM test
Example using the most advanced PHP MySQL API, which is
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: cache put and cache_miss */
$res = $mysqli->query("/*" . MYSQLND_QC_ENABLE_SWITCH . "*/" . "SELECT id FROM test WHERE id = 1");
var_dump($res->fetch_assoc());
$res->free();
/* Will NOT be cached and will NOT hit the cache: no SQL hint */
$res = $mysqli->query("SELECT id FROM test WHERE id = 2");
var_dump($res->fetch_assoc());
$res->free();
/* Display cache statistics */
$stats = mysqlnd_qc_get_core_stats();
printf("Cache hit\t: %d\n", $stats['cache_hit']);
printf("Cache miss\t: %d\n", $stats['cache_miss']);
printf("Cache put\t: %d\n", $stats['cache_put']);
?>
]]>
&examples.outputs;
string(1) "1"
}
array(1) {
["id"]=>
string(1) "2"
}
Cache hit : 0
Cache miss : 1
Cache put : 1
]]>
The default invalidation strategy of the cache is Time-to-Live (
TTL). Cache entries are valid for a certain duration.
The default duration is set by the PHP configuration directive
mysqlnd_qc.tll
Setting the TTL
The default invalidation strategy of the query cache plugin is Time-to-Live (
TTL). The built-in storage handler will use the default
TTL defined by the PHP configuration value
mysqlnd_qc.ttl
unless the query string contains
a hint for setting a different
TTL. The
TTL is specified in seconds.
Any
TTL based cache can serve stale data. Cache entries
are not automatically invalidated, if underlying data changes.
User-defined cache storage handler can implement any invalidation strategy
to work around this limitation.
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 because of the SQL hint */
$res = $mysqli->query("/*" . MYSQLND_QC_ENABLE_SWITCH . "*/" . "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! */
$res = $mysqli->query("/*" . MYSQLND_QC_ENABLE_SWITCH . "*/" . "SELECT id FROM test WHERE id = 1");
var_dump($res->fetch_assoc());
$res->free();
sleep(ini_get("mysqlnd_qc.ttl"));
/* Cache miss - cache entry has expired */
$res = $mysqli->query("/*" . MYSQLND_QC_ENABLE_SWITCH . "*/" . "SELECT id FROM test WHERE id = 1");
var_dump($res->fetch_assoc());
$res->free();
?>
]]>
&examples.outputs;
string(1) "1"
}
array(1) {
["id"]=>
string(1) "1"
}
NULL
]]>
The default
TTL can be overruled using the SQL hint
/*qc_tt=seconds*/. The SQL hint must be appear immediately
after the SQL hint which enables caching. It is recommended to use
the PHP constant
MYSQLND_QC_TTL_SWITCH
instead of using the string value.
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);
?>
]]>
&examples.outputs;
string(1) "1"
}
array(1) {
["id"]=>
string(1) "1"
}
NULL
Script runtime : 3 seconds
]]>
Cache by default
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
mysqlnd_qc.cache_by_default
is set to
1.
The setting
mysqlnd_qc.cache_by_default
is evaluated by the core of the query cache plugins.
Neither the built-in nor user-defined storage handler can overrule the setting.
The SQL hint
/*qc=off*/ can be used to disable caching
of individual queries if
mysqlnd_qc.cache_by_default = 1
It is recommended to use the PHP constant
MYSQLND_QC_DISABLE_SWITCH
instead of using the string value.
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 or served from cache 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();
?>
]]>
&examples.outputs;
string(1) "1"
}
array(1) {
["id"]=>
string(1) "1"
}
NULL
]]>
Procedural user-defined storage handler
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.
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.
Please check the example for details.
$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();
?>
]]>
&examples.outputs;
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
]]>