缓存对象关系映射Caching in the ORM

现实中的每个应用都不同,一些应用的模型数据经常改变而另一些模型的数据几乎不同。访问数据库在很多时候对我们应用的来说 是个瓶颈。这是由于我们每次访问应用时都会和数据库数据通信,和数据库进行通信的代价是很大的。因此在必要时我们可以通过增加缓存层来获取更高的性能。 本章内容的重点即是探讨实施缓存来提高性能的可行性。Phalcon框架给我们提供了灵活的缓存技术来实现我们的应用缓存。

Every application is different, we could have models whose data change frequently and others that rarely change. Accessing database systems is often one of the most common bottlenecks in terms of performance. This is due to the complex connection/communication processes that PHP must do in each request to obtain data from the database. Therefore, if we want to achieve good performance we need to add some layers of caching where the application requires it.

This chapter explains the possible points where it is possible to implement caching to improve performance. The framework gives you the tools to implement the cache where you demand of it according to the architecture of your application.

缓存结果集Caching Resultsets

一个非常可行的方案是我们可以为那些不经常改变且经常访问的数据库数据进行缓存,比如把他们放入内存,这样可以加快程序的执行速度。当 Phalcon\Mvc\Model 需要使用缓存数据的服务时Model可以直接从DI中取得此缓存服务modelsCache(惯例名).Phalcon提供了一个组件(服务)可以用来 cache 任何种类的数据,下面我们会解释如何在model使用它。第一步我们要在启动文件注册这个服务:

A well established technique to avoid the continuous access to the database is to cache resultsets that don’t change frequently using a system with faster access (usually memory).

When Phalcon\Mvc\Model requires a service to cache resultsets, it will request it to the Dependency Injector Container with the convention name “modelsCache”.

As Phalcon provides a component to cache any kind of data, we’ll explain how to integrate it with Models. First, you must register it as a service in the services container:

  1. <?php
  2. use Phalcon\Cache\Frontend\Data as FrontendData;
  3. use Phalcon\Cache\Backend\Memcache as BackendMemcache;
  4. //Set the models cache service
  5. $di->set('modelsCache', function() {
  6. //Cache data for one day by default
  7. $frontCache = new FrontendData(array(
  8. "lifetime" => 86400
  9. ));
  10. //Memcached connection settings
  11. $cache = new BackendMemcache($frontCache, array(
  12. "host" => "localhost",
  13. "port" => "11211"
  14. ));
  15. return $cache;
  16. });

在注册缓存服务时我们可以按照我们的所需进行配置。一旦完成正确的缓存设置之后,我们可以按如下的方式缓存查询的结果了:

You have complete control in creating and customizing the cache before being used by registering the service as an anonymous function. Once the cache setup is properly defined you could cache resultsets as follows:

  1. <?php
  2. // Get products without caching
  3. $products = Products::find();
  4. // Just cache the resultset. The cache will expire in 1 hour (3600 seconds)
  5. $products = Products::find(array(
  6. "cache" => array("key" => "my-cache")
  7. ));
  8. // Cache the resultset for only for 5 minutes
  9. $products = Products::find(array(
  10. "cache" => array("key" => "my-cache", "lifetime" => 300)
  11. ));
  12. // Using a custom cache
  13. $products = Products::find(array("cache" => $myCache));

有关系查询得到的结果集同样可以加入缓存:

Caching could be also applied to resultsets generated using relationships:

  1. <?php
  2. // Query some post
  3. $post = Post::findFirst();
  4. // Get comments related to a post, also cache it
  5. $comments = $post->getComments(array(
  6. "cache" => array("key" => "my-key")
  7. ));
  8. // Get comments related to a post, setting lifetime
  9. $comments = $post->getComments(array(
  10. "cache" => array("key" => "my-key", "lifetime" => 3600)
  11. ));

如果想删除已经缓存的结果,则只需要使用前面指定的缓存的键值进行删除即可。

When a cached resultset needs to be invalidated, you can simply delete it from the cache using the previously specified key.

