05-性能优化审查.md 14 KB

第五阶段:性能优化审查报告

审查概述

项目 详情
审查日期 2026年5月19日
审查范围 数据库查询、缓存策略、队列处理、同步阻塞
发现问题数 35个
高风险 10个
中风险 15个
低风险 10个

1. 数据库查询性能

🔴 高风险问题

1.1 渠道层级遍历 N+1 查询

位置common.php:437,464,483,510

// get_top_channel() - 每次循环执行一次查询
while ($info = $channelModel->where(['id' => $channelId])->find()) {
    if ($info['level'] == 1 || ...) return $info;
    $channelId = $info['parent_id'];
}

影响

  • 渠道层级最多 4 级,每次调用 4-5 次查询
  • Pay.php 分页列表循环调用,100 条数据 = 500+ 次额外查询

建议:使用 id_path 字段一次性查询

1.2 Pay 列表循环内调用查询函数

位置admin/controller/Pay.php:366-376

foreach ($list as $key => $item) {
    $channel = get_top_second_channel_name($list[$key]['channel_id']); // 2-5次查询
    $list[$key]['union_name'] = get_union_channel($list[$key]['channel_id'])['name']; // 2-5次查询
}

影响:分页 100 条 → 每条 4-10 次查询 → 400-1000 次额外查询

建议:批量预查询 + 数组映射

1.3 循环内重复查询(Bug)

位置api/controller/Index.php:607-629

foreach ($child_list as $v) {
    // 每次循环都查一次!且条件完全相同
    $promoter_list = Db::name('nw_channel')
        ->where(['parent_id' => ['in', $child_list_ids], 'level' => 3])
        ->select();
}

建议:提到循环外

1.4 大表重复聚合查询

位置admin/controller/Pay.php:382-409

// 同一请求中,对 cy_pay 表执行了 3 次几乎相同的聚合查询
$totalAmount = $PayModel->...->find()['total_amount'];
$todayTotalAmount = model('Pay')->...->find()['total_amount'];
$lastDayTotalAmount = model('Pay')->...->find()['total_amount'];

建议:合并为单条查询

🟡 中风险问题

1.5 缺少 field() 的 SELECT *

位置admin/controller/Pay.php:1468-1477

$pay = Db::name('cy_pay')->where(['orderid' => $orderid])->find();          // SELECT *
$members = Db::name('cy_members')->where(['id' => $userid])->find();         // SELECT *
$pyb = Db::name('nw_channel_admin')->where(['id' => $chal_id])->find();      // SELECT *

影响:连续 6 次 SELECT *,读取不必要的字段

建议:使用 ->field('id,orderid,amount') 指定字段

1.6 LIKE '%xxx%' 前缀通配符

位置api/controller/Index.php:564

->where(['id_path' => ['like', '%'.$qy_child_id_path.'%']])

影响% 前缀导致索引失效,全表扫描

建议:改用前缀匹配 LIKE 'xxx%'

1.7 3+ 表复杂 JOIN

位置api/controller/JhApi.php:357-362

->join('cy_members cm', 'ns.member_id = cm.id', 'left')
->join('cy_game cg', 'ns.game_id = cg.id', 'left')
->join('nw_channel nc', 'ns.channel_id = nc.id', 'left')
->join('nw_channel p1', 'nc.parent_id = p1.id', 'left')
->join('nw_channel p2', 'p1.parent_id = p2.id', 'left')

影响nw_channel 被 JOIN 3 次

建议:使用 id_path 拆分或冗余父级名称

1.8 limit(10000) 无分页

位置admin/controller/FkExceptionRecords.php:191-195

$data = Db::name('fk_exception_records')
    ->where($where)
    ->order('record_id desc')
    ->limit(10000)
    ->select();

建议:使用分页或流式查询

1.9 3 层嵌套子查询 UNION

位置admin/controller/Pay.php:904-912

$regSql = model('MemberGameServer')->field(...)->group(...)->buildSql();
$unionSql = model('Pay')->field(...)->group(...)->union([$regSql])->buildSql();
$unionSqlNew = Db::table($unionSql . ' bb')->field(...)->group(...)->buildSql();
$list = Db::table($unionSqlNew . ' cc')->paginate(...);

建议:使用临时表或预计算汇总数据

1.10 循环内 COUNT 查询

位置admin/controller/Setting.php:149-151

foreach ($channelList as $v) {
    $hasChildren = Db::name('nw_channel')
        ->where(['parent_id' => $v['value'], 'status' => 1])
        ->count() > 0;
}

建议:使用 GROUP BY 批量查询


2. 缓存策略分析

2.1 缓存配置

配置项
驱动 complex (默认 Redis)
前缀 newsdk_
Redis 过期 7天
文件缓存过期 30天
Session Redis,前缀 think_,过期 35小时

2.2 缓存 Key 设计模式

