PHP项目ThinkPHP索引提示与优化

wen PHP项目 3

本文目录导读:

PHP项目ThinkPHP索引提示与优化

  1. 索引使用提示(SQL Hint)
  2. SQL语句层面的索引优化
  3. ThinkPHP查询构造器优化
  4. 索引设计优化
  5. 性能监控与分析
  6. 缓存优化
  7. 最佳实践建议
  8. 常见性能陷阱

在ThinkPHP项目中,数据库索引优化是提升性能的关键环节,以下是系统性的索引提示与优化方案:

索引使用提示(SQL Hint)

强制索引(Force Index)

// 使用force索引提示
$data = Db::name('users')
    ->force('idx_username')
    ->where('username', 'admin')
    ->find();
// 多表查询强制索引
$data = Db::name('orders')
    ->alias('o')
    ->join('users u', 'o.user_id = u.id')
    ->force('idx_order_no')
    ->where('o.order_no', '20240101001')
    ->find();

忽略索引(Ignore Index)

// 忽略某个索引
$data = Db::name('users')
    ->ignore('idx_email')
    ->where('email', 'test@example.com')
    ->find();

建议索引(Use Index)

// 使用建议索引
$data = Db::name('products')
    ->useIndex('idx_category_price')
    ->where('category_id', 1)
    ->order('price desc')
    ->select();

SQL语句层面的索引优化

查询优化

// 避免使用函数导致索引失效
// 错误示例
$data = Db::name('users')
    ->where("DATE(create_time) = '2024-01-01'")
    ->select();
// 正确示例
$data = Db::name('users')
    ->where('create_time', '>=', '2024-01-01 00:00:00')
    ->where('create_time', '<=', '2024-01-01 23:59:59')
    ->select();
// 避免使用LIKE模糊查询开头通配符
$data = Db::name('products')
    ->where('name', 'like', '手机%') // 有效
    ->select();
$data = Db::name('products')
    ->where('name', 'like', '%手机%') // 索引失效
    ->select();

排序与分组优化

// 排序字段使用索引
$data = Db::name('orders')
    ->where('status', 1)
    ->order('create_time desc')
    ->limit(10)
    ->select();
// 分组查询优化
$data = Db::name('orders')
    ->field('user_id, COUNT(*) as total')
    ->group('user_id')
    ->having('total > 5')
    ->select();

联合索引优化

// 遵循最左前缀原则
// 联合索引(idx_user_id, idx_status, idx_create_time)
$data = Db::name('orders')
    ->where('user_id', 100)
    ->where('status', 1)
    ->order('create_time desc')
    ->select();

ThinkPHP查询构造器优化

字段选择优化

// 只查询需要的字段
$data = Db::name('users')
    ->field('id, username, email')
    ->where('status', 1)
    ->select();
// 避免SELECT * 
$data = Db::name('users')
    ->where('id', 100)
    ->value('username');

分页优化

// 大分页优化
$offset = 100000;
$limit = 20;
// 优化前 - 大量回表
$data = Db::name('orders')
    ->limit($offset, $limit)
    ->select();
// 优化后 - 使用延迟关联
$ids = Db::name('orders')
    ->limit($offset, $limit)
    ->column('id');
$data = Db::name('orders')
    ->whereIn('id', $ids)
    ->select();

子查询优化

// 使用JOIN替代子查询
$data = Db::name('orders')
    ->alias('o')
    ->join('users u', 'o.user_id = u.id')
    ->field('o.*, u.username')
    ->where('o.status', 1)
    ->select();

索引设计优化

索引创建技巧

// 在模型初始化时定义索引(通过迁移文件)
// database/migrations/xxxx_create_users_table.php
Schema::create('users', function (Blueprint $table) {
    $table->bigIncrements('id');
    $table->string('username')->unique();
    $table->string('email')->unique();
    $table->string('phone', 20)->nullable();
    $table->timestamp('created_at')->nullable();
    // 复合索引
    $table->index(['status', 'created_at']);
    // 前缀索引(针对较长字符串)
    $table->index('phone', 'idx_phone_prefix');
});