注意并不是所有的结果都必须缓存下来。那些经常改变的数据就不应该被缓存,这样做只会影响应用的性能。另外对于那些特别大的不易变的数据集,开发者应用根据实际情况进行选择是否进行缓存。

Note that not all resultsets must be cached. Results that change very frequently should not be cached since they are invalidated very quickly and caching in that case impacts performance. Additionally, large datasets that do not change frequently could be cached, but that is a decision that the developer has to make based on the available caching mechanism and whether the performance impact to simply retrieve that data in the first place is acceptable.

重写 find 与 findFirst 方法 Overriding find/findFirst

从上面的我们可以看到这两个方法是从:doc:`Phalcon\Mvc\Model <../api/Phalcon_Mvc_Model>`继承而来 <../api/Phalcon_Mvc_Model>:

As seen above, these methods are available in models that inherit Phalcon\Mvc\Model:

  1. <?php
  2. use Phalcon\Mvc\Model;
  3. class Robots extends Model
  4. {
  5. public static function find($parameters=null)
  6. {
  7. return parent::find($parameters);
  8. }
  9. public static function findFirst($parameters=null)
  10. {
  11. return parent::findFirst($parameters);
  12. }
  13. }

这样做会影响到所有此类的对象对这两个函数的调用,我们可以在其中添加一个缓存层,如果未有其它缓存的话(比如modelsCache)。例如,一个基本的缓存实现是我们在此类中添加一个静态的变量以避免在同一请求中多次查询数据库:

By doing this, you’re intercepting all the calls to these methods, this way, you can add a cache layer or run the query if there is no cache. For example, a very basic cache implementation, uses a static property to avoid that a record would be queried several times in a same request:

  1. <?php
  2. use Phalcon\Mvc\Model;
  3. class Robots extends Model
  4. {
  5. protected static $_cache = array();
  6. /**
  7. * Implement a method that returns a string key based
  8. * on the query parameters
  9. */
  10. protected static function _createKey($parameters)
  11. {
  12. $uniqueKey = array();
  13. foreach ($parameters as $key => $value) {
  14. if (is_scalar($value)) {
  15. $uniqueKey[] = $key . ':' . $value;
  16. } else {
  17. if (is_array($value)) {
  18. $uniqueKey[] = $key . ':[' . self::_createKey($value) .']';
  19. }
  20. }
  21. }
  22. return join(',', $uniqueKey);
  23. }
  24. public static function find($parameters=null)
  25. {
  26. //Create an unique key based on the parameters
  27. $key = self::_createKey($parameters);
  28. if (!isset(self::$_cache[$key])) {
  29. //Store the result in the memory cache
  30. self::$_cache[$key] = parent::find($parameters);
  31. }
  32. //Return the result in the cache
  33. return self::$_cache[$key];
  34. }
  35. public static function findFirst($parameters=null)
  36. {
  37. // ...
  38. }
  39. }

访问数据要远比计算key值慢的多,我们在这里定义自己需要的key生成方式。注意好的键可以避免冲突,这样就可以依据不同的key值取得不同的缓存结果。

Access the database is several times slower than calculate a cache key, you’re free in implement the key generation strategy you find better for your needs. Note that a good key avoids collisions as much as possible, this means that different keys returns unrelated records to the find parameters.

上面的例子中我们把缓存放在了内存中,这做为第一级的缓存。当然我们也可以在第一层缓存的基本上实现第二层的缓存比如使用 APC/XCache或是使用NoSQL数据库(如MongoDB等):

In the above example, we used a cache in memory, it is useful as a first level cache. Once we have the memory cache, we can implement a second level cache layer like APC/XCache or a NoSQL database:

  1. <?php
  2. public static function find($parameters=null)
  3. {
  4. //Create an unique key based on the parameters
  5. $key = self::_createKey($parameters);
  6. if (!isset(self::$_cache[$key])) {
  7. //We're using APC as second cache
  8. if (apc_exists($key)) {
  9. $data = apc_fetch($key);
  10. //Store the result in the memory cache
  11. self::$_cache[$key] = $data;
  12. return $data;
  13. }
  14. //There are no memory or apc cache
  15. $data = parent::find($parameters);
  16. //Store the result in the memory cache
  17. self::$_cache[$key] = $data;
  18. //Store the result in APC
  19. apc_store($key, $data);
  20. return $data;
  21. }
  22. //Return the result in the cache
  23. return self::$_cache[$key];
  24. }