类别 Key 模式 过期时间
Token {token字符串} 15/30天
订单号 orderid:{orderid} 24小时
渠道缓存 clearable_channel_list_* 24小时-30天
游戏列表 clearable_game_list{md5} 300秒
配置 clearable_get_setting:{name} 24小时
风控名单 risk_control:inspect_list:{type}:{value} 7天

🔴 高风险问题

2.1 缓存穿透风险

问题:查不到数据时不缓存空值

// Setting::getSetting() - 不缓存空值
$info = self::where(['name' => $name])->value('value');
if (empty($info)) {
    return false;  // 不缓存,下次继续查库
}

建议:缓存空值,短过期时间

2.2 缓存击穿风险

问题:热点 key 无互斥锁

// Game::getAllByCondition() - 无并发保护
$result = Cache::get($cacheKey);
if (empty($result)) {
    $result = Db::table(...)->select();  // 高并发下同时查库
    Cache::set($cacheKey, $result, 300);
}

建议:使用互斥锁

2.3 缓存雪崩风险

问题:同类 key 过期时间高度集中

  • 渠道缓存全是 24h
  • 游戏列表全是 300s
  • Cache::clear('channel_cache') 瞬间删除所有渠道缓存

建议:过期时间加随机偏移

🟡 中风险问题

2.4 Token 存储不一致

  • Register 存 token:过期 30 天
  • Login 存 token:过期 15 天
  • 相同 key 不同过期时间,存在覆盖问题

2.5 直接操作 Redis handler 不一致

  • 部分代码使用 Cache::store('default')->handler()
  • 部分代码使用 Cache::set/get
  • 绕过框架的前缀、序列化、Tag 机制

2.6 缓存 Key 设计缺乏统一规范

存在多种分隔符混用:

  • token|sdk|{userid}|{gameid}(竖线)
  • risk_control:rule:frequency:*(冒号)
  • clearable_channel_list_(下划线)

3. 队列和异步处理

3.1 队列使用现状

机制 用途 位置
ThinkPHP Queue (Redis) 支付回调异步通知 CP extra/queue.php
原生 Redis List 打包任务、报表生成 SubPackage.php
原生 Redis ZSet 抖音广告推送 Tiktok.php
Kafka 通用消息队列(示例阶段) Kafka.php

🔴 高风险问题

3.1 Redis DB 选择混乱

位置Tiktok.php:22

$this->_redis->select(Env::get('redis.seelect_go', '')); // 拼写错误!

问题:配置键 redis.seelect_go 拼写错误,可能连接到错误的 DB

3.2 异步回调重试机制缺陷

位置crontab/controller/asyncCallback.php

  • 第 33 行和第 55 行重复判断 $job->attempts() > 5
  • 重试间隔固定 120 秒,无指数退避
  • 回调结果比较未做 trim/大小写统一

3.3 同步回调重试名存实亡

位置PayCallback.php:78-95

private function _doCallback($callback_url, $callback_params, $retry_times = 1, $sleep = 5)
// retry_times = 1,sleep 被注释掉,实际只请求一次

🟡 中风险问题

3.4 原生 Redis 与框架 Queue 并存

  • MakeReport.phpTiktok.php 等直接 new \Redis()
  • 连接不复用,无连接数控制
  • 无统一的队列抽象层

3.5 Kafka 仅处于示例阶段

  • 事件处理只有 placeholder
  • 消费者未配置 enable.auto.commit => false
  • 无 graceful shutdown

3.6 队列监控完全缺失

  • 无队列深度监控
  • 无死信队列
  • 无告警机制
  • 无消费速率统计

3.7 定时任务 N+1 查询

位置crontab/ChannelClean.php:347-376

for($i=1; $i<=$day_limit; $i++){
    // 每天单独查询一次数据库
    $temp_ids = Db::table('cy_member_channel_game_rel')...
}

建议:改为单条 SQL 的 BETWEEN


4. 同步阻塞问题

🔴 高风险问题

4.1 curl 无超时设置

位置 函数 问题
common.php:1290-1296 timingCurl() 未设置任何超时
api/complex/Yehua.php:121-133 curlInit() 未设置任何超时
api/controller/v2/Huge.php:175-188 curlJson() 未设置任何超时
admin/controller/Tasks.php:814-830 curl 调用 未设置任何超时
crontab/DealGameChannelDivide.php:156-167 curl 调用 未设置任何超时

4.2 curl 超时过长

位置 超时 说明
common/library/Sms.php:533-548 300秒 短信发送不应超过 30 秒
common/library/jssdk.php:125-139 500秒 微信 JSSDK,荒谬超时
common.php:365-388 60秒 通用 HTTP 工具函数
home/common.php:391-406 60秒 Web 控制器调用

4.3 curl 句柄泄漏

位置service/PayService.php:922-926api/controller/v1/Pay.php:1885-1889

return $xml;  // return 在 curl_close($ch) 之前
// curl 句柄永远不会关闭

🟡 中风险问题

4.4 file_get_contents 访问外部 URL

