📸 性能优化反思:减少DB查询,合理使用成员变量。

阿轩的BUG
文章191
评论56
标签101
站点日志
作者总数:1位
置顶文章:0篇
标签总数:101条
文章总数:196篇
评论总数:56条
微语总数:201条
运行天数:2317天
最近更新:2026-07-28
文章标签
最新评论
Sherlock
2025-08-21
不是应该打包成apk文件吗,怎么突然就运行编译了,下一步呢
老金er
2025-05-25
人生设计非常必要,在信息化发达的今天,周围大部分人依然只能过着人生工程模式生活,长时间生活洗礼后,尽管“人生工程”已经不再适合很多人,他们也无法做出任何改变,人生设计给我们提供了新的思路,值得尝试
赏帮赚
2025-05-25
职场太多的尔虞我诈
三笑
2024-08-16
emlog6.0.1可以升级支持吗?
pony
2024-07-08
这篇文章提供了一种新的人生规划方法——"人生设计",它借鉴了产品设计的理念,鼓励人们以创造性和探索性的方式思考和规划自己的生活。以下是对这篇文章的几个评价维度: 创新性:将产品设计的理念应用于人生规划是一个新颖的视角,为人们提供了一种跳出传统生活轨迹,探索个性化生活路径的方法。 实用性:文章不仅提出了理念,还详细介绍了具体的实施步骤,包括自我评估、制定计划、原型设计和实践选择等,具有很强的操作性。 启发性:通过强调好奇心、不断尝试、重新定义问题等心态,文章鼓励人们打破常规,勇于探索未知,这对于激发个人潜能和创造力具有积极作用。 适应性:"人生设计"理念适用于不同人生阶段和不同背景的人,无论是希望从头开始的人,还是已经取得一定成就但希望探索新方向的人,都可以从中获得启发。 情感共鸣:文章对许多人对传统生活轨迹的不满和对改变的渴望进行了深刻的洞察,能够引起读者的共鸣。 可持续性:人生设计强调的是一个持续迭代和试错的过程,这与现实生活的发展规律相吻合,有助于人们在不断变化的环境中做出适应性调整。 教育意义:作为一种人生规划方法,"人生设计"可以作为教育的一部分,帮助年轻人更早地学会自我探索和规划,为未来的生活和职业发展打下基础。 局限性:尽管"人生设计"提供了一种新的思考框架,但它可能需要个人具备一定的自我认知和反思能力,对于一些缺乏自我探索经验的人来说,可能需要额外的指导和支持。 总体来说,这篇文章提供了一种有价值的人生规划方法,对于那些渴望改变、寻求个性化生活的人来说,是一种有益的参考和指导。
首页实用干货 🌋

思考:减少DB查询次数,合理使用成员变量

高内聚,低耦合是非常深入人心的设计思想,在做到高内聚低耦合的同时,我们也要考虑到值传递的问题:要避免在抽取函数,封装代码时不合理的值传递,避免在多个函数内部重复查询相同的DB。

举个栗子

需求描述

  1. 我们的项目是交友类APP,有划卡片喜欢、不喜欢、超级喜欢的动作,也有赠送礼物、邀请约会等动作

  2. 上述动作有各种判断,比如每天有喜欢的上限,又区分是不是会员;赠送礼物时又要判断是否开通了会员,扣费的话要不要使用会员价;邀请约会的时候要判断是不是好友等等;各种看起来平平无奇的场景融合在一起就让代码结构变得异常复杂了。程序设计变得异常重要。

需求分析

  1. 基于上述需求,我们定义了几个概念:动作、消费、记录;

  2. 把动作进行拆分:基础动作、付费动作、全局动作;把消费进行拆分:充值、消费、领券等;

  3. 根据拆分的概念实现逻辑代码,比如基础动作抽象为一个类,付费动作和全局动作继承这个类:付费的动作单独抽象为一个类,把付费相关的动作放到这里;全局动作指的是不管是不是会员,不换是不是消费,都能进行的动作,也抽象为一个单独的类。