常用索引类型建议

// 覆盖索引 - 避免回表
$data = Db::name('users')
    ->field('id, username, email') // 这些字段都在索引中
    ->where('username', 'admin')
    ->select();
// 冗余索引避免
// 错误:已有idx_user_id,又创建idx_user_id_status
$table->index(['user_id'], 'idx_user_id');
$table->index(['user_id', 'status'], 'idx_user_id_status');

性能监控与分析

查询日志分析

// 开启查询日志
Db::listen(function($sql, $time, $explain) {
    // 记录慢查询
    if ($time > 1) {  // 1秒以上
        Log::warning('Slow query:', [
            'sql' => $sql,
            'time' => $time,
            'explain' => $explain
        ]);
    }
});
// 使用Debug工具
Debug::setConfig(['log' => true]);

EXPLAIN分析

// 通过查询构建器获取EXPLAIN信息
$sql = Db::name('orders')
    ->where('status', 1)
    ->buildSql();
$explain = Db::query('EXPLAIN ' . $sql);

缓存优化

查询结果缓存

// 缓存查询结果
$data = Cache::remember('users_list', 3600, function() {
    return Db::name('users')
        ->where('status', 1)
        ->select();
});
// 缓存查询(ThinkPHP内置)
$data = Db::name('users')
    ->cache('users_list', 3600)
    ->where('status', 1)
    ->select();

数据库查询缓存

// 使用数据库查询缓存
return Db::name('users')
    ->fetchSql(true)
    ->where('id', 100)
    ->select();

最佳实践建议

索引监控表

// 创建索引使用建议表
CREATE TABLE index_optimization_log (
    id INT PRIMARY KEY AUTO_INCREMENT,
    sql_text TEXT,
    table_name VARCHAR(100),
    execution_time FLOAT,
    index_used VARCHAR(100),
    suggestion TEXT,
    created_at TIMESTAMP
);

定期优化策略

// 定期执行优化命令
Artisan::command('db:optimize', function () {
    // 分析表
    $tables = Db::select('SHOW TABLES');
    foreach ($tables as $table) {
        $tableName = array_values((array)$table)[0];
        // 分析表结构
        Db::statement("ANALYZE TABLE {$tableName}");
        // 优化表
        Db::statement("OPTIMIZE TABLE {$tableName}");
    }
});

索引使用率监控

// 查看索引使用情况
class IndexMonitor
{
    public function check()
    {
        $sql = "
            SELECT 
                TABLE_NAME,
                INDEX_NAME,
                CARDINALITY,
                INDEX_TYPE
            FROM 
                INFORMATION_SCHEMA.STATISTICS
            WHERE 
                TABLE_SCHEMA = DATABASE()
        ";
        return Db::select($sql);
    }
}

常见性能陷阱

避免的写法

// 不要在索引列上使用运算
where('age * 2 > 100')  // 错误
where('age > 50')       // 正确
// 类型转换导致索引失效
where('user_id', '=', '100abc')  // 字符串比较
// 两个table的字符集不同

推荐的做法

// 使用自增主键
// 使用短ID替代长字符串
// 对于频繁查询的字段添加索引
// 定期清理不用的索引
// 示例:优化后的查询
class OrderRepository
{
    public function getActiveOrders($userId)
    {
        $query = Db::name('orders')
            ->where('user_id', $userId)
            ->where('status', 1)
            ->where('create_time', 'between', [date('Y-m-d', strtotime('-30 days')), date('Y-m-d')])
            ->field('id, order_no, amount, status')
            ->order('create_time desc')
            ->limit(20);
        // 添加缓存
        $cacheKey = "orders_{$userId}_active";
        return $query->cache($cacheKey, 600)->select();
    }
}

通过以上优化方案,可以显著提升ThinkPHP项目的数据库查询性能,建议根据实际业务场景,结合EXPLAIN分析结果,制定合适的索引策略。

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