EduSoho 开发中的最佳实践---性能和安全(一)

EduSoho 开发中的最佳实践—性能和安全(一)

EduSoho 汇集了很多开发者的心血,系统在不断发展中越来越完善。一个具有良好性能的系统取决于代码的设计和质量,在此和大家分享几个 EduSoho 开发的小技巧。

一、在代码中降低资源消耗,提高加载速度

程序的性能好坏直接影响到网站的整体体验。在EduSoho开发过程中,如果能正确地使用系统默认提供的一些接口和函数,可以有效地提高代码的执行效率。

1. 使用查询最佳实践

EduSoho 中的 Dao 层提供了一部分内置的查询函数,主要有 getsearchfindByFields。简单地按照功能区分如下:

  • 单行数据查询:get,性能提升的空间不大

  • 多条数据查询:searchfindByFields,有性能提升的空间

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 只有 查询条件 一个参数。

findByFields($fields);
findByFields 使用场景:应用在一些查询条件可控、表的数据量可控、无分页要求、无需排序的场景中。

使用最多的场景类似于 findByIdsfindByXXXId$fields 有很明确的参数限制,这样可以增加代码的可读性,同时降低内存泄漏的风险。如果想要灵活设定参数值,建议使用 searchconditions

2. 使用循环结构最佳实践

循环作为一个基本的逻辑代码块,大多出现在批量数据的处理上。而批量处理数据往往是最易消耗性能的地方。遵循以下原则,在一定程度上可以降低循环带来的时间和性能消耗。

2.1 使用循环的原则

2.1.1 避免循环多层嵌套

很多代码会出现循环中嵌套多层循环的现象。其实遇到这种情况,首先该思考业务的实现思路是否合理。多层嵌套循环会使代码的可读性变差,无论是开发当时还是后续回顾都极易杀死更多脑细胞。

2.1.2 将循环次数做到可控

举个栗子,我需要将某个课程下的所有学员都进行数据处理后再更新。如果将所有学员信息直接一次性
搜索出来,在课程学员数据量较大的情况下,往往还没开始循环,内存就先炸了。需要对循环的数据进行分组, 利用 search 方法一次只取出 500 条数据进行处理,处理完一批再处理下一批,这样的好处是可以保证内存可控。

2.1.3 循环代码块中避免 MySQL 取操作

避免在循环逻辑块中执行像 MySQL 读这样的操作。这样会大大增加程序的执行时间。正确的做法是将一组数据需要的资源批量准备好。这样在一定程度上可以减小 MySQL 的压力,减少执行时间。

2.1.4 循环代码块中避免 MySQL 写操作

避免在循环中更新数据。建议先在循环中准备好要更新的数据,等到一组循环执行结束后,批量更新。AdvancedDao 提供了 batchUpdatebatchCreate 等功能,可以配合循环使用。

二、减少代码中的危险操作

数据库是一个程序宝贵的持久化资源,代码中如果使用不当,极易造成数据的丢失。

优雅地使用 Update 和 Delete 函数

  • EduSoho 提供了更新、删除数据的函数 updatedelete

  • AdvancedDao 提供了 batchUpdatebatchDelete

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

你可能感兴趣的:(EduSoho 开发中的最佳实践---性能和安全(一))