(在项目开发第一版,我们是没做这种抽象的,在一个类里面实现了各种动作、消费、记录,随着项目的推进,变得非常臃肿混乱。)

上面讲的可能有些抽象,看下面的代码示例会清晰很多哦~

代码示例

为了行为紧凑,方便大家理解,我不直接粘贴我们的逻辑代码,而是把关键代码段拿出来分析。

整体结构

  1. 下述代码是上面提到的全局动作类,它继承了基础动作类,所有的动作在基础动作类中定义
  2. 规范了输入参数输出参数这些成员变量
  3. 构造方法传入当前用户id和对方id,所有的动作肯定是有双方的
  4. 规范了setAction()设置动作、getActionResult()获得动作的执行结果,统一输入输出的格式和规范,不管在哪里调用,都遵守统一的规范
class UserUniversalAction extends BaseUserAction
{
    //输入参数
    protected $_userid;      //当前用户ID
    protected $_actionId;    //动作ID
    protected $_extra = [];       //动作携带的额外参数
    protected $_otherUserid; //对方用户ID
    protected $_data = [];        //中间存储
    protected $_houseOpen = [];
    protected $_userConsume = null;
    protected $_now;

    //输出参数
    protected $_pass = true;            //财务性约束,有没有消费通过
    protected $_rechargeType = null;    //消费的类型
    protected $_propCount = 0;
    protected $_consumeSuccess = false; //消费成功-默认false
    protected $_canInvite = false;      //
    protected $_errorCode = 0;          //非财务行约束,动作的关系执行条件
    protected $_needAfterAction = true;
    //protected $_out = [];               //动作执行完成后,输出结果

    public $hasRight = false;
    public function __construct($userid, $otherUserid = '')
    {
        parent::__construct($userid, 0);
        $this->_otherUserid = $otherUserid;
        //初始化参数
        $this->_houseOpen = HouseOpen::getCurrentOpen();
        //消费初始化
        $this->_userConsume = new UserConsume($this->_userid);
        $this->_now = time();
    }

    //设置执行动作
    public function setAction($actionId, $extra = [])
    {
        //设置动作参数
        $this->_actionId = $actionId;
        $this->_extra = $extra;

        //前置动作,执行权限校验等
        $this->_beforeAction();
        //只有非财务性约束和财务性约束通过后,才能继续执行
        if ($this->_errorCode == 0 && $this->_pass) {
            //执行动作
            $this->_actionExecute();
            if ($this->_errorCode == 0) {
                //后置动作
                $this->_afterAction();
            }
        }
    }

    //获取动作执行结果
    public function getActionResult()
    {
        return [
            //财务性限制
            'pass' => $this->_pass,
            'rechargeType' => $this->_rechargeType,
            'propCount' => $this->_propCount,
            'canInvite' => $this->_canInvite,
            //业务性限制
            'errorCode' => $this->_errorCode,
            'out' => $this->_data
        ];
    }

设置执行动作详解

细心的同学可能已经发现了:我们又对setAction()做了进一步的拆解,拆分为:

_beforeAction:前置动作,执行权限校验等,比如只有开通了会员才允许超级喜欢,只有成为了好友才允许邀请约会。

_actionExecute:执行动作,比如触发了超级喜欢,比如向对方发起了约会邀请。

_afterAction:后置动作,比如发起约会时给对方发通知栏消息告知对方;比如约会结束之后发推送消息告知对方的约会感受。

这样做的好处是异常清晰,几乎所有的动作都可以理解为三步:动作前、动作中、动作后。

另外一个比较硬核的地方是传入的第二个参数 $extra = []:

传入的第一个参数很好理解:$actionId 就是我们定义的动作id,我们根据动作id判断要执行哪些动作。

第二个参数$extra = [],extra是扩展参数、可变参数的概念。就和我开篇提到的减少DB查询,合理使用成员变量 呼应上了:

把需要在多处使用到的参数传入,而不是每次都通过查询DB的方式获得。我们以参数形式传入的数据可以赋值给成员变量