这样我们可以对可模型的缓存进行完全的控制,如果多个模型需要进行如此缓存可以建立一个基础类:

This gives you full control on how the the caches must be implemented for each model, if this strategy is common to several models you can create a base class for all of them:

  1. <?php
  2. use Phalcon\Mvc\Model;
  3. class CacheableModel extends Model
  4. {
  5. protected static function _createKey($parameters)
  6. {
  7. // .. create a cache key based on the parameters
  8. }
  9. public static function find($parameters=null)
  10. {
  11. //.. custom caching strategy
  12. }
  13. public static function findFirst($parameters=null)
  14. {
  15. //.. custom caching strategy
  16. }
  17. }

然后把这个类作为其它缓存类的基类:

Then use this class as base class for each ‘Cacheable’ model:

  1. <?php
  2. class Robots extends CacheableModel
  3. {
  4. }

强制缓存Forcing Cache

前面的例子中我们在Phalcon\Mvc\Model中使用框架内建的缓存组件。为实现强制缓存我们传递了cache作为参数:

Earlier we saw how Phalcon\Mvc\Model has a built-in integration with the caching component provided by the framework. To make a record/resultset cacheable we pass the key ‘cache’ in the array of parameters:

  1. <?php
  2. // Cache the resultset for only for 5 minutes
  3. $products = Products::find(array(
  4. "cache" => array("key" => "my-cache", "lifetime" => 300)
  5. ));

为了自由的对特定的查询结果进行缓存我们,比如我们想对模型中的所有查询结果进行缓存我们可以重写find/findFirst方法:

This gives us the freedom to cache specific queries, however if we want to cache globally every query performed over the model, we can override the find/findFirst method to force every query to be cached:

  1. <?php
  2. use Phalcon\Mvc\Model;
  3. class Robots extends Model
  4. {
  5. protected static function _createKey($parameters)
  6. {
  7. // .. create a cache key based on the parameters
  8. }
  9. public static function find($parameters=null)
  10. {
  11. //Convert the parameters to an array
  12. if (!is_array($parameters)) {
  13. $parameters = array($parameters);
  14. }
  15. //Check if a cache key wasn't passed
  16. //and create the cache parameters
  17. if (!isset($parameters['cache'])) {
  18. $parameters['cache'] = array(
  19. "key" => self::_createKey($parameters),
  20. "lifetime" => 300
  21. );
  22. }
  23. return parent::find($parameters);
  24. }
  25. public static function findFirst($parameters=null)
  26. {
  27. //...
  28. }
  29. }

缓存 PHQL 查询 Caching PHQL Queries

ORM中的所有查询,不管多么高级的查询方法内部使用使用PHQL进行实现的。这个语言可以让我们非常自由的创建各种查询,当然这些查询也可以被缓存:

All queries in the ORM, no matter how high level syntax we used to create them are handled internally using PHQL. This language gives you much more freedom to create all kinds of queries. Of course these queries can be cached:

  1. <?php
  2. $phql = "SELECT * FROM Cars WHERE name = :name:";
  3. $query = $this->modelsManager->createQuery($phql);
  4. $query->cache(array(
  5. "key" => "cars-by-name",
  6. "lifetime" => 300
  7. ));
  8. $cars = $query->execute(array(
  9. 'name' => 'Audi'
  10. ));

如果不想使用隐式的缓存尽管使用你想用的缓存方式:

If you don’t want to use the implicit cache just save the resulset into your favorite cache backend:

  1. <?php
  2. $phql = "SELECT * FROM Cars WHERE name = :name:";
  3. $cars = $this->modelsManager->executeQuery($phql, array(
  4. 'name' => 'Audi'
  5. ));
  6. apc_store('my-cars', $cars);

重用的相关记录Reusable Related Records

