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

最近靠维护老项目度过中年危机的话题挺火,刚好最近也在维护一个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】,成为我们的一员,让我们一起在编程的海洋中探索、学习、成长!

相关推荐
zopple2 小时前
常见的 Spring 项目目录结构
java·后端·spring
cjy0001113 小时前
springboot的 nacos 配置获取不到导致启动失败及日志不输出问题
java·spring boot·后端
小江的记录本4 小时前
【事务】Spring Framework核心——事务管理:ACID特性、隔离级别、传播行为、@Transactional底层原理、失效场景
java·数据库·分布式·后端·sql·spring·面试
sheji34164 小时前
【开题答辩全过程】以 基于springboot的校园失物招领系统为例,包含答辩的问题和答案
java·spring boot·后端
程序员cxuan5 小时前
人麻了,谁把我 ssh 干没了
人工智能·后端·程序员
wuyikeer6 小时前
Spring Framework 中文官方文档
java·后端·spring
Victor3566 小时前
MongoDB(61)如何避免大文档带来的性能问题?
后端
Victor3566 小时前
MongoDB(62)如何避免锁定问题?
后端
wuyikeer7 小时前
Spring BOOT 启动参数
java·spring boot·后端
子木HAPPY阳VIP7 小时前
Ubuntu 22.04 VMware 设置固定IP配置
人工智能·后端·目标检测·机器学习·目标跟踪