    //设置执行动作
    public function setAction($actionId, $extra = [])
    {
        //设置动作参数
        $this->_actionId = $actionId;
        $this->_extra = $extra;

        //前置动作,执行权限校验等
        $this->_beforeAction();
        //只有非财务性约束和财务性约束通过后,才能继续执行
        if ($this->_errorCode == 0 && $this->_pass) {
            //执行动作
            $this->_actionExecute();
            if ($this->_errorCode == 0) {
                //后置动作
                $this->_afterAction();
            }
        }
    }

核心

下面的示例代码能让大家更好的理解如何合理的使用成员变量

老规矩先说需求:在约会结束时进行判断,如果线上语音约会时间小于1分钟则补偿给用户约会券(我们认为约会时间小于1分钟的就是体验不好的约会,不能让用户白花钱,要给予优惠券补偿)

如果是常规设计:我们需要至少查询3次DB,即:

  1. 触发结束约会时修改状态,进行一系列读写操作,返回给客户端最新的数据状态
  2. _afterAppointmentFinish中查询语音房是否是开放的状态(我们产品是有营业概念的,只有营业中可执行约会动作)
  3. _afterAppointmentFinish中根据约会id,查询双方约会时长等信息

通过成员变量传参的方式,只需要1次查询DB,即:

  1. 触发结束约会时修改状态,进行一系列操作,返回给客户端最新的数据状态的同时,通过$this->_data = $appointmentModel->toArray();赋值给成员变量; _afterAppointmentFinish()中通过$this->_data取值就可以了。
   protected function _actionExecute()
   {
        //执行权限校验
        switch ($this->_actionId) {
              .
              .
              .
            case self::TYPE_ACTION_END:
                $this->_doAppointmentEndActionExecute();
                break;
        }
    }
    protected function _afterAction()
    {
        //动作后置操作
        switch ($this->_actionId) {
            .
            .
            .
            case self::TYPE_ACTION_END:
                $this->_afterAppointmentFinish();
                break;
            default:
        }
    }
    protected function _doAppointmentEndActionExecute()
    {
        $appointmentModel = AppointmentInfo::query()->selectRaw('id,userid,"inviteeUserid",status,endtime,"callStartTimestamp","callDuration","isConsume","appointmentOpenId"')
            ->where('id', $this->_extra['appointmentId'])->first();
        .
        .
        .
        $appointmentModel->endtime = time();
        $appointmentModel->status = AppointmentInfo::TYPE_STATUS_END;
        $appointmentModel->save();
        $this->_data = $appointmentModel->toArray();
        .
        .
        .
    }
    protected function _afterAppointmentFinish()
    {
        $houseOpen = $this->_houseOpen; //减少1次DB查询
        if ($houseOpen['status'] != HouseOpen::HOUSE_STATUS_OPEN) {
            return false;
        }
        //减少2次DB查询
        if (isset($this->_data)
            && $this->_data['isConsume'] == AppointmentInfo::TYPE_IS_CONSUME
            && $this->_data['appointmentOpenId'] == $houseOpen['currentAppointmentOpenId']
            && $this->_data['callDuration'] < 60) { //约会时长不足1分钟,花了多少补偿多少精酿券
            .
            .
            .
            }
        }
    }

上面只是一个简单的栗子,随着项目推进,应用场景增多,合理使用成员变量会体现出更高的价值。

回顾

大家再回顾一下我开篇提到的输入参数,这些都是成员变量,其中 _extra ,_data ,_houseOpen 都是易于扩展的数组类型,我们可以通过合理的使用成员变量,减少冗余的DB查询,提高程序的运行效率。

    //输入参数
    protected $_userid;      //当前用户ID
    protected $_actionId;    //动作ID
    protected $_extra = [];       //动作携带的额外参数
    protected $_otherUserid; //对方用户ID
    protected $_data = [];        //中间存储
    protected $_houseOpen = [];
    protected $_userConsume = null;
    protected $_now;

总结

要知道每次DB查询都是有网络耗时的;我们把数据存到成员变量,从内存中读取数据的耗时是可以忽略不计的。

上一篇
🎉 360Kan接口封装
下一篇
🔧 分享一下自己封装的Laravel常用工具类