一些模型有关联的数据表我们直接使用在内存中的实例关联数据:

Some models may have relationships to other models. This allows us to easily check the records that relate to instances in memory:

  1. <?php
  2. //Get some invoice
  3. $invoice = Invoices::findFirst();
  4. //Get the customer related to the invoice
  5. $customer = $invoice->customer;
  6. //Print his/her name
  7. echo $customer->name, "\n";

这个例子非常简单,依据查询到的订单信息取得用户信息之后再取得用户名。下面的情景也是如何:我们查询了一些订单的信息,然后取得这些订单相关联 用户的信息,之后取得用户名:

This example is very simple, a customer is queried and can be used as required, for example, to show its name. This also applies if we retrieve a set of invoices to show customers that correspond to these invoices:

  1. <?php
  2. //Get a set of invoices
  3. // SELECT * FROM invoices;
  4. foreach (Invoices::find() as $invoice) {
  5. //Get the customer related to the invoice
  6. // SELECT * FROM customers WHERE id = ?;
  7. $customer = $invoice->customer;
  8. //Print his/her name
  9. echo $customer->name, "\n";
  10. }

每个客户可能会有一个或多个帐单,这就意味着客户对象没必须取多次。为了避免一次次的重复取客户信息,我们这里设置关系为reusable为true, 这样ORM即知可以重复使用客户信息:

A customer may have one or more bills, this means that the customer may be unnecessarily more than once. To avoid this, we could mark the relationship as reusable, this way, we tell the ORM to automatically reuse the records instead of re-querying them again and again:

  1. <?php
  2. use Phalcon\Mvc\Model;
  3. class Invoices extends Model
  4. {
  5. public function initialize()
  6. {
  7. $this->belongsTo("customers_id", "Customer", "id", array(
  8. 'reusable' => true
  9. ));
  10. }
  11. }

此Cache存在于内存中,这意味着当请示结束时缓存数据即被释放。我们也可以通过重写模型管理器的方式实现更加复杂的缓存:

This cache works in memory only, this means that cached data are released when the request is terminated. You can add a more sophisticated cache for this scenario overriding the models manager:

  1. <?php
  2. use Phalcon\Mvc\Model\Manager as ModelManager;
  3. class CustomModelsManager extends ModelManager
  4. {
  5. /**
  6. * Returns a reusable object from the cache
  7. *
  8. * @param string $modelName
  9. * @param string $key
  10. * @return object
  11. */
  12. public function getReusableRecords($modelName, $key){
  13. //If the model is Products use the APC cache
  14. if ($modelName == 'Products'){
  15. return apc_fetch($key);
  16. }
  17. //For the rest, use the memory cache
  18. return parent::getReusableRecords($modelName, $key);
  19. }
  20. /**
  21. * Stores a reusable record in the cache
  22. *
  23. * @param string $modelName
  24. * @param string $key
  25. * @param mixed $records
  26. */
  27. public function setReusableRecords($modelName, $key, $records){
  28. //If the model is Products use the APC cache
  29. if ($modelName == 'Products'){
  30. apc_store($key, $records);
  31. return;
  32. }
  33. //For the rest, use the memory cache
  34. parent::setReusableRecords($modelName, $key, $records);
  35. }
  36. }

别忘记注册模型管理器到DI中:

Do not forget to register the custom models manager in the DI:

  1. <?php
  2. $di->setShared('modelsManager', function() {
  3. return new CustomModelsManager();
  4. });

缓存相关记录 Caching Related Records

当使用find或findFirst查询关联数据时,ORM内部会自动的依据以下规则创建查询条件于:

When a related record is queried, the ORM internally builds the appropriate condition and gets the required records using find/findFirst in the target model according to the following table:

TypeDescription | Implicit Method
Belongs-ToReturns a model instance of the related record directly | findFirst
Has-OneReturns a model instance of the related record directly | findFirst
Has-ManyReturns a collection of model instances of the referenced model | find

这意味着当我们取得关联记录时,我们需要解析如何如何取得数据的方法:

This means that when you get a related record you could intercept how these data are obtained by implementing the corresponding method:

  1. <?php
  2. //Get some invoice
  3. $invoice = Invoices::findFirst();
  4. //Get the customer related to the invoice
  5. $customer = $invoice->customer; // Invoices::findFirst('...');
  6. //Same as above
  7. $customer = $invoice->getCustomer(); // Invoices::findFirst('...');

因此,我们可以替换掉Invoices模型中的findFirst方法然后实现我们使用适合的方法

Accordingly, we could replace the findFirst method in the model Invoices and implement the cache we consider most appropriate:

  1. <?php
  2. use Phalcon\Mvc\Model;
  3. class Invoices extends Model
  4. {
  5. public static function findFirst($parameters=null)
  6. {
  7. //.. custom caching strategy
  8. }
  9. }

递归缓存相关记录Caching Related Records Recursively

在这种场景下我们假定我们每次取主记录时都会取模型的关联记录,如果我们此时保存这些记录把相关记录也保存下来可能会为我们的系统带来一些性能上的提升:

In this scenario, we assume that everytime we query a result we also retrieve their associated records. If we store the records found together with their related entities perhaps we could reduce a bit the overhead required to obtain all entities:

  1. <?php
  2. use Phalcon\Mvc\Model;
  3. class Invoices extends Model
  4. {
  5. protected static function _createKey($parameters)
  6. {
  7. // .. create a cache key based on the parameters
  8. }
  9. protected static function _getCache($key)
  10. {
  11. // returns data from a cache
  12. }
  13. protected static function _setCache($key)
  14. {
  15. // stores data in the cache
  16. }
  17. public static function find($parameters=null)
  18. {
  19. //Create a unique key
  20. $key = self::_createKey($parameters);
  21. //Check if there are data in the cache
  22. $results = self::_getCache($key);
  23. // Valid data is an object
  24. if (is_object($results)) {
  25. return $results;
  26. }
  27. $results = array();
  28. $invoices = parent::find($parameters);
  29. foreach ($invoices as $invoice) {
  30. //Query the related customer
  31. $customer = $invoice->customer;
  32. //Assign it to the record
  33. $invoice->customer = $customer;
  34. $results[] = $invoice;
  35. }
  36. //Store the invoices in the cache + their customers
  37. self::_setCache($key, $results);
  38. return $results;
  39. }
  40. public function initialize()
  41. {
  42. // add relations and initialize other stuff
  43. }
  44. }

从已经缓存的订单中取得用户信息,可以减少系统的负载。注意我们也可以使用PHQL来实现这个,下面使用了PHQL来实现:

Getting the invoices from the cache already obtains the customer data in just one hit, reducing the overall overhead of the operation. Note that this process can also be performed with PHQL following an alternative solution:

  1. <?php
  2. use Phalcon\Mvc\Model;
  3. class Invoices extends Model
  4. {
  5. public function initialize()
  6. {
  7. // add relations and initialize other stuff
  8. }
  9. protected static function _createKey($conditions, $params)
  10. {
  11. // .. create a cache key based on the parameters
  12. }
  13. public function getInvoicesCustomers($conditions, $params=null)
  14. {
  15. $phql = "SELECT Invoices.*, Customers.*
  16. FROM Invoices JOIN Customers WHERE " . $conditions;
  17. $query = $this->getModelsManager()->executeQuery($phql);
  18. $query->cache(array(
  19. "key" => self::_createKey($conditions, $params),
  20. "lifetime" => 300
  21. ));
  22. return $query->execute($params);
  23. }
  24. }

基于条件的缓存Caching based on Conditions

此例中,我依据当的条件实施缓存:根据主键值不同的范围分配不同的缓存后端:

In this scenario, the cache is implemented conditionally according to current conditions received. According to the range where the primary key is located we choose a different cache backend:

TypeCache Backend
1 - 10000mongo1
10000 - 20000mongo2
> 20000mongo3

最简单的方式即是为模型类添加一个静态的方法,此方法中我们指定要使用的缓存:

