caching-data.md 14.4 KB
Newer Older
Qiang Xue committed
1
Data Caching
Qiang Xue committed
2
============
Qiang Xue committed
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361

Data caching is about storing some PHP variable in cache and retrieving it later from cache. For this purpose,
the cache component base class [[yii\caching\Cache]] provides two methods that are used most of the time:
[[yii\caching\Cache::set()|set()]] and [[yii\caching\Cache::get()|get()]]. Note, only serializable variables and objects could be cached successfully.

To store a variable `$value` in cache, we choose a unique `$key` and call [[yii\caching\Cache::set()|set()]] to store it:

```php
Yii::$app->cache->set($key, $value);
```

The cached data will remain in the cache forever unless it is removed because of some caching policy
(e.g. caching space is full and the oldest data are removed). To change this behavior, we can also supply
an expiration parameter when calling [[yii\caching\Cache::set()|set()]] so that the data will be removed from the cache after
a certain period of time:

```php
// keep the value in cache for at most 45 seconds
Yii::$app->cache->set($key, $value, 45);
```

Later when we need to access this variable (in either the same or a different web request), we call [[yii\caching\Cache::get()|get()]]
with the key to retrieve it from cache. If the value returned is `false`, it means the value is not available
in cache and we should regenerate it:

```php
public function getCachedData()
{
    $key = /* generate unique key here */;
    $value = Yii::$app->cache->get($key);
    if ($value === false) {
        $value = /* regenerate value because it is not found in cache and then save it in cache for later use */;
        Yii::$app->cache->set($key, $value);
    }
    return $value;
}
```

This is the common pattern of arbitrary data caching for general use.

When choosing the key for a variable to be cached, make sure the key is unique among all other variables that
may be cached in the application. It is **NOT** required that the key is unique across applications because
the cache component is intelligent enough to differentiate keys for different applications.

Some cache storages, such as MemCache, APC, support retrieving multiple cached values in a batch mode,
which may reduce the overhead involved in retrieving cached data. A method named [[yii\caching\Cache::mget()|mget()]] is provided
to exploit this feature. In case the underlying cache storage does not support this feature,
[[yii\caching\Cache::mget()|mget()]] will still simulate it.

To remove a cached value from cache, call [[yii\caching\Cache::delete()|delete()]]; and to remove everything from cache, call
[[yii\caching\Cache::flush()|flush()]].
Be very careful when calling [[yii\caching\Cache::flush()|flush()]] because it also removes cached data that are from
other applications if the cache is shared among different applications.

Note, because [[yii\caching\Cache]] implements `ArrayAccess`, a cache component can be used liked an array. The followings
are some examples:

```php
$cache = Yii::$app->cache;
$cache['var1'] = $value1;  // equivalent to: $cache->set('var1', $value1);
$value2 = $cache['var2'];  // equivalent to: $value2 = $cache->get('var2');
```

### Cache Dependency

Besides expiration setting, cached data may also be invalidated according to some dependency changes. For example, if we
are caching the content of some file and the file is changed, we should invalidate the cached copy and read the latest
content from the file instead of the cache.

We represent a dependency as an instance of [[yii\caching\Dependency]] or its child class. We pass the dependency
instance along with the data to be cached when calling [[yii\caching\Cache::set()|set()]].

```php
use yii\caching\FileDependency;

// the value will expire in 30 seconds
// it may also be invalidated earlier if the dependent file is changed
Yii::$app->cache->set($id, $value, 30, new FileDependency(['fileName' => 'example.txt']));
```

Now if we retrieve $value from cache by calling `get()`, the dependency will be evaluated and if it is changed, we will
get a false value, indicating the data needs to be regenerated.

Below is a summary of the available cache dependencies:

- [[yii\caching\FileDependency]]: the dependency is changed if the file's last modification time is changed.
- [[yii\caching\GroupDependency]]: marks a cached data item with a group name. You may invalidate the cached data items
  with the same group name all at once by calling [[yii\caching\GroupDependency::invalidate()]].
- [[yii\caching\DbDependency]]: the dependency is changed if the query result of the specified SQL statement is changed.
- [[yii\caching\ChainedDependency]]: the dependency is changed if any of the dependencies on the chain is changed.
- [[yii\caching\ExpressionDependency]]: the dependency is changed if the result of the specified PHP expression is
  changed.

### Query Caching

For caching the result of database queries you can wrap them in calls to [[yii\db\Connection::beginCache()]]
and [[yii\db\Connection::endCache()]]:

```php
$connection->beginCache(60); // cache all query results for 60 seconds.
// your db query code here...
$connection->endCache();
```


Data Caching
============

Data caching is about storing some PHP variable in cache and retrieving it
later from cache. For this purpose, the cache component base class [CCache]
provides two methods that are used most of the time: [set()|CCache::set]
and [get()|CCache::get].

To store a variable `$value` in cache, we choose a unique ID and call
[set()|CCache::set] to store it:

~~~
[php]
Yii::app()->cache->set($id, $value);
~~~

The cached data will remain in the cache forever unless it is removed
because of some caching policy (e.g. caching space is full and the oldest
data are removed). To change this behavior, we can also supply an
expiration parameter when calling [set()|CCache::set] so that the data will
be removed from the cache after, at most, that period of time:

~~~
[php]
// keep the value in cache for at most 30 seconds
Yii::app()->cache->set($id, $value, 30);
~~~

Later when we need to access this variable (in either the same or a
different Web request), we call [get()|CCache::get] with the ID to retrieve
it from cache. If the returned value is false, it means the value is not
available in cache and we have to regenerate it.

~~~
[php]
$value=Yii::app()->cache->get($id);
if($value===false)
{
	// regenerate $value because it is not found in cache
	// and save it in cache for later use:
	// Yii::app()->cache->set($id,$value);
}
~~~

When choosing the ID for a variable to be cached, make sure the ID is
unique among all other variables that may be cached in the application. It
is NOT required that the ID is unique across applications because the cache
component is intelligent enough to differentiate IDs for different
applications.

Some cache storages, such as MemCache, APC, support retrieving
multiple cached values in a batch mode, which may reduce the overhead involved
in retrieving cached data. A method named
[mget()|CCache::mget] is provided to achieve this feature. In case the underlying
cache storage does not support this feature, [mget()|CCache::mget] will still
simulate it.

To remove a cached value from cache, call [delete()|CCache::delete]; and
to remove everything from cache, call [flush()|CCache::flush]. Be very
careful when calling [flush()|CCache::flush] because it also removes cached
data that are from other applications.

> Tip: Because [CCache] implements `ArrayAccess`, a cache component can be
> used liked an array. The followings are some examples:
> ~~~
> [php]
> $cache=Yii::app()->cache;
> $cache['var1']=$value1;  // equivalent to: $cache->set('var1',$value1);
> $value2=$cache['var2'];  // equivalent to: $value2=$cache->get('var2');
> ~~~

Cache Dependency
----------------

Besides expiration setting, cached data may also be invalidated according
to some dependency changes. For example, if we are caching the content of
some file and the file is changed, we should invalidate the cached copy and
read the latest content from the file instead of the cache.

We represent a dependency as an instance of [CCacheDependency] or its
child class. We pass the dependency instance along with the data to be
cached when calling [set()|CCache::set].

~~~
[php]
// the value will expire in 30 seconds
// it may also be invalidated earlier if the dependent file is changed
Yii::app()->cache->set($id, $value, 30, new CFileCacheDependency('FileName'));
~~~

Now if we retrieve `$value` from cache by calling [get()|CCache::get], the
dependency will be evaluated and if it is changed, we will get a false
value, indicating the data needs to be regenerated.

Below is a summary of the available cache dependencies:

   - [CFileCacheDependency]: the dependency is changed if the file's last
modification time is changed.

   - [CDirectoryCacheDependency]: the dependency is changed if any of the
files under the directory and its subdirectories is changed.

   - [CDbCacheDependency]: the dependency is changed if the query result
of the specified SQL statement is changed.

   - [CGlobalStateCacheDependency]: the dependency is changed if the value
of the specified global state is changed. A global state is a variable that
is persistent across multiple requests and multiple sessions in an
application. It is defined via [CApplication::setGlobalState()].

   - [CChainedCacheDependency]: the dependency is changed if any of the
dependencies on the chain is changed.

   - [CExpressionDependency]: the dependency is changed if the result of
the specified PHP expression is changed.


Query Caching
-------------

Since version 1.1.7, Yii has added support for query caching.
Built on top of data caching, query caching stores the result of a DB query
in cache and may thus save the DB query execution time if the same query is requested
in future, as the result can be directly served from the cache.

