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

最近靠维护老项目度过中年危机的话题挺火,刚好最近也在维护一个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: <[email protected]> *//** 确保这个函数只能运行在 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】,成为我们的一员,让我们一起在编程的海洋中探索、学习、成长!

相关推荐
noravinsc15 分钟前
django中用 InforSuite RDS 替代memcache
后端·python·django
喝醉的小喵40 分钟前
【mysql】并发 Insert 的死锁问题 第二弹
数据库·后端·mysql·死锁
kaixin_learn_qt_ing1 小时前
Golang
开发语言·后端·golang
炒空心菜菜2 小时前
MapReduce 实现 WordCount
java·开发语言·ide·后端·spark·eclipse·mapreduce
wowocpp4 小时前
spring boot Controller 和 RestController 的区别
java·spring boot·后端
后青春期的诗go4 小时前
基于Rust语言的Rocket框架和Sqlx库开发WebAPI项目记录(二)
开发语言·后端·rust·rocket框架
freellf4 小时前
go语言学习进阶
后端·学习·golang
全栈派森6 小时前
云存储最佳实践
后端·python·程序人生·flask
CircleMouse6 小时前
基于 RedisTemplate 的分页缓存设计
java·开发语言·后端·spring·缓存
獨枭8 小时前
使用 163 邮箱实现 Spring Boot 邮箱验证码登录
java·spring boot·后端