程序员难道不能靠维护老项目度过中年危机吗?

最近靠维护老项目度过中年危机的话题挺火,刚好最近也在维护一个PHP开发的CRM的老项目,项目由于数据量比较大, 导致查询速度很慢, 经常出现超时的情况, 下面记录一下具体的优化过程。

优化老项目,老生常淡的几点:

markdown 复制代码
1. 数据库优化
2. 代码结构优化
3. 缓存优化
4. 资源优化
...

数据库优化

众所周知, MySQL 优化第一步,就是建索引, 看了一下整个系统的表, 发现有大量的表都没有索引, 建了索引的表,索引名称有点花里胡哨, 如下:

css 复制代码
contractId	`contacts_id`	NORMAL	BTREE	27599	A		0		
customer_id	`customer_id`	NORMAL	BTREE	27599	A		0		

-- 

index_group	`role_id`, `callDate`	NORMAL	BTREE	4359069	A		0		
business_id	`business_id`	NORMAL	BTREE	518	A		0		
status_id	`status_id`	NORMAL	BTREE	43	A		0		

于是,优化第一步,规范一下索引的命名,MySQL索引的命名虽然没有硬性的规范,但是修改一下自己看着舒服, 个人理解:

普通索引:idx_字段1_字段2
唯一索引:uk_字段1_字段2
主键索引:pk_字段1_字段2

于是 上面的索引改成了:

css 复制代码
idx_contacts_id	`contacts_id`	NORMAL	BTREE	27599	A		0		
idx_customer_id	`customer_id`	NORMAL	BTREE	27599	A		0		

-- 

idx_role_id_callDate	`role_id`, `callDate`	NORMAL	BTREE	4359069	    A		0		
idx_business_id	        `business_id`	        NORMAL	BTREE	518	        A		0		
idx_status_id	        `status_id`	            NORMAL	BTREE	43	        A		0	

一下看起来舒服多了, 于是, 优化第二步, 就是给没有索引的表加上索引, 这个工作量比较大, 先把几个 常用功能模块的 表给加上索引, 于是 吭哧吭哧的 分析了 2天的 慢日志, 给需要加索引的表加上索引,本以为 加完索引后, 查询速度会快很多,结果发现, 并没有什么卵用. 一个页面 虽然快了点, 但是 不是太明显.

本着能加 配置 绝不改代码的原则,先去问了一下运维 Mysql 运行的机器内存是多大 64G. 这么大,那好办,先分析一下 数据库中的表引擎. 上了一段代码:

php 复制代码
<?php/** * Author: PFinal南丞 * Date: 2023/12/28 * Email: <lampxiezi@163.com> *//** 确保这个函数只能运行在 shell 中 **/if (!str_starts_with(php_sapi_name(), "cli")) {    die("此脚本只能在cli模式下运行.\n");}/** 关闭最大执行时间限制 */set_time_limit(0);error_reporting(E_ALL);ini_set('display_errors', 1);const MAX_SLEEP_TIME = 10;$hostname   = '';$username   = '';$password   = '';$connection = mysqli_connect($hostname, $username, $password);if (!$connection) {    die('Could not connect: ' . mysqli_error($connection));}$query  = "SELECT table_name,engine FROM information_schema.tables WHERE table_schema = 'smm';";$result = mysqli_query($connection, $query);if (!$result) {    die("Query failed: " . mysqli_error($connection));}$InnoDB_num = 0;$MyISAM_num = 0;while ($process = mysqli_fetch_assoc($result)) {    echo $process['table_name'] . " " . $process['engine'] . PHP_EOL;    if ($process['engine'] == 'InnoDB') {        $InnoDB_num++;    }    if ($process['engine'] == 'MyISAM') {        $MyISAM_num++;    }}echo "InnoDB " . $InnoDB_num . " MyISAM " . $MyISAM_num . PHP_EOL;mysqli_close($connection);

得出结果:

表引擎 MyISAM 的表 176 张 InnoDB的表引擎 88张. 要了一份 线上MySql 的配置发现:

ini 复制代码
...

key_buffer_size = 512M    
innodb_buffer_pool_size = 2048M

...

都知道 innodb_buffer_pool_size 针对的 是 InnoDB的表引擎,key_buffer_size 针对的 是 MyISAM的表引擎. 这配置不得修改一下. 果断打申请, 申请修改线上配置.

ini 复制代码
...

key_buffer_size = 2048M    
innodb_buffer_pool_size = 2048M

...

重启服务后,果然比原来快了好多.能撑到 同事不在群里 打报告了.

艰巨的长征路迈出了第一步,接下来,本着 死道友不死贫道的原则, 厚着脸皮,让运维帮忙整了一台mysql 的机器,来做了个主从分离。 速度一下,不影响业务的正常使用了.

接着 开启漫长的 优化之路.

缓存优化

  1. 项目没有开启数据缓存, 只有 代码编译的缓存

所以这一块是一个大的工程, 所以先不动, 只是 给 几个 常用的功能加了一个 数据 的 缓存。后续的思路是:

css 复制代码
  a. 加一个 redis, 使用 把代码中的统计数据 缓存到 redis 中

  b. 把客户信息,客户的关联信息,组合到一起, 然后缓存到 redis中.
  ....
  