> Info: Some DBMS (e.g. [MySQL](http://dev.mysql.com/doc/refman/5.1/en/query-cache.html))
> also support query caching on the DB server side. Compared with the server-side
> query caching, the same feature we support here offers more flexibility and
> may be potentially more efficient.


### Enabling Query Caching

To enable query caching, make sure [CDbConnection::queryCacheID] refers to the ID of a valid
cache application component (it defaults to `cache`).


### Using Query Caching with DAO

To use query caching, we call the [CDbConnection::cache()] method when we perform DB queries.
The following is an example:

~~~
[php]
$sql = 'SELECT * FROM tbl_post LIMIT 20';
$dependency = new CDbCacheDependency('SELECT MAX(update_time) FROM tbl_post');
$rows = Yii::app()->db->cache(1000, $dependency)->createCommand($sql)->queryAll();
~~~

When running the above statements, Yii will first check if the cache contains a valid
result for the SQL statement to be executed. This is done by checking the following three conditions:

- if the cache contains an entry indexed by the SQL statement.
- if the entry is not expired (less than 1000 seconds since it was first saved in the cache).
- if the dependency has not changed (the maximum `update_time` value is the same as when
the query result was saved in the cache).

If all of the above conditions are satisfied, the cached result will be returned directly from the cache.
Otherwise, the SQL statement will be sent to the DB server for execution, and the corresponding
result will be saved in the cache and returned.


### Using Query Caching with ActiveRecord

Query caching can also be used with [Active Record](/doc/guide/database.ar).
To do so, we call a similar [CActiveRecord::cache()] method like the following:

~~~
[php]
$dependency = new CDbCacheDependency('SELECT MAX(update_time) FROM tbl_post');
$posts = Post::model()->cache(1000, $dependency)->findAll();
// relational AR query
$posts = Post::model()->cache(1000, $dependency)->with('author')->findAll();
~~~

The `cache()` method here is essentially a shortcut to [CDbConnection::cache()].
Internally, when executing the SQL statement generated by ActiveRecord, Yii will
attempt to use query caching as we described in the last subsection.


### Caching Multiple Queries

By default, each time we call the `cache()` method (of either [CDbConnection] or [CActiveRecord]),
it will mark the next SQL query to be cached. Any other SQL queries will NOT be cached
unless we call `cache()` again. For example,

~~~
[php]
$sql = 'SELECT * FROM tbl_post LIMIT 20';
$dependency = new CDbCacheDependency('SELECT MAX(update_time) FROM tbl_post');

$rows = Yii::app()->db->cache(1000, $dependency)->createCommand($sql)->queryAll();
// query caching will NOT be used
$rows = Yii::app()->db->createCommand($sql)->queryAll();
~~~

By supplying an extra `$queryCount` parameter to the `cache()` method, we can enforce
multiple queries to use query caching. In the following example, when we call `cache()`,
we specify that query caching should be used for the next 2 queries:

~~~
[php]
// ...
$rows = Yii::app()->db->cache(1000, $dependency, 2)->createCommand($sql)->queryAll();
// query caching WILL be used
$rows = Yii::app()->db->createCommand($sql)->queryAll();
~~~

As we know, when performing a relational AR query, it is possible several SQL queries will
be executed (by checking the [log messages](/doc/guide/topics.logging)).
For example, if the relationship between `Post` and `Comment` is `HAS_MANY`,
then the following code will actually execute two DB queries:

- it first selects the posts limited by 20;
- it then selects the comments for the previously selected posts.

~~~
[php]
$posts = Post::model()->with('comments')->findAll(array(
	'limit'=>20,
));
~~~

If we use query caching as follows, only the first DB query will be cached:

~~~
[php]
$posts = Post::model()->cache(1000, $dependency)->with('comments')->findAll(array(
	'limit'=>20,
));
~~~

In order to cache both DB queries, we need supply the extra parameter indicating how
many DB queries we want to cache next:

~~~
[php]
$posts = Post::model()->cache(1000, $dependency, 2)->with('comments')->findAll(array(
	'limit'=>20,
));
~~~


### Limitations

Query caching does not work with query results that contain resource handles. For example,
when using the `BLOB` column type in some DBMS, the query result will return a resource
handle for the column data.

Some caching storage has size limitation. For example, memcache limits the maximum size
of each entry to be 1MB. Therefore, if the size of a query result exceeds this limit,
the caching will fail.


Qiang Xue committed
362 363 364
Note, by definition, cache is a volatile storage medium. It does not ensure the existence of the cached
data even if it does not expire. Therefore, do not use cache as a persistent storage (e.g. do not use cache
to store session data or other valuable information).