ThinkPHP项目关联查询性能测试

wen PHP项目 3

本文目录导读:

ThinkPHP项目关联查询性能测试

  1. 测试准备
  2. 性能测试脚本
  3. 性能监控工具类
  4. 性能优化实践
  5. 测试建议

我来为您提供一个完整的ThinkPHP项目关联查询性能测试方案。

测试准备

创建测试数据表

-- 用户表
CREATE TABLE `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(50) NOT NULL,
  `email` varchar(100) NOT NULL,
  `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_name` (`name`)
) ENGINE=InnoDB;
-- 订单表
CREATE TABLE `orders` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NOT NULL,
  `order_no` varchar(50) NOT NULL,
  `amount` decimal(10,2) NOT NULL,
  `status` tinyint(1) NOT NULL DEFAULT '1',
  `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_user_id` (`user_id`),
  KEY `idx_order_no` (`order_no`)
) ENGINE=InnoDB;
-- 订单详情表
CREATE TABLE `order_items` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `order_id` int(11) NOT NULL,
  `product_name` varchar(100) NOT NULL,
  `price` decimal(10,2) NOT NULL,
  `quantity` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_order_id` (`order_id`)
) ENGINE=InnoDB;

生成测试数据

<?php
namespace app\common\command;
use think\console\Command;
use think\console\Input;
use think\console\Output;
use think\facade\Db;
class GenerateTestData extends Command
{
    protected function configure()
    {
        $this->setName('generate:test-data')
            ->setDescription('生成测试数据');
    }
    protected function execute(Input $input, Output $output)
    {
        // 生成10000条用户数据
        $users = [];
        for ($i = 0; $i < 10000; $i++) {
            $users[] = [
                'name' => 'user_' . $i,
                'email' => 'user' . $i . '@example.com'
            ];
            // 每1000条插入一次
            if (count($users) >= 1000) {
                Db::name('users')->insertAll($users);
                $users = [];
            }
        }
        // 生成50000条订单数据
        $orders = [];
        for ($i = 0; $i < 50000; $i++) {
            $orders[] = [
                'user_id' => rand(1, 10000),
                'order_no' => 'ORDER' . str_pad($i, 8, '0', STR_PAD_LEFT),
                'amount' => rand(100, 10000) / 10,
                'status' => rand(1, 5),
                'created_at' => date('Y-m-d H:i:s', strtotime('-1 month') + $i * 10)
            ];
            if (count($orders) >= 1000) {
                Db::name('orders')->insertAll($orders);
                $orders = [];
            }
        }
        // 生成订单详情数据
        $items = [];
        for ($i = 1; $i <= 50000; $i++) {
            $itemCount = rand(1, 5);
            for ($j = 0; $j < $itemCount; $j++) {
                $items[] = [
                    'order_id' => $i,
                    'product_name' => 'product_' . rand(1, 100),
                    'price' => rand(10, 500) / 10,
                    'quantity' => rand(1, 10)
                ];
            }
            if (count($items) >= 1000) {
                Db::name('order_items')->insertAll($items);
                $items = [];
            }
        }
        $output->writeln('测试数据生成完成');
    }
}

性能测试脚本

基础关联查询测试

