EduSoho 开发中的最佳实践—性能和安全(一)
EduSoho 汇集了很多开发者的心血,系统在不断发展中越来越完善。一个具有良好性能的系统取决于代码的设计和质量,在此和大家分享几个 EduSoho 开发的小技巧。
一、在代码中降低资源消耗,提高加载速度
程序的性能好坏直接影响到网站的整体体验。在EduSoho开发过程中,如果能正确地使用系统默认提供的一些接口和函数,可以有效地提高代码的执行效率。
1. 使用查询最佳实践
EduSoho 中的 Dao
层提供了一部分内置的查询函数,主要有 get
、search
、findByFields
。简单地按照功能区分如下:
-
单行数据查询:
get
,性能提升的空间不大 -
多条数据查询:
search
、findByFields
,有性能提升的空间
1.1 search
search 函数接口
search($conditions, $orderBys, $start, $limit, $columns = array())
search 使用场景:在一些数据量不可控、需要排序、需要分页限制的场景中。
场景1 列表展示的场景
应用到一些列表展示的场景,只要控制好查询条件和分页,毫无问题。
场景2 查找课程下所有学员的id
比如仅仅想要查找课程下所有学员的id,在考虑选取具体措施的时候可以说是非常的纠结。
-
考虑1: 直接写个 SQL 语句去获取觉得有点多余;
-
考虑2: 课程的学员数量又是不可控的,课程学员数量多的时候,使用
findByFields
全部搜索出来必然会增加进程内存的消耗。
场景2 解决方案 —— columns
我们可以使用 search
函数的第五个参数 columns
来解决这个难点。columns
含义是限制输出的列。巧妙地使用 columns
,就可以降低内存占用率。它的优势如下:
- 大大降低了
MySQL
的查询时间; - 减少了输出数据。
$this->getCourseDao()->search(
array(...),
array(),
0,
PHP_INT_MAX, // 在输出字段 columns 可控的时候,使用PHP_INT_MAX可以接受
array('id') // columns,按需取列,降低内存消耗,提高性能的利器
);
1.2 findByFields
findByFields 函数接口
findByFields
只有 查询条件 一个参数。扫描二维码关注公众号,回复: 5606957 查看本文章
findByFields($fields);
findByFields 使用场景:应用在一些查询条件可控、表的数据量可控、无分页要求、无需排序的场景中。
使用最多的场景类似于 findByIds
、findByXXXId
。$fields
有很明确的参数限制,这样可以增加代码的可读性,同时降低内存泄漏的风险。如果想要灵活设定参数值,建议使用 search
的 conditions
。
2. 使用循环结构最佳实践
循环作为一个基本的逻辑代码块,大多出现在批量数据的处理上。而批量处理数据往往是最易消耗性能的地方。遵循以下原则,在一定程度上可以降低循环带来的时间和性能消耗。
2.1 使用循环的原则
2.1.1 避免循环多层嵌套
很多代码会出现循环中嵌套多层循环的现象。其实遇到这种情况,首先该思考业务的实现思路是否合理。多层嵌套循环会使代码的可读性变差,无论是开发当时还是后续回顾都极易杀死更多脑细胞。
2.1.2 将循环次数做到可控
举个栗子,我需要将某个课程下的所有学员都进行数据处理后再更新。如果将所有学员信息直接一次性
搜索出来,在课程学员数据量较大的情况下,往往还没开始循环,内存就先炸了。需要对循环的数据进行分组, 利用 search
方法一次只取出 500
条数据进行处理,处理完一批再处理下一批,这样的好处是可以保证内存可控。
2.1.3 循环代码块中避免 MySQL 取操作
避免在循环逻辑块中执行像 MySQL
读这样的操作。这样会大大增加程序的执行时间。正确的做法是将一组数据需要的资源批量准备好。这样在一定程度上可以减小 MySQL
的压力,减少执行时间。
2.1.4 循环代码块中避免 MySQL 写操作
避免在循环中更新数据。建议先在循环中准备好要更新的数据,等到一组循环执行结束后,批量更新。AdvancedDao
提供了 batchUpdate
、batchCreate
等功能,可以配合循环使用。
二、减少代码中的危险操作
数据库是一个程序宝贵的持久化资源,代码中如果使用不当,极易造成数据的丢失。
优雅地使用 Update 和 Delete 函数
-
EduSoho 提供了更新、删除数据的函数
update
、delete
-
AdvancedDao
提供了batchUpdate
、batchDelete
update|delete 使用场景
适用于将一组数据批量更新或者批量删除同一组值。
update 函数实践
// $identifier : int型会通过$id更新, 数组会通过$conditions更新
update($identifier, array $fields)
更新形式取决于传入 $identifier
的参数类型:
-
int
型会通过$id
更新单条数据,简单明了,操作简单,一般不会出现数据安全问题; -
数组会通过
$conditions
更新,可能存在数据的“越权操作”风险。
数据的越权操作
需要注意的是,根据查询条件去更新极易造成数据的“越权操作”。比如,我们本来要更新某个课程下所有课时的某个字段,正常情况下会有类似这样的操作。
update(
array(
'courseId' => 1
),
$fields
)
以上代码集齐了四个因素就能解锁将整个数据库更新的技能!
-
第一个参数是数组类型,意味着此处的查询条件会很灵活。
-
php
是弱类型的语言,很多情况下数据的类型不会这么敏感。 -
传入一个
'courseId' => null
的值 -
恰好使用了
array_filter
函数进行筛选
surprise !!! 灾难就发生了,我将整个数据库的数据全部更新了!不要以为我在开玩笑,很多代码就是在这种无意识中被写出来了。并且常规情况不会触发,只有在某些极端情况下被触发,极其难被测试出来。
update 批量更新原则
保证参数严格可控,业务层永远不能直接通过调用 update
实现批量更新
引用上文的例子,我们需要根据业务封装一个严格控制参数的函数。
public function updateByCourseId($courseId, $fields)
{
// 保证参数courseId有效
if (empty($courseId)) {
return false;
}
// 保证参数严格限定,不会有额外的参数
$conditions = array('courseId' => $courseId);
return $this->getLessonService()->update($conditions, $fields);
}
提取需要更新数据的唯一标识符
批量更新的时候,可以将要更新数据的唯一标识符取出来,然后再去更新,也不失为一个好办法。
public function updateByCourseId($courseId, $fields)
{
// 保证参数courseId有效
if (empty($courseId)) {
return false;
}
// 查找符合条件的ids
$ids = $this->getLessonService()->search(
array('courseId' => $courseId),
array(),
0,
PHP_INT_MAX,
array(id)
);
$ids = ArrayToolkit::column($ids, 'id');
if (!$ids) {
return false;
}
return $this->getLessonService()->updateByIds($ids, $fields);
}
batchUpdate|batchDelete 使用场景
适用于更新和删除一批数据中,并且值不同的状况。
批量更新和删除依赖于 AdvancedDao
的扩展功能具体使用很简单,不再赘述。
结束语
程序开发从来都不是一件枯燥的事情,当你乐意去思考代码的细节和实现思路,你将会爱上你的代码。
如文章中有不同的观点和建议,欢迎斧正~
我们正在寻求外包团队
EduSoho官方开发文档地址
EduSoho官网 https://www.edusoho.com/
EduSoho开源地址 https://github.com/edusoho/edusoho