注册

靠维护老项目度中年危机

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


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


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

数据库优化


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


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


于是 上面的索引改成了:


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. 这么大,那好办,先分析一下 数据库中的表引擎. 上了一段代码:


/** * Author: PFinal南丞 * Date: 2023/12/28 * Email:  */@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 informati0n—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 的配置发现:


...

key_buffer_size = 512M
innodb_buffer_pool_size = 2048M

...


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


...

key_buffer_size = 2048M
innodb_buffer_pool_size = 2048M

...


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


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


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


缓存优化



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

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


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

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


代码结构优化


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



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


    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 的方便复用, 大量的 表关联模型 复用,导致查询的 废字段特别多.比如:


    <?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. 代码中的业务逻辑一直再叠加,导致废代码量特别的大需要重新梳理逻辑

针对以上的代码做修改:


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

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

资源优化



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

最后


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


作者:PFinal社区_南丞
来源:juejin.cn/post/7353475049418260517

0 个评论

要回复文章请先登录注册