Commit a3e2b94a by Qiang Xue

Fixes #3611: Refactored query caching.

parent 6a6caf71
...@@ -245,20 +245,15 @@ Query caching requires a [[yii\db\Connection|DB connection]] and a valid `cache` ...@@ -245,20 +245,15 @@ Query caching requires a [[yii\db\Connection|DB connection]] and a valid `cache`
The basic usage of query caching is as follows, assuming `$db` is a [[yii\db\Connection]] instance: The basic usage of query caching is as follows, assuming `$db` is a [[yii\db\Connection]] instance:
```php ```php
$duration = 60; // cache query results for 60 seconds. $result = $db->cache(function ($db) {
$dependency = ...; // optional dependency
$db->beginCache($duration, $dependency);
// ...performs DB queries here... // the result of the SQL query will be served from the cache
// if query caching is enabled and the query result is found in the cache
return $db->createCommand('SELECT * FROM customer WHERE id=1')->queryOne();
$db->endCache(); });
``` ```
As you can see, any SQL queries in between the `beginCache()` and `endCache()` calls will be cached.
If the result of the same query is found valid in the cache, the query will be skipped and the result
will be served from the cache instead.
Query caching can be used for [DAO](db-dao.md) as well as [ActiveRecord](db-active-record.md). Query caching can be used for [DAO](db-dao.md) as well as [ActiveRecord](db-active-record.md).
> Info: Some DBMS (e.g. [MySQL](http://dev.mysql.com/doc/refman/5.1/en/query-cache.html)) > Info: Some DBMS (e.g. [MySQL](http://dev.mysql.com/doc/refman/5.1/en/query-cache.html))
...@@ -269,13 +264,85 @@ Query caching can be used for [DAO](db-dao.md) as well as [ActiveRecord](db-acti ...@@ -269,13 +264,85 @@ Query caching can be used for [DAO](db-dao.md) as well as [ActiveRecord](db-acti
### Configurations <a name="query-caching-configs"></a> ### Configurations <a name="query-caching-configs"></a>
Query caching has two configurable options through [[yii\db\Connection]]: Query caching has three global configurable options through [[yii\db\Connection]]:
* [[yii\db\Connection::enableQueryCache|enableQueryCache]]: whether to turn on or off query caching.
It defaults to false. Note that to effectively turn on query caching, you also need to have a valid
cache, as specified by [[yii\db\Connection::queryCache|queryCache]].
* [[yii\db\Connection::queryCacheDuration|queryCacheDuration]]: this represents the number of seconds * [[yii\db\Connection::queryCacheDuration|queryCacheDuration]]: this represents the number of seconds
that a query result can remain valid in the cache. The duration will be overwritten if you call that a query result can remain valid in the cache. You can use 0 to indicate a query result should
[[yii\db\Connection::beginCache()]] with an explicit duration parameter. remain in the cache forever. This property is the default value used when [[yii\db\Connection::cache()]]
is called without specifying a duration.
* [[yii\db\Connection::queryCache|queryCache]]: this represents the ID of the cache application component. * [[yii\db\Connection::queryCache|queryCache]]: this represents the ID of the cache application component.
It defaults to `'cache'`. Query caching is enabled only when there is a valid cache application component. It defaults to `'cache'`. Query caching is enabled only if there is a valid cache application component.
### Usages <<a name="query-caching-usages"></a>
You can use [[yii\db\Connection::cache()]] if you have multiple SQL queries that need to take advantage of
query caching. The usage is as follows,
```php
$duration = 60; // cache query results for 60 seconds.
$dependency = ...; // optional dependency
$result = $db->cache(function ($db) {
// ... perform SQL queries here ...
return $result;
}, $duration, $dependency);
```
Any SQL queries in the anonymous function will be cached for the specified duration with the specified dependency.
If the result of a query is found valid in the cache, the query will be skipped and the result will be served
from the cache instead. If you do not specify the `$duration` parameter, the value of
[[yii\db\Connection::queryCacheDuration|queryCacheDuration]] will be used instead.
Sometimes within `cache()`, you may want to disable query caching for some particular queries. You can use
[[yii\db\Connection::noCache()]] in this case.
```php
$result = $db->cache(function ($db) {
// SQL queries that use query caching
$db->noCache(function ($db) {
// SQL queries that do not use query caching
});
// ...
return $result;
});
```
If you just want to use query caching for a single query, you can call [[yii\db\Command::cache()]] when building
the command. For example,
```php
// use query caching and set query cache duration to be 60 seconds
$customer = $db->createCommand('SELECT * FROM customer WHERE id=1')->cache(60)->queryOne();
```
You can also use [[yii\db\Command::noCache()]] to disable query caching for a single command. For example,
```php
$result = $db->cache(function ($db) {
// SQL queries that use query caching
// do not use query caching for this command
$customer = $db->createCommand('SELECT * FROM customer WHERE id=1')->noCache()->queryOne();
// ...
return $result;
});
```
### Limitations <a name="query-caching-limitations"></a> ### Limitations <a name="query-caching-limitations"></a>
......
...@@ -167,6 +167,11 @@ Yii Framework 2 Change Log ...@@ -167,6 +167,11 @@ Yii Framework 2 Change Log
- Chg #3383: Added `$type` parameter to `IdentityInterface::findIdentityByAccessToken()` (qiangxue) - Chg #3383: Added `$type` parameter to `IdentityInterface::findIdentityByAccessToken()` (qiangxue)
- Chg #3531: \yii\grid\GridView now allows any character (except ":") in the attribute part of the shorthand syntax for columns (rawtaz) - Chg #3531: \yii\grid\GridView now allows any character (except ":") in the attribute part of the shorthand syntax for columns (rawtaz)
- Chg #3544: Added `$key` as a parameter to the callable specified via `yii\grid\DataColumn::value` (mdmunir) - Chg #3544: Added `$key` as a parameter to the callable specified via `yii\grid\DataColumn::value` (mdmunir)
- Chg #3611: Query caching is refactored. (qiangxue)
- `yii\db\Connection::beginCache()` and `endCache()` are removed.
- Added `yii\db\Connection::cache()` and `noCache()`.
- Added `Command::cache()` and `noCache()`.
- `yii\db\Connection::queryCacheDuration` is now used as a default cache duration parameter.
- Chg #3640: All cookies are now httpOnly by default in order to increase overall security (samdark) - Chg #3640: All cookies are now httpOnly by default in order to increase overall security (samdark)
- Chg #3687: Default `sourceLanguage` and `language` are now `en-US` in order for i18n formatter to work correctly (samdark) - Chg #3687: Default `sourceLanguage` and `language` are now `en-US` in order for i18n formatter to work correctly (samdark)
- Chg #3804: Added `fileinfo` PHP extension to the basic requirement of Yii (Ragazzo) - Chg #3804: Added `fileinfo` PHP extension to the basic requirement of Yii (Ragazzo)
......
...@@ -161,3 +161,14 @@ new ones save the following code as `convert.php` that should be placed in the s ...@@ -161,3 +161,14 @@ new ones save the following code as `convert.php` that should be placed in the s
// ... // ...
]; ];
``` ```
* If you are using query caching, you should modify your relevant code as follows, as `beginCache()` and `endCache()` are
replaced by `cache()`:
```php
$db->cache(function ($db) {
// ... SQL queries that need to use query caching
}, $duration, $dependency);
```
...@@ -204,30 +204,21 @@ class Connection extends Component ...@@ -204,30 +204,21 @@ class Connection extends Component
* @var boolean whether to enable query caching. * @var boolean whether to enable query caching.
* Note that in order to enable query caching, a valid cache component as specified * Note that in order to enable query caching, a valid cache component as specified
* by [[queryCache]] must be enabled and [[enableQueryCache]] must be set true. * by [[queryCache]] must be enabled and [[enableQueryCache]] must be set true.
*
* Methods [[beginCache()]] and [[endCache()]] can be used as shortcuts to turn on
* and off query caching on the fly.
* @see queryCacheDuration
* @see queryCache * @see queryCache
* @see queryCacheDependency * @see cache()
* @see beginCache() * @see noCache()
* @see endCache()
*/ */
public $enableQueryCache = false; public $enableQueryCache = false;
/** /**
* @var integer number of seconds that query results can remain valid in cache. * @var integer the default number of seconds that query results can remain valid in cache.
* Defaults to 3600, meaning 3600 seconds, or one hour.
* Use 0 to indicate that the cached data will never expire. * Use 0 to indicate that the cached data will never expire.
* Defaults to 3600, meaning 3600 seconds, or one hour. Use 0 to indicate that the cached data will never expire.
* The value of this property will be used when [[cache()]] is called without a cache duration.
* @see enableQueryCache * @see enableQueryCache
* @see cache()
*/ */
public $queryCacheDuration = 3600; public $queryCacheDuration = 3600;
/** /**
* @var \yii\caching\Dependency the dependency that will be used when saving query results into cache.
* Defaults to null, meaning no dependency.
* @see enableQueryCache
*/
public $queryCacheDependency;
/**
* @var Cache|string the cache object or the ID of the cache application component * @var Cache|string the cache object or the ID of the cache application component
* that is used for query caching. * that is used for query caching.
* @see enableQueryCache * @see enableQueryCache
...@@ -369,6 +360,10 @@ class Connection extends Component ...@@ -369,6 +360,10 @@ class Connection extends Component
* @var Connection the currently active slave connection * @var Connection the currently active slave connection
*/ */
private $_slave = false; private $_slave = false;
/**
* @var array query cache parameters for the [[cache()]] calls
*/
private $_queryCacheInfo = [];
/** /**
...@@ -381,29 +376,98 @@ class Connection extends Component ...@@ -381,29 +376,98 @@ class Connection extends Component
} }
/** /**
* Turns on query caching. * Uses query cache for the queries performed with the callable.
* This method is provided as a shortcut to setting two properties that are related * When query caching is enabled ([[enableQueryCache]] is true and [[queryCache]] refers to a valid cache),
* with query caching: [[queryCacheDuration]] and [[queryCacheDependency]]. * queries performed within the callable will be cached and their results will be fetched from cache if available.
* @param integer $duration the number of seconds that query results may remain valid in cache. * For example,
* If not set, it will use the value of [[queryCacheDuration]]. See [[queryCacheDuration]] for more details. *
* @param \yii\caching\Dependency $dependency the dependency for the cached query result. * ```php
* See [[queryCacheDependency]] for more details. * // The customer will be fetched from cache if available.
* // If not, the query will be made against DB and cached for use next time.
* $customer = $db->cache(function (Connection $db) {
* return $db->createCommand('SELECT * FROM customer WHERE id=1')->queryOne();
* });
* ```
*
* Note that query cache is only meaningful for queries that return results. For queries performed with
* [[Command::execute()]], query cache will not be used.
*
* @param callable $callable a PHP callable that contains DB queries which will make use of query cache.
* The signature of the callable is `function (Connection $db)`.
* @param integer $duration the number of seconds that query results can remain valid in the cache. If this is
* not set, the value of [[queryCacheDuration]] will be used instead.
* Use 0 to indicate that the cached data will never expire.
* @param \yii\caching\Dependency $dependency the cache dependency associated with the cached query results.
* @return mixed the return result of the callable
* @see enableQueryCache
* @see queryCache
* @see noCache()
*/ */
public function beginCache($duration = null, $dependency = null) public function cache(callable $callable, $duration = null, $dependency = null)
{ {
$this->enableQueryCache = true; $this->_queryCacheInfo[] = [$duration === null ? $this->queryCacheDuration : $duration, $dependency];
if ($duration !== null) { $result = call_user_func($callable, $this);
$this->queryCacheDuration = $duration; array_pop($this->_queryCacheInfo);
return $result;
}
/**
* Disables query cache temporarily.
* Queries performed within the callable will not use query cache at all. For example,
*
* ```php
* $db->cache(function (Connection $db) {
*
* // ... queries that use query cache ...
*
* return $db->noCache(function (Connection $db) {
* // this query will not use query cache
* return $db->createCommand('SELECT * FROM customer WHERE id=1')->queryOne();
* });
* });
* ```
*
* @param callable $callable a PHP callable that contains DB queries which should not use query cache.
* The signature of the callable is `function (Connection $db)`.
* @return mixed the return result of the callable
* @see enableQueryCache
* @see queryCache
* @see cache()
*/
public function noCache(callable $callable)
{
$this->_queryCacheInfo[] = false;
$result = call_user_func($callable, $this);
array_pop($this->_queryCacheInfo);
return $result;
}
/**
* Returns the current query cache information.
* This method is used internally by [[Command]].
* @return array the current query cache information, or null if query cache is not enabled.
* @internal
*/
public function getQueryCacheInfo()
{
$info = end($this->_queryCacheInfo);
if ($this->enableQueryCache) {
$cache = $this->queryCache instanceof Cache ? $this->queryCache : Yii::$app->get($this->queryCache, false);
if ($cache instanceof Cache) {
return is_array($info) ? [$cache, $info[0], $info[1]] : [$cache, null, null];
}
} }
$this->queryCacheDependency = $dependency; return null;
} }
/** /**
* Turns off query caching. * Cleans up the query cache information.
* This method is used internally by [[Command]].
* @internal
*/ */
public function endCache() public function resetQueryCacheInfo()
{ {
$this->enableQueryCache = false; $this->_queryCacheInfo = [];
} }
/** /**
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
namespace yiiunit\framework\db; namespace yiiunit\framework\db;
use yii\caching\FileCache;
use yii\db\Connection;
use yii\db\DataReader; use yii\db\DataReader;
/** /**
...@@ -240,6 +242,7 @@ class CommandTest extends DatabaseTestCase ...@@ -240,6 +242,7 @@ class CommandTest extends DatabaseTestCase
$this->assertEquals(2, $command->execute()); $this->assertEquals(2, $command->execute());
} }
/*
public function testInsert() public function testInsert()
{ {
} }
...@@ -299,6 +302,7 @@ class CommandTest extends DatabaseTestCase ...@@ -299,6 +302,7 @@ class CommandTest extends DatabaseTestCase
public function testDropIndex() public function testDropIndex()
{ {
} }
*/
public function testIntegrityViolation() public function testIntegrityViolation()
{ {
...@@ -311,4 +315,49 @@ class CommandTest extends DatabaseTestCase ...@@ -311,4 +315,49 @@ class CommandTest extends DatabaseTestCase
$command->execute(); $command->execute();
$command->execute(); $command->execute();
} }
public function testQueryCache()
{
$db = $this->getConnection();
$db->enableQueryCache = true;
$db->queryCache = new FileCache(['cachePath' => '@yiiunit/runtime/cache']);
$command = $db->createCommand('SELECT name FROM customer WHERE id=:id');
$this->assertEquals('user1', $command->bindValue(':id', 1)->queryScalar());
$update = $db->createCommand('UPDATE customer SET name=:name WHERE id=:id');
$update->bindValues([':id' => 1, ':name' => 'user11'])->execute();
$this->assertEquals('user11', $command->bindValue(':id', 1)->queryScalar());
$db->cache(function (Connection $db) use ($command, $update) {
$this->assertEquals('user2', $command->bindValue(':id', 2)->queryScalar());
$update->bindValues([':id' => 2, ':name' => 'user22'])->execute();
$this->assertEquals('user2', $command->bindValue(':id', 2)->queryScalar());
$db->noCache(function () use ($command) {
$this->assertEquals('user22', $command->bindValue(':id', 2)->queryScalar());
});
$this->assertEquals('user2', $command->bindValue(':id', 2)->queryScalar());
}, 10);
$db->enableQueryCache = false;
$db->cache(function ($db) use ($command, $update) {
$this->assertEquals('user22', $command->bindValue(':id', 2)->queryScalar());
$update->bindValues([':id' => 2, ':name' => 'user2'])->execute();
$this->assertEquals('user2', $command->bindValue(':id', 2)->queryScalar());
}, 10);
$db->enableQueryCache = true;
$command = $db->createCommand('SELECT name FROM customer WHERE id=:id')->cache();
$this->assertEquals('user11', $command->bindValue(':id', 1)->queryScalar());
$update->bindValues([':id' => 1, ':name' => 'user1'])->execute();
$this->assertEquals('user11', $command->bindValue(':id', 1)->queryScalar());
$this->assertEquals('user1', $command->noCache()->bindValue(':id', 1)->queryScalar());
$command = $db->createCommand('SELECT name FROM customer WHERE id=:id');
$db->cache(function (Connection $db) use ($command, $update) {
$this->assertEquals('user11', $command->bindValue(':id', 1)->queryScalar());
$this->assertEquals('user1', $command->noCache()->bindValue(':id', 1)->queryScalar());
}, 10);
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment