# 緩存對象關系映射(Caching in the ORM)
現實中的每個應用都不同,一些應用的模型數據經常改變而另一些模型的數據幾乎不同。訪問數據庫在很多時候對我們應用的來說 是個瓶頸。這是由于我們每次訪問應用時都會和數據庫數據通信,和數據庫進行通信的代價是很大的。因此在必要時我們可以通過增加 緩存層來獲取更高的性能。 本章內容的重點即是探討實施緩存來提高性能的可行性。Phalcon框架給我們提供了靈活的緩存技術來實現我們的應用緩存。
## 緩存結果集(Caching Resultsets)
一個非常可行的方案是我們可以為那些不經常改變且經常訪問的數據庫數據進行緩存,比如把他們放入內存,這樣可以加快程序的執行速度。
當[Phalcon\\Mvc\\Model](http://docs.iphalcon.cn/api/Phalcon_Mvc_Model.html)需要使用緩存數據的服務時Model可以直接從DI中取得此緩存服務modelsCache(慣例名).
Phalcon提供了一個組件(服務)可以用來[緩存](http://docs.iphalcon.cn/reference/cache.html)任何種類的數據,下面我們會解釋如何在model使用它。第一步我們要在啟動文件注冊 這個服務:
~~~
<?php
use Phalcon\Cache\Frontend\Data as FrontendData;
use Phalcon\Cache\Backend\Memcache as BackendMemcache;
// 設置模型緩存服務
$di->set(
"modelsCache",
function () {
// 默認緩存時間為一天
$frontCache = new FrontendData(
[
"lifetime" => 86400,
]
);
// Memcached連接配置 這里使用的是Memcache適配器
$cache = new BackendMemcache(
$frontCache,
[
"host" => "localhost",
"port" => "11211",
]
);
return $cache;
}
);
~~~
在注冊緩存服務時我們可以按照我們的需要進行配置。一旦完成正確的緩存設置之后,我們可以按如下的方式緩存查詢的結果了:
~~~
<?php
// 直接取Products模型里的數據(未緩存)
$products = Products::find();
// 緩存查詢結果.緩存時間為默認1天。
$products = Products::find(
[
"cache" => [
"key" => "my-cache",
],
]
);
// 緩存查詢結果時間為300秒
$products = Products::find(
[
"cache" => [
"key" => "my-cache",
"lifetime" => 300,
],
]
);
// Use the 'cache' service from the DI instead of 'modelsCache'
$products = Products::find(
[
"cache" => [
"key" => "my-cache",
"service" => "cache",
],
]
);
這里我們也可以緩存關聯表的數據:
~~~
~~~
<?php
// Query some post
$post = Post::findFirst();
// Get comments related to a post, also cache it
$comments = $post->getComments(
[
"cache" => [
"key" => "my-key",
],
]
);
// Get comments related to a post, setting lifetime
$comments = $post->getComments(
[
"cache" => [
"key" => "my-key",
"lifetime" => 3600,
],
]
);
~~~
如果想刪除已經緩存的結果,則只需要使用前面指定的緩存的鍵值進行刪除即可。
注意并不是所有的結果都必須緩存下來。那些經常改變的數據就不應該被緩存,這樣做只會影響應用的性能。另外對于那些特別大的 不易變的數據集,開發者應用根據實際情況進行選擇是否進行緩存。
## 強制緩存(Forcing Cache)
前面的例子中我們在[Phalcon\\Mvc\\Model](http://docs.iphalcon.cn/api/Phalcon_Mvc_Model.html)中使用框架內建的緩存組件。為實現強制緩存我們傳遞了cache作為參數:
~~~
<?php
// 緩存查詢結果5分鐘
$products = Products::find(
[
"cache" => [
"key" => "my-cache",
"lifetime" => 300,
],
]
);
~~~
這給了我們自由選擇需要緩存的查詢結果,但是如果我們想對模型中的所有查詢結果進行緩存,那么我們可以重寫:code:find()/:code:[`](http://docs.iphalcon.cn/reference/models-cache.html#id1)findFirst()`方法:
~~~
<?php
use Phalcon\Mvc\Model;
class Robots extends Model
{
/**
* Implement a method that returns a string key based
* on the query parameters
*/
protected static function _createKey($parameters)
{
$uniqueKey = [];
foreach ($parameters as $key => $value) {
if (is_scalar($value)) {
$uniqueKey[] = $key . ":" . $value;
} elseif (is_array($value)) {
$uniqueKey[] = $key . ":[" . self::_createKey($value) . "]";
}
}
return join(",", $uniqueKey);
}
public static function find($parameters = null)
{
// Convert the parameters to an array
if (!is_array($parameters)) {
$parameters = [$parameters];
}
// Check if a cache key wasn't passed
// and create the cache parameters
if (!isset($parameters["cache"])) {
$parameters["cache"] = [
"key" => self::_createKey($parameters),
"lifetime" => 300,
];
}
return parent::find($parameters);
}
public static function findFirst($parameters = null)
{
// ...
}
}
~~~
訪問數據要遠比計算key值慢的多,我們在這里定義自己需要的key生成方式。注意好的鍵可以避免沖突,這樣就可以依據不同的key值 取得不同的緩存結果。
這樣我們可以對每個模型的緩存進行完全的控制,如果其他的模型也需要共用此緩存,可以建立一個模型緩存基類:
~~~
<?php
use Phalcon\Mvc\Model;
class CacheableModel extends Model
{
protected static function _createKey($parameters)
{
// ... Create a cache key based on the parameters
}
public static function find($parameters = null)
{
// ... Custom caching strategy
}
public static function findFirst($parameters = null)
{
// ... Custom caching strategy
}
}
~~~
然后把這個類作為其它緩存類的基類:
~~~
<?php
class Robots extends CacheableModel
{
}
~~~
## 緩存 PHQL 查詢(Caching PHQL Queries)
ORM中的所有查詢,不論多高級的查詢方法,內部都是通過PHQL進行實現的。PHQL可以讓我們非常自由的創建各種查詢,當然這些查詢也可以被緩存:
~~~
<?php
$phql = "SELECT * FROM Cars WHERE name = :name:";
$query = $this->modelsManager->createQuery($phql);
$query->cache(
[
"key" => "cars-by-name",
"lifetime" => 300,
]
);
$cars = $query->execute(
[
"name" => "Audi",
]
);
~~~
## 可重用的相關記錄(Reusable Related Records)
一些模型可能與其他模型之間有關聯關系。下面的例子可以讓我們非常容易的在內存中檢索相關聯的數據:
~~~
<?php
// Get some invoice
$invoice = Invoices::findFirst();
// Get the customer related to the invoice
$customer = $invoice->customer;
// Print his/her name
echo $customer->name, "\n";
~~~
這個例子非常簡單,依據查詢到的訂單信息取得用戶信息之后再取得用戶名。下面的例子也是如此:我們查詢了一些訂單的信息,然后取得這些訂單相關聯 用戶的信息,之后取得用戶名:
~~~
<?php
// Get a set of invoices
// SELECT * FROM invoices;
$invoices = Invoices::find();
foreach ($invoices as $invoice) {
// Get the customer related to the invoice
// SELECT * FROM customers WHERE id = ?;
$customer = $invoice->customer;
// Print his/her name
echo $customer->name, "\n";
}
~~~
每個客戶可能會有一個或多個帳單,這就意味著客戶對象沒必須取多次。為了避免一次次的重復取客戶信息,我們這里設置關系為reusable為true, 這樣ORM就可以重復使用客戶信息:
~~~
<?php
use Phalcon\Mvc\Model;
class Invoices extends Model
{
public function initialize()
{
$this->belongsTo(
"customers_id",
"Customer",
"id",
[
"reusable" => true,
]
);
}
}
~~~
此Cache存在于內存中,這意味著當請求結束時緩存數據即被釋放。
## 緩存相關記錄(Caching Related Records)
當使用:code:[`](http://docs.iphalcon.cn/reference/models-cache.html#id3)find()`或:code:[`](http://docs.iphalcon.cn/reference/models-cache.html#id5)findFirst()`查詢關聯數據時,ORM內部會自動的依據以下規則創建查詢條件:
| 類型 | 描述 | 隱含方法 |
| --- | --- | --- |
| Belongs-To | 直接的返回模型相關的記錄 | `findFirst()` |
| Has-One | 直接的返回模型相關的記錄 | `findFirst()` |
| Has-Many | 返回模型相關的記錄集合 | `find()` |
這意味著當我們取得關聯記錄時,我們需要解析如何取得數據的方法:
~~~
<?php
// Get some invoice
$invoice = Invoices::findFirst();
// Get the customer related to the invoice
$customer = $invoice->customer; // Invoices::findFirst("...");
// Same as above
$customer = $invoice->getCustomer(); // Invoices::findFirst("...");
~~~
因此,我們可以替換掉Invoices模型中的:code:[`](http://docs.iphalcon.cn/reference/models-cache.html#id7)findFirst()`方法然后實現我們使用適合的方法
~~~
<?php
use Phalcon\Mvc\Model;
class Invoices extends Model
{
public static function findFirst($parameters = null)
{
// ... Custom caching strategy
}
}
~~~
## 遞歸緩存相關記錄(Caching Related Records Recursively)
在這種場景下我們假定我們每次取主記錄時都會取模型的關聯記錄,如果我們此時保存這些記錄可能會為我們的系統帶來一些性能上的提升:
~~~
<?php
use Phalcon\Mvc\Model;
class Invoices extends Model
{
protected static function _createKey($parameters)
{
// ... Create a cache key based on the parameters
}
protected static function _getCache($key)
{
// Returns data from a cache
}
protected static function _setCache($key, $results)
{
// Stores data in the cache
}
public static function find($parameters = null)
{
// Create a unique key
$key = self::_createKey($parameters);
// Check if there are data in the cache
$results = self::_getCache($key);
// Valid data is an object
if (is_object($results)) {
return $results;
}
$results = [];
$invoices = parent::find($parameters);
foreach ($invoices as $invoice) {
// Query the related customer
$customer = $invoice->customer;
// Assign it to the record
$invoice->customer = $customer;
$results[] = $invoice;
}
// Store the invoices in the cache + their customers
self::_setCache($key, $results);
return $results;
}
public function initialize()
{
// Add relations and initialize other stuff
}
}
~~~
從已經緩存的訂單中取得用戶信息,可以減少系統的負載。注意我們也可以使用PHQL來實現這個,下面使用了PHQL來實現:
~~~
<?php
use Phalcon\Mvc\Model;
class Invoices extends Model
{
public function initialize()
{
// Add relations and initialize other stuff
}
protected static function _createKey($conditions, $params)
{
// ... Create a cache key based on the parameters
}
public function getInvoicesCustomers($conditions, $params = null)
{
$phql = "SELECT Invoices.*, Customers.* FROM Invoices JOIN Customers WHERE " . $conditions;
$query = $this->getModelsManager()->executeQuery($phql);
$query->cache(
[
"key" => self::_createKey($conditions, $params),
"lifetime" => 300,
]
);
return $query->execute($params);
}
}
~~~
## 基于條件的緩存(Caching based on Conditions)
此例中,根據不同的條件實施緩存. We might decide that the cache backend should be determined by the primary key:
| 類型 | 緩存 |
| --- | --- |
| 1 - 10000 | mongo1 |
| 10000 - 20000 | mongo2 |
| \> 20000 | mongo3 |
最簡單的方式即是為模型類添加一個靜態的方法,此方法中我們指定要使用的緩存:
~~~
<?php
use Phalcon\Mvc\Model;
class Robots extends Model
{
public static function queryCache($initial, $final)
{
if ($initial >= 1 && $final < 10000) {
$service = "mongo1";
} elseif ($initial >= 10000 && $final <= 20000) {
$service = "mongo2";
} elseif ($initial > 20000) {
$service = "mongo3";
}
return self::find(
[
"id >= " . $initial . " AND id <= " . $final,
"cache" => [
"service" => $service,
],
]
);
}
}
~~~
這個方法是可以解決問題,不過如果我們需要添加其它的參數比如排序或條件等等,我們還要創建更復雜的方法。另外當我們使用:code:find()/:code:[`](http://docs.iphalcon.cn/reference/models-cache.html#id9)findFirst()`來查詢關聯數據時此方法亦會失效:
~~~
<?php
$robots = Robots::find("id < 1000");
$robots = Robots::find("id > 100 AND type = 'A'");
$robots = Robots::find("(id > 100 AND type = 'A') AND id < 2000");
$robots = Robots::find(
[
"(id > ?0 AND type = 'A') AND id < ?1",
"bind" => [100, 2000],
"order" => "type",
]
);
~~~
為了實現這個,我們需要攔截中間語言解析,然后書寫相關的代碼以定制緩存: 首先我們需要創建自定義的創建器,然后我們可以使用它來創建自己定義的查詢:
~~~
<?php
use Phalcon\Mvc\Model\Query\Builder as QueryBuilder;
class CustomQueryBuilder extends QueryBuilder
{
public function getQuery()
{
$query = new CustomQuery($this->getPhql());
$query->setDI($this->getDI());
return $query;
}
}
~~~
這里我們返回的是CustomQuery而不是不直接的返回[Phalcon\\Mvc\\Model\\Query](http://docs.iphalcon.cn/api/Phalcon_Mvc_Model_Query.html), 類定義如下所示:
~~~
<?php
use Phalcon\Mvc\Model\Query as ModelQuery;
class CustomQuery extends ModelQuery
{
/**
* The execute method is overridden
*/
public function execute($params = null, $types = null)
{
// Parse the intermediate representation for the SELECT
$ir = $this->parse();
// Check if the query has conditions
if (isset($ir["where"])) {
// The fields in the conditions can have any order
// We need to recursively check the conditions tree
// to find the info we're looking for
$visitor = new CustomNodeVisitor();
// Recursively visits the nodes
$visitor->visit($ir["where"]);
$initial = $visitor->getInitial();
$final = $visitor->getFinal();
// Select the cache according to the range
// ...
// Check if the cache has data
// ...
}
// Execute the query
$result = $this->_executeSelect($ir, $params, $types);
// Cache the result
// ...
return $result;
}
}
~~~
這里我們實現了一個幫助類,用遞歸的方式來檢查條件中的查詢字段,方便我們了解需要使用緩存的范圍(即檢查條件以確認實施查詢緩存的范圍):
~~~
<?php
class CustomNodeVisitor
{
protected $_initial = 0;
protected $_final = 25000;
public function visit($node)
{
switch ($node["type"]) {
case "binary-op":
$left = $this->visit($node["left"]);
$right = $this->visit($node["right"]);
if (!$left || !$right) {
return false;
}
if ($left === "id") {
if ($node["op"] === ">") {
$this->_initial = $right;
}
if ($node["op"] === "=") {
$this->_initial = $right;
}
if ($node["op"] === ">=") {
$this->_initial = $right;
}
if ($node["op"] === "<") {
$this->_final = $right;
}
if ($node["op"] === "<=") {
$this->_final = $right;
}
}
break;
case "qualified":
if ($node["name"] === "id") {
return "id";
}
break;
case "literal":
return $node["value"];
default:
return false;
}
}
public function getInitial()
{
return $this->_initial;
}
public function getFinal()
{
return $this->_final;
}
}
~~~
最后,我們替換Robots模型中的查詢方法,以使用我們創建的自定義類:
~~~
<?php
use Phalcon\Mvc\Model;
class Robots extends Model
{
public static function find($parameters = null)
{
if (!is_array($parameters)) {
$parameters = [$parameters];
}
$builder = new CustomQueryBuilder($parameters);
$builder->from(get_called_class());
$query = $builder->getQuery();
if (isset($parameters["bind"])) {
return $query->execute($parameters["bind"]);
} else {
return $query->execute();
}
}
}
~~~
## 緩存 PHQL 查詢計劃(Caching of PHQL planning)
像大多數現代的操作系統一樣PHQL內部會緩存執行計劃,如果同樣的語句多次執行,PHQL會使用之前生成的查詢計劃以提升系統的性能, 對開發者來說只采用綁定參數的形式傳遞參數即可實現:
~~~
<?php
for ($i = 1; $i <= 10; $i++) {
$phql = "SELECT * FROM Store\Robots WHERE id = " . $i;
$robots = $this->modelsManager->executeQuery($phql);
// ...
}
~~~
上面的例子中,Phalcon產生了10個查詢計劃,這導致了應用的內存使用量增加。重寫以上代碼,我們使用綁定參數的這個優點可以減少系統和數據庫的過多操作:
~~~
<?php
$phql = "SELECT * FROM Store\Robots WHERE id = ?0";
for ($i = 1; $i <= 10; $i++) {
$robots = $this->modelsManager->executeQuery(
$phql,
[
$i,
]
);
// ...
}
~~~
使用PHQL查詢亦可以提升查詢性能:
~~~
<?php
$phql = "SELECT * FROM Store\Robots WHERE id = ?0";
$query = $this->modelsManager->createQuery($phql);
for ($i = 1; $i <= 10; $i++) {
$robots = $query->execute(
$phql,
[
$i,
]
);
// ...
}
~~~
[預先準備的查詢語句](http://en.wikipedia.org/wiki/Prepared_statement)的查詢計劃亦可以被大多數的數據庫所緩存,這樣可以減少執行的時間,也可以使我們的系統免受[SQL注入](http://en.wikipedia.org/wiki/SQL_injection)的影響。
- 簡介
- 安裝
- 安裝(installlation)
- XAMPP下的安裝
- WAMP下安裝
- Nginx安裝說明
- Apache安裝說明
- Cherokee 安裝說明
- 使用 PHP 內置 web 服務器
- Phalcon 開發工具
- Linux 系統下使用 Phalcon 開發工具
- Mac OS X 系統下使用 Phalcon 開發工具
- Windows 系統下使用 Phalcon 開發工具
- 教程
- 教程 1:讓我們通過例子來學習
- 教程 2:INVO簡介
- 教程 3: 保護INVO
- 教程4: 使用CRUD
- 教程5: 定制INVO
- 教程 6: V?kuró
- 教程 7:創建簡單的 REST API
- 組件
- 依賴注入與服務定位器
- MVC架構
- 使用控制器
- 使用模型
- 模型關系
- 事件與事件管理器
- Behaviors
- 模型元數據
- 事務管理
- 驗證數據完整性
- Workingwith Models
- Phalcon查詢語言
- 緩存對象關系映射
- 對象文檔映射 ODM
- 使用視圖
- 視圖助手
- 資源文件管理
- Volt 模版引擎
- MVC 應用
- 路由
- 調度控制器
- Micro Applications
- 使用命名空間
- 事件管理器
- Request Environmen
- 返回響應
- Cookie 管理
- 生成 URL 和 路徑
- 閃存消息
- 使用 Session 存儲數據
- 過濾與清理
- 上下文編碼
- 驗證Validation
- 表單_Forms
- 讀取配置
- 分頁 Pagination
- 使用緩存提高性能
- 安全
- 加密與解密 Encryption/Decryption
- 訪問控制列表
- 多語言支持
- 類加載器 Class Autoloader
- 日志記錄_Logging
- 注釋解析器 Annotations Parser
- 命令行應用 Command Line Applications
- Images
- 隊列 Queueing
- 數據庫抽象層
- 國際化
- 數據庫遷移
- 調試應用程序
- 單元測試
- 進階技巧與延伸閱讀
- 提高性能:下一步該做什么?
- Dependency Injection Explained
- Understanding How Phalcon Applications Work
- Api
- Abstract class Phalcon\Acl