后台接口绝大数情况下,都需要与数据库进行交互,以获取业务数据或者接收保存客户端上报的数据。为方便后台开发同学进行调试,以及实时查看全部执行的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:接口文档参考模板