位置 说明
api/controller/v1/Login.php:298 淘宝 IP 查询,无超时
common/model/CscAppeal.php:137 淘宝 IP 查询,无超时
common.php:1662 IP 查询,无超时

4.5 CONNECTTIMEOUT 与 TIMEOUT 配置不合理

位置api/common.php:14-52

CONNECTTIMEOUT=30, TIMEOUT=5  // 连接超时30秒过长,总超时仅5秒,配置反了

4.6 数据库读写未分离

位置config.php:327-345

'deploy' => 0,           // 集中式
'rw_separate' => false,  // 读写未分离
// 从库配置与主库相同,从库形同虚设

4.7 长时间运行任务无保护

位置crontab/mlbb/TaskHandle.php:52-86

do {
    // 调用外部 API
    usleep(10000); // 仅 10ms
} while(true);  // 无限循环

4.8 递归调用无深度限制

位置common.php:394-408

function makeOrderid($head = 'WL') {
    if ($redis->has($cache_key)) {
        return makeOrderid(); // 无递归深度保护
    }
}

5. 问题汇总

按风险等级分类

🔴 高风险

序号 问题 位置
1 渠道层级遍历 N+1 查询 common.php:437
2 Pay 列表循环内调用查询函数 admin/controller/Pay.php:366
3 循环内重复查询(Bug) api/controller/Index.php:620
4 大表重复聚合查询 admin/controller/Pay.php:382
5 缓存穿透风险 全局
6 缓存击穿风险 全局
7 缓存雪崩风险 全局
8 Redis DB 选择混乱 Tiktok.php:22
9 curl 无超时设置 5 处
10 curl 超时过长(300-500秒) 2 处

🟡 中风险

序号 问题 数量/位置
1 缺少 field() 的 SELECT * 10+ 处
2 LIKE '%xxx%' 前缀通配符 2 处
3 3+ 表复杂 JOIN 多处
4 limit(10000) 无分页 1 处
5 3 层嵌套子查询 UNION 1 处
6 循环内 COUNT 查询 1 处
7 Token 存储不一致 全局
8 缓存 Key 设计不规范 全局
9 异步回调重试缺陷 asyncCallback.php
10 同步回调重试名存实亡 PayCallback.php
11 原生 Redis 与框架并存 多处
12 Kafka 仅示例阶段 Kafka.php
13 队列监控缺失 全局
14 定时任务 N+1 查询 ChannelClean.php
15 curl 句柄泄漏 2 处

🟢 低风险

序号 问题
1 file_get_contents 访问外部 URL
2 CONNECTTIMEOUT 配置不合理
3 数据库读写未分离
4 长时间运行任务无保护
5 递归调用无深度限制
6 缓存 Key 分隔符混用
7 直接操作 Redis handler 不一致
8 定时任务 usleep 过短
9 密钥文件每次请求读取
10 文件日志锁竞争

6. 改进建议

第一优先级(查询优化)

  1. 修复渠道层级查询:使用 id_path 一次性查询
  2. Pay 列表批量预查询:收集所有 ID 后批量查询
  3. 修复循环内重复查询 Bug:提到循环外
  4. 合并重复聚合查询:单条 SQL 完成

第二优先级(缓存优化)

  1. 防缓存穿透:缓存空值
  2. 防缓存击穿:互斥锁
  3. 防缓存雪崩:过期时间加随机偏移
  4. 统一 Token 存储:统一过期时间

第三优先级(队列优化)

  1. 修复拼写错误redis.seelect_goredis.select_go
  2. 完善异步回调:指数退避 + 结果比较容错
  3. 增加死信队列:失败任务可追溯
  4. 增加队列监控:深度 + 告警

第四优先级(阻塞优化)

  1. 统一 curl 超时:CONNECTTIMEOUT=5, TIMEOUT=10
  2. 修复 curl 句柄泄漏curl_close 移到 return
  3. 替换 file_get_contents:使用 curl 并设置超时
  4. 启用数据库读写分离

7. 参考代码

渠道层级查询优化

// ❌ 当前:循环查询
while ($info = $channelModel->where(['id' => $channelId])->find()) {
    $channelId = $info['parent_id'];
}

// ✅ 建议:使用 id_path
$ids = explode(',', trim($channel->id_path, ','));
$channels = $channelModel->whereIn('id', $ids)->select();

缓存穿透防护

// ❌ 当前:不缓存空值
if (empty($info)) return false;

// ✅ 建议:缓存空值
if (empty($info)) {
    Cache::set($cache_key, '', 300);
    return false;
}

缓存击穿防护

// ✅ 建议:互斥锁
$result = Cache::get($cacheKey);
if (empty($result)) {
    $lockKey = $cacheKey . ':lock';
    if (Cache::set($lockKey, 1, 5)) {
        $result = Db::table(...)->select();
        Cache::set($cacheKey, $result, 3600);
        Cache::rm($lockKey);
    } else {
        usleep(100000);
        return $this->getData($cacheKey);
    }
}

curl 超时统一

// ✅ 建议
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);

审查报告生成时间:2026年5月19日