后臺接口絕大數情況下,都需要與數據庫進行交互,以獲取業務數據或者接收保存客戶端上報的數據。為方便后臺開發同學進行調試,以及實時查看全部執行的SQL語句,這里簡單地對全部執行的SQL語句進行調試模式下輸出。
##2.13.1 開啟SQL調試
開啟調試模式很簡單,但這里和通常的框架不一樣,我們不是全部統一地開啟調試模式,因為在接口正常調用情況下返回非法的JSON會導致接口結果解析失敗。故我們通過添加調試參數來控制是否開啟SQL調試。如下:
```javascript
//$vim ./Public/init.php
//數據操作 - 基于NotORM
DI()->notorm = function() {
$debug = isset($_GET['debug']) ? true : false;
return new PhalApi_DB_NotORM(DI()->config->get('dbs'), $debug);
};
```
> 特別注意:
> 通常,我們的調試參數不應都簡單地使用&debug=1,而是各自定義,如 **復雜一點:&__phalapi_debug__=1** ,或者再添加一個簡單的驗簽,額外帶個參數校驗,如:&__phalapi__sign__=202cb962ac59075b964b07152d234b70。減少暴露SQL的風險。
##2.13.2 調試示例
回到前面獲取用戶基本信息接口 /demo/?service=User.GetBaseInfo 的示例。
###(1)正常情況下
請求:
```javascript
http://dev.phalapi.com/demo/?service=User.GetBaseInfo&user_id=1
```
返回:
```javascript
{"ret":200,"data":{"code":0,"msg":"","info":{"id":"1","name":"dogstar","note":"oschina"}},"msg":""}
```
###(2)帶&debug=1調試下
請求:
```
http://dev.phalapi.com/demo/?service=User.GetBaseInfo&user_id=1&debug=1
```
返回:
```javascript
[1 - 0.00057s]SELECT * FROM tbl_user WHERE (id = ?); -- 1
{"ret":200,"data":{"code":0,"msg":"","info":{"id":"1","name":"dogstar","note":"oschina"}},"msg":""}
```
##2.13.3 一個錯誤的接口開發
有時,在進行接口開發時,會需要進行批量獲取的功能,如列表。但很多開發的同學可能會因為時間趕或者沒有意識去對SQL查詢進行優化,或者甚至不知道自己的接口背后隱藏著多少問題。下面是一個錯誤的開發示例。
###(1)新增的批量獲取接口
假設我們在開發一個國際的項目,并且運行良好,BOSS說因業務需要,要加多一個接口以支持批量獲取用戶的基本信息,提供給國外某知名的社交平臺調用。
于是乎,我們很快就根據原來的單個獲取接口實現了新的接口:
```javascript
//$vim ./Demo/Api/User.php
<?php
class Api_User extends PhalApi_Api {
public function getRules() {
return array(
//...
'getMultiBaseInfo' => array(
'user_ids' => array('name' => 'user_ids', 'type' => 'array', 'format' => 'explode', 'require' => true),
),
);
}
//...
public function getMultiBaseInfo() {
$rs = array('code' => 0, 'msg' => '', 'list' => array());
$domain = new Domain_User();
foreach ($this->user_ids as $userId) {
$rs['list'][] = $domain->getBaseInfo($userId);
}
return $rs;
}
}
```
###(2)運行調用一下
顯然,我們可以很清楚地調用新增的接口:
```
http://dev.phalapi.com/demo/?service=User.GetMultiBaseInfo&user_ids=1,2,3
```
可返回:
```javascript
{
"ret": 200,
"data": {
"code": 0,
"msg": "",
"list": [
{
"id": "1",
"name": "dogstar",
"note": "oschina"
},
{
"id": "2",
"name": "Tom",
"note": "USA"
},
{
"id": "3",
"name": "King",
"note": "game"
}
]
},
"msg": ""
}
```
假設我們已經有了這樣的數據庫表數據:
```javascript
INSERT INTO `tbl_user` VALUES ('1', 'dogstar', 'oschina');
INSERT INTO `tbl_user` VALUES ('2', 'Tom', 'USA');
INSERT INTO `tbl_user` VALUES ('3', 'King', 'game');
```
###(3)這樣的問題?
這樣的問題,在對外黑盒調用的客戶端同學是發現不了的,對于測試人員來說也是無法感知的。但所犯的錯誤也是顯然易見的,就是沒有進行SQL的批量查詢優化,造成了很多不必要的重復查詢。
這里,根據后臺接口開發人員提供的調試參數(假設為:&debug=1),則我們可以快速發現存在的問題:
```
http://dev.phalapi.com/demo/?service=User.GetMultiBaseInfo&user_ids=1,2,3&debug=1
```
如下返回,我們看到了很多重復類似的查詢語句。
```javascript
[1 - 0.0005s]SELECT * FROM tbl_user WHERE (id = ?); -- 1
[2 - 0.00042s]SELECT * FROM tbl_user WHERE (id = ?); -- 2
[3 - 0.00038s]SELECT * FROM tbl_user WHERE (id = ?); -- 3
{"ret":200,"data":{"code":0,"msg":"","list":[{"id":"1","name":"dogstar","note":"oschina"},{"id":"2","name":"Tom","note":"USA"},{"id":"3","name":"King","note":"game"}]},"msg":""}
```
上面輸出的調試信息,簡單補充一個格式:
```javascript
[序號 - 所耗時間]SQl語句 -- [參數1, 參數2]
```
###(4)如何改進?
這是一個很基本的問題,當然在實際項目中不會普通存在,這里只是作為一個示例加以說明。但讓人失望的是,實際項目確實存在為數不少的這樣的情況。可能是新人的技術和意識問題,也有可能是老同學的態度問題。所以,優化這么一個接口的批量SQL查詢不難,難的是如何才能讓新、老同學都注重這塊的SQL查詢優化呢?而不是等到線上服務器異常崩潰后再來推托責任。
具體的代碼改進,留給讀者自己實踐了。畢竟,看了,實踐了,才會真正深刻地掌握。
##2.13.4 由此引申
+ 這里不專門講述SQL的優化,但也順便提供一些SQL查詢優化的建議:
+ 使用批量查詢,而不是N次循環查詢!
+ 重復的數據,不要重復獲取;
+ 根據需要,按需要獲取表字段,而不是SELECT *;
+ 針對頻繁的搜索字段,建立必要的索引,以加快查詢速度;
+ 使用關聯查詢,而不是粗暴地類似:where uid IN (... 這里是成千上W個用戶ID ...);
+ 針對單條SQL語句執行時間超過1秒的,重點優化;
##2.13.5 最后最后
奉上我們堅持TDD開發下的單元測試代碼:
```javascript
public function testGetMultiBaseInfo()
{
$str = 'service=User.GetMultiBaseInfo&user_ids=1,2,3';
parse_str($str, $params);
DI()->request = new PhalApi_Request($params);
$api = new Api_User();
//自己進行初始化
$api->init();
$rs = $api->getMultiBaseInfo();
$this->assertNotEmpty($rs);
$this->assertArrayHasKey('code', $rs);
$this->assertArrayHasKey('msg', $rs);
$this->assertArrayHasKey('list', $rs);
foreach ($rs['list'] as $item) {
$this->assertArrayHasKey('id', $item);
$this->assertArrayHasKey('name', $item);
$this->assertArrayHasKey('note', $item);
}
}
```
執行單元測試的效果:
```javascript
dogstar@ubuntu:Tests$ phpunit --filter testGetMultiBaseInfo ./Api/Api_User_Test.php
PHPUnit 4.3.4 by Sebastian Bergmann.
.
Time: 23 ms, Memory: 6.25Mb
OK (1 test, 13 assertions)
```
搞定,收工,開飯!
- 歡迎使用PhalApi!
- 接口,從簡單開始!
- [1.1]-下載與安裝
- [1.2]-創建一個自己的項目
- [1.3]-在線體驗
- [1.4]-文檔、幫助和官網
- [1.10]-對PhalApi框架的抉擇
- [1.11]-快速入門(backup)
- [1.12]-參數規則:接口參數規則配置
- [1.13]-統一的接口請求方式:_sevice=XXX.XXX
- [1.14]-統一的返回格式和結構:ret-data-msg
- [1.15]-數據庫操作:基于NotORM的使用及優化
- [1.16]-配置讀取:內外網環境配置的完美切換
- [1.17]-日記紀錄:簡化版的日記接口
- [1.18]-快速函數:人性化的關懷
- [1.19]-DI服務速查:各資源服務一覽表
- [1.20]-DB操作:數據庫基本操作速查
- [1.21]-類的自動加載:遵循PEAR包的命名規范
- [1.22]-簽名驗證:自定義簽名規則
- [1.23]-請求和響應:GET和POST兩者皆可得及超越JSON格式返回
- [1.24]-緩存策略:更靈活地可配置化的多級緩存
- [1.25]-國際化翻譯:為走向國際化提前做好翻譯準備
- [1.26]-數據安全:數據對稱加密方案
- [1.27]-精益開發:更富表現力的Model層和重量級數據獲取的應對方案
- [1.28]-COOKIE:對COOKIE原生態的支持及記憶加密升級版
- [1.29]-開放與封閉:多入口和統一初始化
- [1.30]-保持的力量:接口開發最佳實踐
- [1.31]-新型計劃任務:以接口形式實現的計劃任務
- [2.11]-核心思想:DI依賴注入-讓資源更可控
- [2.12]-海量數據:可配置的分庫分表
- [2.13]-接口調試:在線SQL語句查看與性能優化
- [2.14]-測試驅動開發:意圖導向編程下的接口開發
- [2.15]-演進:新型計劃任務續篇
- [2.16]-領域驅動設計:應對復雜領域業務的Domain層
- [2.17]-微服務:Api接口服務層
- [2.18]-定制化:資源服務的再實現
- [2.19]-擴展庫:可重用的擴展類庫
- [2.20]-約定編程:架構明顯的編程風格
- [2.21]-服務器統一部署方案簡明版:CentOs---Nginx---php-fpm---MySql-[--Memcached]
- [2.22]-更多工具:精益項目和團隊建設
- [3.1]-擴展類庫:微信開發
- [3.2]-擴展類庫:代理模式下phprpc協議的輕松支持
- [3.3]-擴展類庫:基于PHPMailer的郵件發送
- [3.4]-擴展類庫:優酷開放平臺接口調用
- [3.5]-擴展類庫:七牛云存儲接口調用
- [3.6]-擴展類庫:新型計劃任務
- [3.8]-擴展類庫:用戶、會話和第三方登錄集成
- [3.9]-擴展類庫:swoole支持下的長鏈接和異步任務實現
- [3.11]-擴展類庫:基于FastRoute的快速路由
- [4.2]-開發實戰2:模擬優酷開放平臺接口項目開發
- [4.3]-開發實戰3:一個簡單的小型項目開發(奔跑吧兄弟投票活動)
- [5.1]-架構與思想:PhalApi核心設計和思想解讀
- [5.2]-雜談:扯一些PhalApi的前世和今生
- [5.3]-框架總結:術語表和PHP開發建議
- [5.4]-許可
- [5.5]-聯系和加入我們
- [5.6]-更新日記
- [5.8]-致框架貢獻者:加入PhalApi開源指南
- [6.1]-基于接口查詢語言的SDK包
- [6.2]-SDK包(JAVA版)
- [6.3]-SDK包(PHP版)
- [6.4]-SDK包(Objective-C版)
- [6.5]-SDK包(javascript版)
- [6.6]-SDK包(Ruby版)
- [8.1]-PhalApi視頻教程
- 附錄1:接口文檔參考模板