<?php
namespace app\api\controller;
use think\facade\Db;
use think\response\Json;
class PerformanceTest extends Base
{
    /**
     * 测试1:一对多关联查询(hasMany)
     */
    public function testHasMany()
    {
        $startTime = microtime(true);
        $startMemory = memory_get_usage();
        // 方法1:使用关联查询
        $users = Db::name('users')
            ->alias('u')
            ->leftJoin('orders o', 'o.user_id = u.id')
            ->field('u.id, u.name, COUNT(o.id) as order_count')
            ->group('u.id')
            ->limit(100)
            ->select();
        $endTime = microtime(true);
        $endMemory = memory_get_usage();
        return json([
            'code' => 1,
            'data' => [
                'method' => 'JOIN查询',
                'time' => round(($endTime - $startTime) * 1000, 2) . 'ms',
                'memory' => round(($endMemory - $startMemory) / 1024, 2) . 'KB',
                'count' => count($users)
            ]
        ]);
    }
    /**
     * 测试2:使用ThinkPHP关联模型
     */
    public function testRelationModel()
    {
        $startTime = microtime(true);
        $startMemory = memory_get_usage();
        // 使用模型关联
        $users = \app\model\User::with(['orders'])
            ->limit(100)
            ->select();
        $endTime = microtime(true);
        $endMemory = memory_get_usage();
        return json([
            'code' => 1,
            'data' => [
                'method' => '模型关联',
                'time' => round(($endTime - $startTime) * 1000, 2) . 'ms',
                'memory' => round(($endMemory - $startMemory) / 1024, 2) . 'KB',
                'count' => count($users)
            ]
        ]);
    }
    /**
     * 测试3:多级关联查询
     */
    public function testMultiLevelRelation()
    {
        $startTime = microtime(true);
        $startMemory = memory_get_usage();
        // 查询用户及其订单和订单详情
        $users = \app\model\User::with(['orders.items'])
            ->limit(50)
            ->select();
        $endTime = microtime(true);
        $endMemory = memory_get_usage();
        return json([
            'code' => 1,
            'data' => [
                'method' => '多级关联',
                'time' => round(($endTime - $startTime) * 1000, 2) . 'ms',
                'memory' => round(($endMemory - $startMemory) / 1024, 2) . 'KB',
                'count' => count($users)
            ]
        ]);
    }
    /**
     * 测试4:大数据量分页关联查询
     */
    public function testLargeDataPagination()
    {
        $page = input('page', 1);
        $limit = 20;
        $startTime = microtime(true);
        // JOIN查询带条件
        $orders = Db::name('orders')
            ->alias('o')
            ->join('users u', 'o.user_id = u.id')
            ->join('order_items oi', 'oi.order_id = o.id')
            ->where('o.status', 1)
            ->field('o.id, o.order_no, o.amount, u.name, COUNT(oi.id) as item_count')
            ->group('o.id')
            ->page($page, $limit)
            ->select();
        $endTime = microtime(true);
        return json([
            'code' => 1,
            'data' => [
                'method' => '分页JOIN',
                'time' => round(($endTime - $startTime) * 1000, 2) . 'ms',
                'page' => $page,
                'count' => count($orders)
            ]
        ]);
    }
}

对比测试脚本

<?php
namespace app\common\library;
use think\facade\Db;
use think\facade\Log;
class QueryPerformanceTest
{
    // 测试方法集合
    protected $tests = [];
    public function __construct()
    {
        $this->tests = [
            'simple_query' => '简单查询',
            'join_query' => 'JOIN查询',
            'relation_query' => '关联模型',
            'sub_query' => '子查询',
            'batch_query' => '批量查询'
        ];
    }
    /**
     * 执行所有测试
     */
    public function runAllTests($iterations = 100)
    {
        $results = [];
        foreach ($this->tests as $method => $name) {
            $results[$name] = $this->benchmark($method, $iterations);
        }
        return $results;
    }
    /**
     * 基准测试
     */
    protected function benchmark($method, $iterations)
    {
        $times = [];
        $memories = [];
        for ($i = 0; $i < $iterations; $i++) {
            $startTime = microtime(true);
            $startMem = memory_get_usage();
            // 执行测试方法
            call_user_func([$this, $method]);
            $endTime = microtime(true);
            $endMem = memory_get_usage();
            $times[] = ($endTime - $startTime) * 1000;
            $memories[] = ($endMem - $startMem) / 1024;
        }
        return [
            'avg_time' => round(array_sum($times) / count($times), 2),
            'max_time' => round(max($times), 2),
            'min_time' => round(min($times), 2),
            'avg_memory' => round(array_sum($memories) / count($memories), 2) . 'KB',
            'total_time' => round(array_sum($times), 2)
        ];
    }
    /**
     * 简单查询
     */
    protected function simple_query()
    {
        return Db::name('users')->limit(100)->select();
    }
    /**
     * JOIN查询
     */
    protected function join_query()
    {
        return Db::name('users')
            ->alias('u')
            ->join('orders o', 'o.user_id = u.id')
            ->field('u.*, COUNT(o.id) as order_count')
            ->group('u.id')
            ->limit(100)
            ->select();
    }
    /**
     * 关联模型查询
     */
    protected function relation_query()
    {
        return \app\model\User::with(['orders'])
            ->limit(100)
            ->select();
    }
    /**
     * 子查询
     */
    protected function sub_query()
    {
        $subQuery = Db::name('orders')
            ->field('user_id, COUNT(*) as order_count')
            ->group('user_id');
        return Db::name('users')
            ->alias('u')
            ->join(['' . $subQuery->buildSql() => 'o'], 'o.user_id = u.id')
            ->limit(100)
            ->select();
    }
    /**
     * 批量查询
     */
    protected function batch_query()
    {
        $userIds = Db::name('users')->limit(100)->column('id');
        $orders = Db::name('orders')
            ->where('user_id', 'in', $userIds)
            ->select();
        return $orders;
    }
}

性能监控工具类

