Laravel 13 数据库优化完全指南
数据库优化是提升应用性能的关键环节。本文将深入探讨 Laravel 13 中数据库优化的各种技巧和最佳实践。
查询优化
选择必要字段
1 2 3 4 5 6 7 8
| $users = User::all();
$users = User::select(['id', 'name', 'email'])->get();
$users = User::select($request->get('fields', ['id', 'name']))->get();
|
避免 N+1 问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| $posts = Post::all(); foreach ($posts as $post) { echo $post->user->name; }
$posts = Post::with('user')->get(); foreach ($posts as $post) { echo $post->user->name; }
$posts = Post::with('user.profile', 'comments.user')->get();
$posts = Post::with(['comments' => function ($query) { $query->where('approved', true); }])->get();
$posts = Post::all(); $posts->load('user', 'comments');
|
智能预加载
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Post extends Model { protected $with = ['user'];
protected $withCount = ['comments'];
public function scopeWithMinimal($query) { return $query->select(['id', 'title', 'user_id']) ->with('user:id,name'); }
public function scopeWithFull($query) { return $query->with(['user.profile', 'comments.user', 'tags']) ->withCount(['comments', 'likes']); } }
|
索引优化
创建索引
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| Schema::create('users', function (Blueprint $table) { $table->id(); $table->string('email')->unique(); $table->string('name'); $table->string('status'); $table->timestamp('created_at');
$table->index('status'); $table->index(['status', 'created_at']); $table->fullText('description'); });
Schema::table('users', function (Blueprint $table) { $table->index(['status', 'created_at'], 'users_status_created_at_index'); });
|
索引使用分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| use Illuminate\Support\Facades\DB;
$users = DB::table('users') ->select(['id', 'name', 'email']) ->where('status', 'active') ->where('created_at', '>', now()->subMonth()) ->get();
$plan = DB::table('users') ->where('status', 'active') ->explain() ->get();
DB::listen(function ($query) { if ($query->time > 100) { Log::channel('slow-query')->info('Slow query', [ 'sql' => $query->sql, 'bindings' => $query->bindings, 'time' => $query->time, ]); } });
|
分页优化
基础分页
1 2 3 4 5 6 7 8
| $users = User::simplePaginate(15);
$users = User::paginate(15);
$users = User::orderBy('id')->cursorPaginate(15);
|
高效分页
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| <?php
namespace App\Services;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
class OptimizedPagination { public function paginate($query, int $perPage = 15, int $page = 1): LengthAwarePaginator { $total = $this->getEstimatedCount($query); $items = $query->clone() ->forPage($page, $perPage) ->get();
return new \Illuminate\Pagination\LengthAwarePaginator( $items, $total, $perPage, $page ); }
protected function getEstimatedCount($query): int { $sql = $query->toSql(); $bindings = $query->getBindings();
$countSql = "EXPLAIN SELECT COUNT(*) FROM ({$sql}) as subquery"; $result = DB::select($countSql, $bindings);
return $result[0]->rows ?? 0; } }
|
无限滚动分页
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public function index(Request $request) { $users = User::orderBy('id') ->cursorPaginate($request->get('per_page', 15));
return response()->json([ 'data' => UserResource::collection($users), 'meta' => [ 'has_more' => $users->hasMorePages(), 'next_cursor' => $users->nextCursor()?->encode(), ], ]); }
$users = User::orderBy('id') ->cursorPaginate(15, ['*'], 'cursor', $request->cursor);
|
批量操作
批量插入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| foreach ($users as $userData) { User::create($userData); }
User::insert($users);
$users = User::insertAndReturnIds($users);
collect($users)->chunk(1000)->each(function ($chunk) { User::insert($chunk->toArray()); });
|
批量更新
1 2 3 4 5 6 7 8 9 10 11 12 13
| User::upsert([ ['id' => 1, 'name' => 'John', 'email' => 'john@example.com'], ['id' => 2, 'name' => 'Jane', 'email' => 'jane@example.com'], ], ['id'], ['name', 'email']);
User::where('status', 'pending') ->update(['status' => 'processed']);
DB::table('users') ->upsert($data, ['email'], ['name', 'updated_at']);
|
批量删除
1 2 3 4 5 6 7 8 9 10 11 12
| User::whereIn('id', $ids)->delete();
User::withTrashed() ->whereIn('id', $ids) ->restore();
User::chunk(1000, function ($users) { $users->each->delete(); });
|
查询构建优化
子查询优化
1 2 3 4 5 6 7 8 9 10 11 12 13
| $users = User::addSelect([ 'last_post_title' => Post::select('title') ->whereColumn('user_id', 'users.id') ->latest() ->limit(1), ])->get();
$users = User::join('posts', function ($join) { $join->on('users.id', '=', 'posts.user_id') ->where('posts.is_latest', true); })->select('users.*', 'posts.title as last_post_title')->get();
|
条件查询优化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| $users = User::query() ->when($request->search, function ($query, $search) { $query->where('name', 'like', "%{$search}%"); }) ->when($request->status, function ($query, $status) { $query->where('status', $status); }) ->when($request->role, function ($query, $role) { $query->whereHas('roles', fn($q) => $q->where('name', $role)); }) ->get();
$users = User::whereHas('posts', function ($query) { $query->where('published', true); }, '>=', 5)->get();
$users = User::whereExists(function ($query) { $query->select(DB::raw(1)) ->from('posts') ->whereColumn('posts.user_id', 'users.id') ->where('posts.published', true); })->get();
|
聚合查询优化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| $users = User::withCount(['posts', 'comments'])->get();
$users = User::withCount([ 'posts as published_posts_count' => fn($q) => $q->where('published', true), 'comments as approved_comments_count' => fn($q) => $q->where('approved', true), ])->get();
$stats = Cache::remember('user_stats', 3600, function () { return [ 'total' => User::count(), 'active' => User::where('active', true)->count(), 'new_this_month' => User::whereMonth('created_at', now()->month)->count(), ]; });
|
连接池与读写分离
配置读写分离
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 'mysql' => [ 'read' => [ 'host' => ['read1.example.com', 'read2.example.com'], ], 'write' => [ 'host' => 'write.example.com', ], 'sticky' => true, 'driver' => 'mysql', 'database' => 'database', 'username' => 'root', 'password' => '', 'charset' => 'utf8mb4', 'collation' => 'utf8mb4_unicode_ci', 'prefix' => '', ],
|
强制使用主库
1 2 3 4 5
| $user = User::writeConnection()->find($id);
$user = User::onWriteConnection()->find($id);
|
数据库连接优化
连接池配置
1 2 3 4 5 6 7 8 9
| 'mysql' => [ 'driver' => 'mysql', 'options' => [ PDO::ATTR_PERSISTENT => true, PDO::ATTR_EMULATE_PREPARES => false, PDO::ATTR_STRINGIFY_FETCHES => false, ], ],
|
连接复用
1 2 3 4 5
| DB::connection('mysql')->transaction(function () { DB::connection('mysql')->table('users')->insert([...]); DB::connection('mysql')->table('profiles')->insert([...]); });
|
慢查询分析
查询日志
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| DB::enableQueryLog();
$users = User::with('posts')->get();
$queries = DB::getQueryLog();
foreach ($queries as $query) { if ($query['time'] > 100) { Log::warning('Slow query detected', $query); } }
|
查询分析器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| <?php
namespace App\Services;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Collection;
class QueryAnalyzer { protected Collection $queries;
public function __construct() { $this->queries = collect(); }
public function start(): void { DB::listen(function ($query) { $this->queries->push([ 'sql' => $query->sql, 'bindings' => $query->bindings, 'time' => $query->time, 'backtrace' => debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 10), ]); }); }
public function getReport(): array { return [ 'total_queries' => $this->queries->count(), 'total_time' => $this->queries->sum('time'), 'slow_queries' => $this->queries->where('time', '>', 100)->values(), 'duplicate_queries' => $this->findDuplicates(), 'n_plus_one' => $this->detectNPlusOne(), ]; }
protected function findDuplicates(): Collection { return $this->queries->groupBy('sql') ->filter(fn($group) => $group->count() > 1) ->map(fn($group) => [ 'sql' => $group->first()['sql'], 'count' => $group->count(), 'total_time' => $group->sum('time'), ]); }
protected function detectNPlusOne(): Collection { return $this->queries->groupBy('sql') ->filter(fn($group) => $group->count() > 5) ->keys(); } }
|
总结
Laravel 13 的数据库优化包括:
- 查询字段优化和预加载
- 索引设计与分析
- 高效分页策略
- 批量操作优化
- 读写分离配置
- 慢查询监控分析
持续优化数据库是保证应用高性能的关键。