代码结构优化

开始挖开代码, 看看 查询慢的 功能 代码是咋写的,不看不知道,一看直接上头:

  1. 几乎全是 foreach 中 的 SQL 查询:

    scss 复制代码
    foreach($customer_list as $key=>$value){        # ......        $customer_list[$key]['customer_name'] = $this->customer_model->get_customer_name($value['customer_id']);        $customer_list[$key]['customer_phone'] = $this->customer_model->get_customer_phone($value['customer_id']);        $customer_list[$key]['customer_address'] = $this->customer_model->get_customer_address($value['customer_id']);                # ......    }
  2. 由于 ORM 的方便复用, 大量的 表关联模型 复用,导致查询的 废字段特别多.比如:

    ruby 复制代码
    <?php    class CustomerViewModel extends ViewModel {        protected $viewFields;  public function _initialize(){   $main_must_field = array('customer_id','owner_role_id','is_locked','creator_role_id','contacts_id','delete_role_id','create_time','delete_time','update_time','last_relation_time','get_time','is_deleted','business_license');   $main_list = array_unique(array_merge(M('Fields')->where(array('model'=>'customer','is_main'=>1,'warehouse_id'=>0))->getField('field', true),$main_must_field));   $data_list = M('Fields')->where(array('model'=>'customer','is_main'=>0,'warehouse_id'=>0))->getField('field', true);   $data_list['_on'] = 'customer.customer_id = customer_data.customer_id';   $data_list['_type'] = "LEFT";   //置顶逻辑   $data_top = array('set_top','top_time');   $data_top['_on'] = "customer.customer_id = top.module_id and top.module = 'customer' and top.create_role_id = ".session('role_id');   $data_top['_type'] = "LEFT";   //首要联系人(姓名、电话)   $data_contacts = array('name'=>'contacts_name', 'telephone'=>'contacts_telephone');   $data_contacts['_on'] = "customer.contacts_id = contacts.contacts_id";   // 检查是否存在部门库字段            $warehouse_id = I('warehouse_id', '', 'intval');            if ($warehouse_id) {                $warehouse_id = D('Fields')->isExistsWarehouseTable(1, $warehouse_id);                if ($warehouse_id) {                    $customer_warehouse_data_table = customer_warehouse_table($warehouse_id);                    $warehouse_data_list = M('Fields')->where(array('model'=>'customer','is_main'=>0,'warehouse_id'=>$warehouse_id))->getField('field', true);                    $warehouse_data_list['_on'] = 'customer.customer_id = ' . $customer_warehouse_data_table .'.customer_id';                    $warehouse_data_list['_type'] = "LEFT";                    $this->viewFields = array('customer'=>$main_list,'customer_data'=>$data_list,$customer_warehouse_data_table=>$warehouse_data_list,'top'=>$data_top,'contacts'=>$data_contacts);                } else {                    $this->viewFields = array('customer'=>$main_list,'customer_data'=>$data_list,'top'=>$data_top,'contacts'=>$data_contacts);                }            } else {                $this->viewFields = array('customer'=>$main_list,'customer_data'=>$data_list,'top'=>$data_top,'contacts'=>$data_contacts);            }  }    ?>
  3. 代码中的业务逻辑一直再叠加,导致废代码量特别的大需要重新梳理逻辑

针对以上的代码做修改:

sql 复制代码
a. 第一点, 把所有foreach 中的 sql拆出来,先去查询到内存中,然后组合减少sql语句 

b. 第二点, 简化 ORM的乱用,比如只需要查询一个字段的 就直接用原生sql或者新的一个不关联的orm 来处理

资源优化

  1. 由于录音文件过大, 找运维 做了一个专门的文件服务器,移到了文件服务器上

最后

最后,给加了个定时任务告警的功能, 方便及时发现异常, 优化的 第一步 勉强交活。剩下的 优化 需要再花点时间了,慢慢来了.

🌟 更多精彩内容等你发现!关注【PFinalClub】,成为我们的一员,让我们一起在编程的海洋中探索、学习、成长!

相关推荐
战神刘玉栋19 分钟前
《SpringBoot、Vue 组装exe与套壳保姆级教学》
vue.js·spring boot·后端
码到成功>_<2 小时前
Spring Boot实现License生成和校验
数据库·spring boot·后端
Ztiddler2 小时前
【npm设置代理-解决npm网络连接error network失败问题】
前端·后端·npm·node.js·vue
货拉拉技术3 小时前
多元消息融合分发平台
javascript·后端·架构
醒过来摸鱼3 小时前
【Golang】协程
开发语言·后端·golang
谷大羽3 小时前
Kafka Stream实战教程
spring boot·后端·中间件·kafka·stream
2401_857636393 小时前
实验室管理平台:Spring Boot技术构建
java·spring boot·后端
一个小坑货4 小时前
Rust基础
开发语言·后端·rust
初晴~4 小时前
【Spring】RESTful设计风格
java·后端·spring·springboot·restful
H2Z20Str4 小时前
PIXHAWK(ardupilot4.52)单ic通道输出pwm
后端·restful