From 4adaafa28e910b045f4768d9c9f256d491892b03 Mon Sep 17 00:00:00 2001 From: Ulf Wendel Date: Wed, 11 Jan 2012 15:08:51 +0000 Subject: [PATCH] Reworking and extending the quick start and examples. Aligning things a bit with PELC/mysqlnd_ms git-svn-id: https://svn.php.net/repository/phpdoc/en/trunk@322071 c90b9560-bf6c-de11-be94-00142212c4b1 --- reference/mysqlnd_qc/book.xml | 38 +- reference/mysqlnd_qc/quickstart.xml | 922 ++++++++++++++++++++++++++++ reference/mysqlnd_qc/usage.xml | 608 ------------------ 3 files changed, 931 insertions(+), 637 deletions(-) create mode 100644 reference/mysqlnd_qc/quickstart.xml delete mode 100644 reference/mysqlnd_qc/usage.xml diff --git a/reference/mysqlnd_qc/book.xml b/reference/mysqlnd_qc/book.xml index 649a9dc663..265d613eaf 100644 --- a/reference/mysqlnd_qc/book.xml +++ b/reference/mysqlnd_qc/book.xml @@ -137,44 +137,24 @@ -
- Architecture +
+ On the name - The query cache 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 - mysqlnd plugin to replace selected - mysqlnd C methods. - - - At PHP run time it proxies queries send from - mysqlnd (PHP) to the MySQL server. If a query string starts with the SQL hint - (/*qc=on*/) to enable caching of it and the query - is not cached (cache miss), the query cache plugin will record the - raw wire protocol data send from MySQL to PHP to answer the query. - The query cache records the wire protocol data in its cache - and replays it, if still valid, on a cache hit. - - - Note that the query cache does not hold decoded result sets consisting - of - zvals (C struct representing a PHP variable). - It stores the raw wire data of the MySQL client server protocol. - In case of a cache hits, - mysqlnd still needs - to decode the cached raw wire data into PHP variables before passing - the result to the user space. - This approach has one major advantage: simplicity. Furthermore this - approach eliminates the need for serializing data for cache storage. + The shortcut mysqlnd_qc + stands for mysqlnd query cache plugin. The name + was chosen for a quick-and-dirty proof-of-concept. In the beginning + the developers did not expect to continue using the code base. + Sometimes PECL/mysqlnd_qc has also been called + client-side query result set cache.
+ &reference.mysqlnd-qc.quickstart; &reference.mysqlnd-qc.setup; &reference.mysqlnd-qc.constants; - &reference.mysqlnd-qc.usage; &reference.mysqlnd-qc.reference; &reference.mysqlnd-qc.changes; diff --git a/reference/mysqlnd_qc/quickstart.xml b/reference/mysqlnd_qc/quickstart.xml new file mode 100644 index 0000000000..110d349a44 --- /dev/null +++ b/reference/mysqlnd_qc/quickstart.xml @@ -0,0 +1,922 @@ + + + + + Quickstart and Examples + + The mysqlnd query cache plugin is easy to use. + This quickstart will demo typical use-cases, and provide practical advice on getting + started. + + + 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. + + + Most of the examples use the mysqli 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 + mysqlnd library. + + +
+ Architecture and Concepts + + 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 + mysqlnd plugin to replace selected + mysqlnd C methods. Hereby, it can change the behaviour of any + PHP MySQL extension (mysqli, + PDO_MYSQL, + mysql) 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 + mysqlnd plugin API description + for a discussion of the advantages of the plugin architecture and + a comparison with proxy based solutions. + + + Transparent to use + + + At PHP run time PECL/mysqlnd_qc can proxy queries send from PHP + (mysqlnd) 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. + + + 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 + (/*qc=on*/). The plugin is capable of caching any + query isssued by calling appropriate API calls of any of the existing + PHP MySQL extensions. + + + Flexible storage: various storage handler + + + Various storage handler are supported to offer different scopes for cache + entries. Different scopes allow for different degrees in sharing cache + entries among clients. + + + + + default (built-in): process memory, scope: process, one or more web requests depending on PHP deployment model used + + + APC: shared memory, scope: single server, multiple web requests + + + SQLite: memory or file, scope: single server, multiple web requests + + + MEMCACHE: main memory, scope: single or multiple server, multiple web requests + + + user (built-in): user-defined - any, scope: user-defined - any + + + + + Support for the APC, SQLite and + MEMCACHE storage handler has to be enabled at compile time. The + default and user 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. + + + Built-in slam defence to avoid overloading + + + 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 defence 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. + + + Unique approach to caching + + + 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 + storing in a persistent cache and unserialized when fetching. The mysqlnd + query cache stores the raw wire protocol data send from MySQL to PHP in its cache + and replayes 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 + serizalize into a PHP variable first and deserializing the PHP variable for storing + in the cache again. + +
+ +
+ Setup + + The plugin is implemented as a PHP extension. See also the + installation instructions to + install the + PECL/mysqlnd_qc extension. + + + Compile or configure the PHP MySQL extension (mysqli, + PDO_MYSQL, + mysql) that you plan to use with support + for the mysqlnd 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. + + + Then, load the extension into PHP and activate the plugin in the PHP configuration + file using the PHP configuration directive named + mysqlnd_qc.enable_qc. + + + + Enabling the plugin (php.ini) + + + + + +
+ +
+ Caching queries + + The plugin can either cache all statements by default or, only those that begin + with an SQL hint to enable caching. A SQL hint is a SQL standards compliant + comment. As a SQL comment it is ignored by the database. A statement is considered + eligable for caching if it either begins with the SQL hint enabling caching + or it is a SELECT statement. + + + An individual 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. + + + + + + not eliable for caching and not cached: INSERT INTO test(id) VALUES (1) + + + + + not eliable for caching and not cached: SHOW ENGINES + + + + + eliable for caching but uncached: SELECT id FROM test + + + + + eliable for caching and cached: /*qc=on*/SELECT id FROM test + + + + + + The examples SELECT statement string is prefixed with the + MYSQLND_QC_ENABLE_SWITCH + 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. + + + + + + + +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); +?> +]]> + + &examples.outputs; + + + string(1) "1" +} +Total time uncached query: 0.000740s +array(1) { + ["id"]=> + string(1) "1" +} +Total time cached query: 0.000098s +]]> + + + + + If nothing else is configured, as it is the case in the quickstart example, + the plugin will use the built-in default storage handler. + The default storage handler uses process memory to hold a cache entry. + Depending on the PHP deploymnet 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. + + + 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 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 +]]> + + + + + 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 + NOW() or LAST_INSERT_ID(). The policy + aims to prevent pitfalls if caching by default is used. + + + + + + + +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); +} +?> +]]> + + &examples.outputs; + + + 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 +]]> + + + + + 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 + mysqlnd_qc.cache_no_table = 1 + to enable caching of such statements. Please, note the difference in the + measured times for the above and below examples. + + + + + + + +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); +} +?> +]]> + + &examples.outputs; + + + 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 +]]> + + + + + + Although mysqlnd_qc.cache_no_table = 1 + has been created for use with + mysqlnd_qc.cache_by_default = 1 + it is bound it. The plugin will evaluate the + mysqlnd_qc.cache_no_table + whenever a query is to be cached, no matter whether caching has been enabled using a + SQL hint or any other measure. + + +
+ +
+ Setting the TTL + + The default invalidation strategy of the query cache plugin is Time to Live + (TTL). The built-in storage handlers 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. + By default cache entries expire after 30 seconds + + + The example sets mysqlnd_qc.ttl=3 to cache + statements for three seconds by default. Every second it updates + a database table record to hold the current time and executes + a SELECT statement to fetch the record from the + database. The SELECT 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. + + + + + + + +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); +} +?> +]]> + + &examples.outputs; + + + + + + + As can be seen from the example, any TTL based cache + can serve stale data. Cache entries are not automatically invalidated, + if underlying data changes. Applications using the default + TTL invalidation strategy must be able to work correctly + with stale data. + + + User-defined cache storage handler can implement any invalidation strategy + to work around this limitation. Please, find more on this below. + + + 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 +]]> + + + + +
+ +
+ 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 + +]]> + + + + +
+ +
+ + diff --git a/reference/mysqlnd_qc/usage.xml b/reference/mysqlnd_qc/usage.xml deleted file mode 100644 index 272fefe2b2..0000000000 --- a/reference/mysqlnd_qc/usage.xml +++ /dev/null @@ -1,608 +0,0 @@ - - - - - &reftitle.examples; - -
- Basic usage - - The Query Cache plugin supports caching of queries issued by mysqli, - PDO_MYSQL and mysql. PECL/mysqlnd_qc supports - prepared statements (native and emulated), buffered queries and unbuffered queries. - - - - 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 - -]]> - - - - -
- -
- -