[TOC]
# ORM緩存
每個應用程序都不同。但是,在大多數應用程序中,有些數據不經常更改。性能方面最常見的瓶頸之一是訪問數據庫。這是由于PHP執行的復雜連接/通信過程以及從數據庫獲取數據的每個請求。因此,如果我們想要獲得良好的性能,我們需要在應用程序需要的地方添加一些緩存層。
本章介紹了可以實現緩存以提高性能的潛在領域。Phalcon為開發人員提供了在應用程序需要時實現緩存所需的工具。
## 緩存結果集
避免持續訪問數據庫的一種成熟技術是使用具有更快訪問權限的系統(通常是內存)來緩存不經常更改的結果集。
當`Phalcon\Mvc\Model`需要服務來緩存結果集時,它將從依賴注入容器中請求它。服務名稱稱為modelsCache。Phalcon提供了一個可以存儲任何類型數據的緩存組件。我們現在將看到如何將它與我們的模型集成。
首先,我們需要將緩存組件注冊為DI容器中的服務。
```php
<?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連接設置
$cache = new BackendMemcache(
$frontCache,
[
'host' => 'localhost',
'port' => '11211',
]
);
return $cache;
}
);
```
在將緩存組件注冊為DI容器中的服務之前,Phalcon提供了對緩存組件的創建和自定義的完全控制。正確設置緩存組件后,可以按如下方式緩存結果集:
```php
<?php
// Get products without caching
$products = Products::find();
// Just cache the resultset. The cache will expire in 1 hour (3600 seconds)
$products = Products::find(
[
'cache' => [
'key' => 'my-cache',
],
]
);
// Cache the resultset for only for 5 minutes
$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
<?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,
],
]
);
```
當緩存的結果集需要失效時,您只需使用上面指定的`key`從緩存中刪除它。
在評估了應用程序的需求之后,緩存哪個結果集以及開發人員需要多長時間。不應緩存經常更改的結果集,因為緩存結果將很快失效。此外,緩存結果集會消耗處理周期,因此旨在加速應用程序的緩存實際上會降低其速度。應緩存不經常更改的結果集以最小化數據庫交互。關于在何處使用緩存以及使用多長時間的決定取決于應用程序的需求。
## 強制緩存
之前我們看到了`Phalcon\Mvc\Model`如何與框架提供的緩存組件集成。為了使記錄/結果集可緩存,我們在參數數組中傳遞密鑰緩存:
```php
<?php
// Cache the resultset for only for 5 minutes
$products = Products::find(
[
'cache' => [
'key' => 'my-cache',
'lifetime' => 300,
],
]
);
```
這使我們可以自由地緩存特定查詢,但是如果我們想要全局緩存在模型上執行的每個查詢,我們可以覆蓋`find()`/`findFirst()`方法來強制緩存每個查詢:
```php
<?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盡可能避免沖突 - 這意味著不同的密鑰應返回不相關的記錄。
這使您可以完全控制如何為每個模型實現緩存。如果此策略對于多個模型是通用的,則可以為所有模型創建基類:
```php
<?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
}
}
```
然后將此類用作每個 `Cacheable` 模型的基類:
```php
<?php
class Robots extends CacheableModel
{
}
```
## 緩存PHQL查詢
無論我們用于創建它們的語法如何,ORM中的所有查詢都是使用PHQL在內部處理的。這種語言使您可以更自由地創建各種查詢。當然,這些查詢可以緩存:
```php
<?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',
]
);
```
## 可重復使用的相關記錄
某些模型可能與其他模型有關系。這允許我們輕松檢查與內存中的實例相關的記錄:
```php
<?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
<?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";
}
```
客戶可能有一個或多個賬單,因此,在此示例中,可能會多次不必要地查詢相同的客戶記錄。為避免這種情況,我們可以將關系標記為可重用;通過這樣做,我們告訴ORM自動重用內存中的記錄,而不是一次又一次地重新查詢它們:
```php
<?php
use Phalcon\Mvc\Model;
class Invoices extends Model
{
public function initialize()
{
$this->belongsTo(
'customers_id',
'Customer',
'id',
[
'reusable' => true,
]
);
}
}
```
請注意,此類緩存僅在內存中工作,這意味著在請求終止時釋放緩存數據。
## 緩存相關記錄
查詢相關記錄時,ORM在內部構建適當的條件,并根據下表使用目標模型中的 `find()`/`findFirst()` 獲取所需的記錄:
| 類型 | 描述 | 隱含方法|
| ---------- | --------------------------------------------------------------- | --------------- |
| Belongs-To | 直接返回相關記錄的模型實例 | `findFirst()` |
| Has-One | 直接返回相關記錄的模型實例 | `findFirst()` |
| Has-Many | 返回引用模型的模型實例的集合 | `find()` |
這意味著當您獲得相關記錄時,您可以通過實現相應的方法來攔截數據的獲取方式:
```php
<?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模型中的`findFirst() `方法,并實現我們認為最合適的緩存:
```php
<?php
use Phalcon\Mvc\Model;
class Invoices extends Model
{
public static function findFirst($parameters = null)
{
// ... Custom caching strategy
}
}
```
## 遞歸緩存相關記錄
在這種情況下,我們假設每次查詢結果時,我們也會檢索其關聯的記錄。如果我們存儲與其相關實體一起找到的記錄,也許我們可以減少獲取所有實體所需的開銷:
```php
<?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執行以下替代解決方案:
```php
<?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);
}
}
```
## 基于條件的緩存
在這種情況下,緩存的實現方式取決于收到的條件。我們可能會決定緩存后端應該由主鍵確定:
| Type | Cache Backend |
| ------------- | ------------- |
| 1 - 10000 | mongo1 |
| 10000 - 20000 | mongo2 |
| > 20000 | mongo3 |
實現此目的的最簡單方法是向模型添加靜態方法,以選擇要使用的正確緩存:
```php
<?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,
],
]
);
}
}
```
這種方法解決了這個問題,但是,如果我們想要添加其他參數,例如命令或條件,我們將需要創建一個更復雜的方法。此外,如果使用相關記錄或`find()`/`findFirst()`獲取數據,則此方法不起作用:
```php
<?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',
]
);
```
為了實現這一點,我們需要攔截PHQL解析器生成的中間表示(IR),從而盡可能地自定義緩存:
第一個是創建自定義構建器,因此我們可以生成完全自定義的查詢:
```php
<?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());
if ( is_array($this->_bindParams) ) {
$query->setBindParams($this->_bindParams);
}
if ( is_array($this->_bindTypes) ) {
$query->setBindTypes($this->_bindTypes);
}
if ( is_array($this->_sharedLock) ) {
$query->setSharedLock($this->_sharedLock);
}
return $query;
}
}
```
我們的自定義構建器返回一個CustomQuery實例,而不是直接返回 `Phalcon\Mvc\Model\Query`,這個類看起來像:
```php
<?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();
if ( is_array($this->_bindParams) ) {
$params = array_merge($this->_bindParams, (array)$params);
}
if ( is_array($this->_bindTypes) ) {
$types = array_merge($this->_bindTypes, (array)$types);
}
// 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);
$result = $this->_uniqueRow ? $result->getFirst() : $result;
// Cache the result
// ...
return $result;
}
}
```
實現一個幫助程序(`CustomNodeVisitor`),它遞歸地檢查條件,查找告訴我們在緩存中使用的可能范圍的字段:
```php
<?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模型中的find方法來使用我們創建的自定義類:
```php
<?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執行計劃
與大多數現代數據庫系統一樣,PHQL在內部緩存執行計劃,如果同一語句執行多次PHQL重用以前生成的計劃提高性能,開發人員可以更好地利用這一點,強烈建議構建所有SQL語句傳遞變量參數作為綁定參數:
```php
<?php
for ($i = 1; $i <= 10; $i++) {
$phql = 'SELECT * FROM Store\Robots WHERE id = ' . $i;
$robots = $this->modelsManager->executeQuery($phql);
// ...
}
```
在上面的示例中,生成了10個計劃,增加了應用程序中的內存使用和處理。重寫代碼以利用綁定參數可以減少ORM和數據庫系統的處理:
```php
<?php
$phql = 'SELECT * FROM Store\Robots WHERE id = ?0';
for ($i = 1; $i <= 10; $i++) {
$robots = $this->modelsManager->executeQuery(
$phql,
[
$i,
]
);
// ...
}
```
重用PHQL查詢還可以提高性能:
```php
<?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)。
- 常規
- Welcome
- 貢獻
- 生成回溯
- 測試重現
- 單元測試
- 入門
- 安裝
- Web服務器設置
- WAMP
- XAMPP
- 教程
- 基礎教程
- 教程:創建一個簡單的REST API
- 教程:V?kuró
- 提升性能
- 教程:INVO
- 開發環境
- Phalcon Compose (Docker)
- Nanobox
- Phalcon Box (Vagrant)
- 開發工具
- Phalcon開發者工具的安裝
- Phalcon開發者工具的使用
- 調試應用程序
- 核心
- MVC應用
- 微應用
- 創建命令行(CLI)應用程序
- 依賴注入與服務定位
- MVC架構
- 服務
- 使用緩存提高性能
- 讀取配置
- 上下文轉義
- 類加載器
- 使用命名空間
- 日志
- 隊列
- 數據庫
- 數據庫抽象層
- Phalcon查詢語言(PHQL)
- ODM(對象文檔映射器)
- 使用模型
- 模型行為
- ORM緩存
- 模型事件
- 模型元數據
- 模型關系
- 模型事務
- 驗證模型
- 數據庫遷移
- 分頁
- 前端
- Assets管理
- 閃存消息
- 表單
- 圖像
- 視圖助手(標簽)
- 使用視圖
- Volt:模板引擎
- 業務邏輯
- 訪問控制列表(ACL)
- 注解解析器
- 控制器
- 調度控制器
- 事件管理器
- 過濾與清理
- 路由
- 在session中存儲數據
- 生成URL和路徑
- 驗證
- HTTP
- Cookies管理
- 請求環境
- 返回響應
- 安全
- 加密/解密
- 安全
- 國際化
- 國際化
- 多語言支持