<?php
namespace app\common\library;
use think\facade\Db;
use think\facade\Log;
class QueryMonitor
{
    protected $queries = [];
    protected $startTime;
    protected $startMemory;
    /**
     * 开始监控
     */
    public function start()
    {
        $this->startTime = microtime(true);
        $this->startMemory = memory_get_usage();
        // 开启SQL日志
        Db::listen(function ($sql, $time, $explain) {
            $this->queries[] = [
                'sql' => $sql,
                'time' => $time,
                'explain' => $explain
            ];
        });
    }
    /**
     * 结束监控并返回结果
     */
    public function stop()
    {
        $endTime = microtime(true);
        $endMemory = memory_get_usage();
        $result = [
            'total_time' => round(($endTime - $this->startTime) * 1000, 2) . 'ms',
            'memory_usage' => round(($endMemory - $this->startMemory) / 1024, 2) . 'KB',
            'query_count' => count($this->queries),
            'queries' => $this->queries
        ];
        $this->queries = [];
        return $result;
    }
    /**
     * 分析慢查询
     */
    public function analyzeSlowQueries($threshold = 100)
    {
        $slowQueries = array_filter($this->queries, function ($query) use ($threshold) {
            return $query['time'] > $threshold;
        });
        if (!empty($slowQueries)) {
            Log::warning('慢查询检测', [
                'count' => count($slowQueries),
                'queries' => $slowQueries
            ]);
        }
        return $slowQueries;
    }
}

性能优化实践

查询优化策略

<?php
namespace app\common\library;
class QueryOptimization
{
    /**
     * 使用索引优化
     */
    public function indexOptimization()
    {
        // 确保索引使用
        Db::name('orders')
            ->where('user_id', 100)
            ->where('status', 1)
            ->field('id, order_no, amount')
            ->select();
        // 复合索引
        // ALTER TABLE orders ADD INDEX idx_user_status (user_id, status);
    }
    /**
     * 分页优化 - 延迟关联
     */
    public function paginationOptimization($page = 1, $limit = 20)
    {
        // 先查询ID,再关联数据
        $ids = Db::name('orders')
            ->where('status', 1)
            ->order('id', 'desc')
            ->limit(($page - 1) * $limit, $limit)
            ->column('id');
        if (empty($ids)) {
            return [];
        }
        $data = Db::name('orders')
            ->alias('o')
            ->join('users u', 'o.user_id = u.id')
            ->where('o.id', 'in', $ids)
            ->field('o.*, u.name')
            ->select();
        return $data;
    }
    /**
     * 缓存优化
     */
    public function cacheOptimization($userId)
    {
        $cacheKey = 'user_orders_' . $userId;
        // 尝试获取缓存
        $orders = cache($cacheKey);
        if (empty($orders)) {
            // 从数据库查询
            $orders = Db::name('orders')
                ->where('user_id', $userId)
                ->select();
            // 存入缓存,有效期5分钟
            cache($cacheKey, $orders, 300);
        }
        return $orders;
    }
}

测试执行和报告

<?php
namespace app\api\controller;
use app\common\library\QueryPerformanceTest;
use app\common\library\QueryMonitor;
class TestController
{
    /**
     * 运行性能测试
     */
    public function runTest()
    {
        // 创建测试实例
        $tester = new QueryPerformanceTest();
        // 运行所有测试
        $results = $tester->runAllTests(50);
        // 格式化为报告
        $report = $this->formatReport($results);
        return json([
            'code' => 1,
            'data' => $report
        ]);
    }
    /**
     * 格式化报告
     */
    protected function formatReport($results)
    {
        $html = '<table border="1">';
        $html .= '<tr><th>查询方法</th><th>平均耗时</th><th>最大耗时</th><th>最小耗时</th><th>内存使用</th></tr>';
        foreach ($results as $name => $data) {
            $html .= "<tr>";
            $html .= "<td>{$name}</td>";
            $html .= "<td>{$data['avg_time']}ms</td>";
            $html .= "<td>{$data['max_time']}ms</td>";
            $html .= "<td>{$data['min_time']}ms</td>";
            $html .= "<td>{$data['avg_memory']}</td>";
            $html .= "</tr>";
        }
        $html .= '</table>';
        return $html;
    }
}

测试建议

测试场景覆盖

  • 不同的数据量级(1万、10万、100万条数据)
  • 不同的关联深度(1级、2级、3级关联)
  • 不同条件的查询(带where、排序、分组)
  • 并发查询测试

监控指标

  • 响应时间
  • 内存使用
  • SQL执行次数
  • 索引使用情况

优化建议

根据需要选择不同的查询方式:

  • 简单查询:使用Db
  • 关联查询:使用JOIN或者模型关联
  • 大数据量:使用分页和延迟关联
  • 频繁查询:使用缓存

注意事项

  • 避免N+1查询问题
  • 合理使用预加载(with
  • 建立合适的索引
  • 避免在大数据量下使用select *
  • 使用findInSetJSON_CONTAINS时注意性能

这个测试方案可以帮助您全面评估ThinkPHP项目的关联查询性能,并指导您进行相应优化。

抱歉,评论功能暂时关闭!