本文目录导读:

在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分析结果,制定合适的索引策略。