The easiest way is adding an static method to the model that chooses the right cache to be used:

  1. <?php
  2. use Phalcon\Mvc\Model;
  3. class Robots extends Model
  4. {
  5. public static function queryCache($initial, $final)
  6. {
  7. if ($initial >= 1 && $final < 10000) {
  8. return self::find(array(
  9. 'id >= ' . $initial . ' AND id <= '.$final,
  10. 'cache' => array('service' => 'mongo1')
  11. ));
  12. }
  13. if ($initial >= 10000 && $final <= 20000) {
  14. return self::find(array(
  15. 'id >= ' . $initial . ' AND id <= '.$final,
  16. 'cache' => array('service' => 'mongo2')
  17. ));
  18. }
  19. if ($initial > 20000) {
  20. return self::find(array(
  21. 'id >= ' . $initial,
  22. 'cache' => array('service' => 'mongo3')
  23. ));
  24. }
  25. }
  26. }

这个方法是可以解决问题,不过如果我们需要添加其它的参数比如排序或条件等我们还要创建更复杂的方法。另外当我们使用find/findFirst来查询关联数据时此方法亦会失效:

This approach solves the problem, however, if we want to add other parameters such orders or conditions we would have to create a more complicated method. Additionally, this method does not work if the data is obtained using related records or a find/findFirst:

  1. <?php
  2. $robots = Robots::find('id < 1000');
  3. $robots = Robots::find('id > 100 AND type = "A"');
  4. $robots = Robots::find('(id > 100 AND type = "A") AND id < 2000');
  5. $robots = Robots::find(array(
  6. '(id > ?0 AND type = "A") AND id < ?1',
  7. 'bind' => array(100, 2000),
  8. 'order' => 'type'
  9. ));

为了实现这个我们需要拦截中间语言解析,然后书写相关的代码以定制缓存: 首先我们需要创建自定义的创建器,然后我们可以使用它来创建守全自己定义的查询:

To achieve this we need to intercept the intermediate representation (IR) generated by the PHQL parser and thus customize the cache everything possible:

The first is create a custom builder, so we can generate a totally customized query:

  1. <?php
  2. use Phalcon\Mvc\Model\Query\Builder as QueryBuilder;
  3. class CustomQueryBuilder extends QueryBuilder
  4. {
  5. public function getQuery()
  6. {
  7. $query = new CustomQuery($this->getPhql());
  8. $query->setDI($this->getDI());
  9. return $query;
  10. }
  11. }

这里我们返回的是CustomQuery而不是不直接的返回Phalcon\Mvc\Model\Query, 类定义如下所示:

Instead of directly returning a Phalcon\Mvc\Model\Query, our custom builder returns a CustomQuery instance, this class looks like:

  1. <?php
  2. use Phalcon\Mvc\Model\Query as ModelQuery;
  3. class CustomQuery extends ModelQuery
  4. {
  5. /**
  6. * The execute method is overridden
  7. */
  8. public function execute($params=null, $types=null)
  9. {
  10. //Parse the intermediate representation for the SELECT
  11. $ir = $this->parse();
  12. //Check if the query has conditions
  13. if (isset($ir['where'])) {
  14. //The fields in the conditions can have any order
  15. //We need to recursively check the conditions tree
  16. //to find the info we're looking for
  17. $visitor = new CustomNodeVisitor();
  18. //Recursively visits the nodes
  19. $visitor->visit($ir['where']);
  20. $initial = $visitor->getInitial();
  21. $final = $visitor->getFinal();
  22. //Select the cache according to the range
  23. //...
  24. //Check if the cache has data
  25. //...
  26. }
  27. //Execute the query
  28. $result = $this->_executeSelect($ir, $params, $types);
  29. //cache the result
  30. //...
  31. return $result;
  32. }
  33. }

这里我们实现了一个帮助类用以递归的的检查条件以查询字段用以识我们知了需要使用缓存的范围(即检查条件以确认实施查询缓存的范围):

Implementing a helper (CustomNodeVisitor) that recursively checks the conditions looking for fields that tell us the possible range to be used in the cache:

  1. <?php
  2. class CustomNodeVisitor
  3. {
  4. protected $_initial = 0;
  5. protected $_final = 25000;
  6. public function visit($node)
  7. {
  8. switch ($node['type']) {
  9. case 'binary-op':
  10. $left = $this->visit($node['left']);
  11. $right = $this->visit($node['right']);
  12. if (!$left || !$right) {
  13. return false;
  14. }
  15. if ($left=='id') {
  16. if ($node['op'] == '>') {
  17. $this->_initial = $right;
  18. }
  19. if ($node['op'] == '=') {
  20. $this->_initial = $right;
  21. }
  22. if ($node['op'] == '>=') {
  23. $this->_initial = $right;
  24. }
  25. if ($node['op'] == '<') {
  26. $this->_final = $right;
  27. }
  28. if ($node['op'] == '<=') {
  29. $this->_final = $right;
  30. }
  31. }
  32. break;
  33. case 'qualified':
  34. if ($node['name'] == 'id') {
  35. return 'id';
  36. }
  37. break;
  38. case 'literal':
  39. return $node['value'];
  40. default:
  41. return false;
  42. }
  43. }
  44. public function getInitial()
  45. {
  46. return $this->_initial;
  47. }
  48. public function getFinal()
  49. {
  50. return $this->_final;
  51. }
  52. }

最后,我们替换Robots模型中的查询方法以使用我们创建的自定义类:

Finally, we can replace the find method in the Robots model to use the custom classes we’ve created:

  1. <?php
  2. use Phalcon\Mvc\Model;
  3. class Robots extends Model
  4. {
  5. public static function find($parameters=null)
  6. {
  7. if (!is_array($parameters)) {
  8. $parameters = array($parameters);
  9. }
  10. $builder = new CustomQueryBuilder($parameters);
  11. $builder->from(get_called_class());
  12. if (isset($parameters['bind'])) {
  13. return $builder->getQuery()->execute($parameters['bind']);
  14. } else {
  15. return $builder->getQuery()->execute();
  16. }
  17. }
  18. }

缓存PHQL查询计划 Caching of PHQL planning

像大多数现代的操作系统一样PHQL内部会缓存执行计划,如果同样的语句多次执行,PHQL会使用之前生成的查询计划以提升系统的性能, 对开发者来说只采用绑定参数的形式传递参数即可实现:

As well as most moderns database systems PHQL internally caches the execution plan, if the same statement is executed several times PHQL reuses the previously generated plan improving performance, for a developer to take better advantage of this is highly recommended build all your SQL statements passing variable parameters as bound parameters:

  1. <?php
  2. for ($i = 1; $i <= 10; $i++) {
  3. $phql = "SELECT * FROM Store\Robots WHERE id = " . $i;
  4. $robots = $this->modelsManager->executeQuery($phql);
  5. //...
  6. }

上面的例子中,Phalcon产生了10个查询计划,这导致了应用的内存使用量增加。重写以上代码,我们使用绑定参数的这个优点可以减少系统和数据库的过多操作:

In the above example, ten plans were generated increasing the memory usage and processing in the application. Rewriting the code to take advantage of bound parameters reduces the processing by both ORM and database system:

  1. <?php
  2. $phql = "SELECT * FROM Store\Robots WHERE id = ?0";
  3. for ($i = 1; $i <= 10; $i++) {
  4. $robots = $this->modelsManager->executeQuery($phql, array($i));
  5. //...
  6. }

重用PHQL查询也可以提高性能:

Performance can be also improved reusing the PHQL query:

  1. <?php
  2. $phql = "SELECT * FROM Store\Robots WHERE id = ?0";
  3. $query = $this->modelsManager->createQuery($phql);
  4. for ($i = 1; $i <= 10; $i++) {
  5. $robots = $query->execute($phql, array($i));
  6. //...
  7. }

prepared statements 的查询计划亦可以被大多数的数据库所缓存,这样可以减少执行的时间,也可以使用我们的系统免受`SQL Injections`_的影响。

Execution plans for queries involving prepared statements are also cached by most database systems reducing the overall execution time, also protecting your application against SQL Injections.