跳转至内容

数据库:入门

简介

几乎每个现代 Web 应用程序都需要与数据库交互。Laravel 通过原生 SQL、流畅的查询构建器以及 Eloquent ORM,使得与各种受支持的数据库进行交互变得极其简单。目前,Laravel 为以下五种数据库提供了官方支持:

此外,MongoDB 通过 mongodb/laravel-mongodb 包提供支持,该包由 MongoDB 官方维护。有关更多信息,请查看 Laravel MongoDB 文档。

配置

Laravel 数据库服务的配置位于应用程序的 config/database.php 配置文件中。在该文件中,你可以定义所有的数据库连接,并指定默认使用的连接。此文件中的大多数配置选项都由应用程序的环境变量驱动。文件中提供了 Laravel 支持的多数数据库系统的示例。

默认情况下,Laravel 的示例 环境变量配置已适配 Laravel Sail,这是一种用于在本地机器上开发 Laravel 应用程序的 Docker 配置。当然,你可以根据需要随时修改本地数据库配置。

SQLite 配置

SQLite 数据库存储在文件系统中的单个文件中。你可以通过终端的 touch 命令创建一个新的 SQLite 数据库:touch database/database.sqlite。创建数据库后,只需将 DB_DATABASE 环境变量设置为该数据库的绝对路径,即可轻松配置环境变量。

1DB_CONNECTION=sqlite
2DB_DATABASE=/absolute/path/to/database.sqlite

默认情况下,SQLite 连接已启用外键约束。如果你想禁用它们,可以将 DB_FOREIGN_KEYS 环境变量设置为 false

1DB_FOREIGN_KEYS=false

如果你使用 Laravel 安装程序创建 Laravel 应用程序并选择了 SQLite 作为数据库,Laravel 会自动为你创建 database/database.sqlite 文件并运行默认的 数据库迁移

Microsoft SQL Server 配置

要使用 Microsoft SQL Server 数据库,你需要确保已安装 sqlsrvpdo_sqlsrv PHP 扩展,以及它们可能需要的依赖项,例如 Microsoft SQL ODBC 驱动程序。

使用 URL 配置

通常,数据库连接是使用多个配置值(如 hostdatabaseusernamepassword 等)来配置的。这些配置值中的每一个都有其对应的环境变量。这意味着在生产服务器上配置数据库连接信息时,你需要管理多个环境变量。

一些托管数据库服务提供商(如 AWS 和 Heroku)提供了一个单一的数据库“URL”,其中包含单个字符串形式的所有连接信息。一个数据库 URL 示例可能如下所示:

1mysql://root:[email protected]/forge?charset=UTF-8

这些 URL 通常遵循标准的模式约定:

1driver://username:password@host:port/database?options

为方便起见,Laravel 支持使用这些 URL 作为配置数据库的多项选项的替代方案。如果存在 url(或对应的 DB_URL 环境变量)配置选项,它将被用于提取数据库连接和凭据信息。

读写分离连接

有时你可能希望将一个数据库连接用于 SELECT 语句,将另一个连接用于 INSERT、UPDATE 和 DELETE 语句。Laravel 让这一切变得非常简单,无论你是使用原生查询、查询构建器还是 Eloquent ORM,系统都会自动使用正确的连接。

要了解如何配置读/写连接,请看这个例子:

1'mysql' => [
2 'driver' => 'mysql',
3 
4 'read' => [
5 'host' => [
6 '192.168.1.1',
7 '196.168.1.2',
8 ],
9 ],
10 'write' => [
11 'host' => [
12 '192.168.1.3',
13 ],
14 ],
15 'sticky' => true,
16 
17 'port' => env('DB_PORT', '3306'),
18 'database' => env('DB_DATABASE', 'laravel'),
19 'username' => env('DB_USERNAME', 'root'),
20 'password' => env('DB_PASSWORD', ''),
21 'unix_socket' => env('DB_SOCKET', ''),
22 'charset' => env('DB_CHARSET', 'utf8mb4'),
23 'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
24 'prefix' => '',
25 'prefix_indexes' => true,
26 'strict' => true,
27 'engine' => null,
28 'options' => extension_loaded('pdo_mysql') ? array_filter([
29 (PHP_VERSION_ID >= 80500 ? \Pdo\Mysql::ATTR_SSL_CA : \PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'),
30 ]) : [],
31],

注意,配置数组中添加了三个键:readwritestickyreadwrite 键的值是包含单个键 host 的数组。读写连接的其他数据库选项将从主要的 mysql 配置数组中合并。

只有当你希望覆盖主要 mysql 数组中的值时,才需要在 readwrite 数组中添加项。因此,在这种情况下,192.168.1.1 将被用作“读”连接的主机,而 192.168.1.3 将被用作“写”连接的主机。数据库凭据、前缀、字符集以及主要 mysql 数组中的所有其他选项将在两个连接之间共享。当 host 配置数组中存在多个值时,每次请求都会随机选择一个数据库主机。

sticky 选项

sticky 选项是一个*可选*值,可用于允许在当前请求周期内读取刚刚写入数据库的记录。如果启用了 sticky 选项,并且在当前请求周期内已对数据库执行了“写”操作,则后续的任何“读”操作都将使用“写”连接。这确保了在请求周期内写入的任何数据都可以在同一个请求中立即从数据库读取。是否需要此行为由你决定。

运行 SQL 查询

配置好数据库连接后,你可以使用 DB 门面来运行查询。DB 门面为每种查询类型提供了相应的方法:selectupdateinsertdeletestatement

运行 Select 查询

要运行基本的 SELECT 查询,可以使用 DB 门面上的 select 方法:

1<?php
2 
3namespace App\Http\Controllers;
4 
5use Illuminate\Support\Facades\DB;
6use Illuminate\View\View;
7 
8class UserController extends Controller
9{
10 /**
11 * Show a list of all of the application's users.
12 */
13 public function index(): View
14 {
15 $users = DB::select('select * from users where active = ?', [1]);
16 
17 return view('user.index', ['users' => $users]);
18 }
19}

传递给 select 方法的第一个参数是 SQL 查询,第二个参数是需要绑定到查询的参数绑定。通常,这些是 where 子句约束的值。参数绑定提供了针对 SQL 注入的保护。

select 方法将始终返回一个结果 array。数组中的每个结果都将是一个代表数据库记录的 PHP stdClass 对象:

1use Illuminate\Support\Facades\DB;
2 
3$users = DB::select('select * from users');
4 
5foreach ($users as $user) {
6 echo $user->name;
7}

选择标量值

有时你的数据库查询可能只产生一个标量值。Laravel 允许你使用 scalar 方法直接检索该值,而无需从记录对象中提取:

1$burgers = DB::scalar(
2 "select count(case when food = 'burger' then 1 end) as burgers from menu"
3);

选择多个结果集

如果你的应用程序调用返回多个结果集的存储过程,可以使用 selectResultSets 方法来检索存储过程返回的所有结果集:

1[$options, $notifications] = DB::selectResultSets(
2 "CALL get_user_options_and_notifications(?)", $request->user()->id
3);

使用命名绑定

除了使用 ? 来表示参数绑定外,你还可以使用命名绑定来执行查询:

1$results = DB::select('select * from users where id = :id', ['id' => 1]);

运行 Insert 语句

要执行 insert 语句,可以使用 DB 门面上的 insert 方法。与 select 一样,该方法接受 SQL 查询作为第一个参数,绑定作为第二个参数:

1use Illuminate\Support\Facades\DB;
2 
3DB::insert('insert into users (id, name) values (?, ?)', [1, 'Marc']);

运行 Update 语句

update 方法应仅用于更新数据库中的现有记录。该方法会返回语句影响的行数:

1use Illuminate\Support\Facades\DB;
2 
3$affected = DB::update(
4 'update users set votes = 100 where name = ?',
5 ['Anita']
6);

运行 Delete 语句

delete 方法应仅用于删除数据库中的记录。与 update 一样,该方法会返回受影响的行数:

1use Illuminate\Support\Facades\DB;
2 
3$deleted = DB::delete('delete from users');

运行通用语句

某些数据库语句不返回任何值。对于这些类型的操作,你可以使用 DB 门面上的 statement 方法:

1DB::statement('drop table users');

运行未经预处理的语句

有时你可能希望执行一条不绑定任何值的 SQL 语句。你可以使用 DB 门面的 unprepared 方法来实现:

1DB::unprepared('update users set votes = 100 where name = "Dries"');

由于未经预处理的语句不绑定参数,因此它们可能容易受到 SQL 注入攻击。你不应在未经预处理的语句中包含任何用户可控的值。

隐式提交

在事务中使用 DB 门面的 statementunprepared 方法时,必须小心避免会导致 隐式提交 的语句。这些语句会导致数据库引擎间接地提交整个事务,从而导致 Laravel 无法知晓数据库的事务级别。此类语句的一个例子是创建数据库表:

1DB::unprepared('create table a (col varchar(1) null)');

请参阅 MySQL 手册以获取 所有触发隐式提交的语句列表

使用多个数据库连接

如果你的应用程序在 config/database.php 配置文件中定义了多个连接,你可以通过 DB 门面提供的 connection 方法访问每个连接。传递给 connection 方法的连接名称应对应于 config/database.php 配置文件中列出的连接名称,或在运行时使用 config 辅助函数配置的名称:

1use Illuminate\Support\Facades\DB;
2 
3$users = DB::connection('sqlite')->select(/* ... */);

你可以使用连接实例上的 getPdo 方法来访问连接底层的原生 PDO 实例:

1$pdo = DB::connection()->getPdo();

监听查询事件

如果你想指定一个在应用程序执行每条 SQL 查询时都会被调用的闭包,可以使用 DB 门面的 listen 方法。这对于查询日志记录或调试非常有用。你可以在 服务提供者boot 方法中注册你的查询监听器闭包:

1<?php
2 
3namespace App\Providers;
4 
5use Illuminate\Database\Events\QueryExecuted;
6use Illuminate\Support\Facades\DB;
7use Illuminate\Support\ServiceProvider;
8 
9class AppServiceProvider extends ServiceProvider
10{
11 /**
12 * Register any application services.
13 */
14 public function register(): void
15 {
16 // ...
17 }
18 
19 /**
20 * Bootstrap any application services.
21 */
22 public function boot(): void
23 {
24 DB::listen(function (QueryExecuted $query) {
25 // $query->sql;
26 // $query->bindings;
27 // $query->time;
28 // $query->toRawSql();
29 });
30 }
31}

监控累计查询时间

现代 Web 应用程序的一个常见性能瓶颈是查询数据库所花费的时间。幸运的是,当 Laravel 在单个请求中查询数据库花费的时间过长时,它可以调用你选择的闭包或回调。首先,通过 whenQueryingForLongerThan 方法提供查询时间阈值(以毫秒为单位)和闭包。你可以在 服务提供者boot 方法中调用此方法:

1<?php
2 
3namespace App\Providers;
4 
5use Illuminate\Database\Connection;
6use Illuminate\Support\Facades\DB;
7use Illuminate\Support\ServiceProvider;
8use Illuminate\Database\Events\QueryExecuted;
9 
10class AppServiceProvider extends ServiceProvider
11{
12 /**
13 * Register any application services.
14 */
15 public function register(): void
16 {
17 // ...
18 }
19 
20 /**
21 * Bootstrap any application services.
22 */
23 public function boot(): void
24 {
25 DB::whenQueryingForLongerThan(500, function (Connection $connection, QueryExecuted $event) {
26 // Notify development team...
27 });
28 }
29}

数据库事务

你可以使用 DB 门面提供的 transaction 方法在一组数据库事务中运行操作。如果在事务闭包内抛出异常,事务将自动回滚,并且异常会被重新抛出。如果闭包执行成功,事务将自动提交。使用 transaction 方法时,无需担心手动回滚或提交:

1use Illuminate\Support\Facades\DB;
2 
3DB::transaction(function () {
4 DB::update('update users set votes = 1');
5 
6 DB::delete('delete from posts');
7});

处理死锁

transaction 方法接受一个可选的第二个参数,用于定义发生死锁时事务应该重试的次数。一旦重试次数耗尽,就会抛出异常:

1use Illuminate\Support\Facades\DB;
2 
3DB::transaction(function () {
4 DB::update('update users set votes = 1');
5 
6 DB::delete('delete from posts');
7}, attempts: 5);

手动使用事务

如果你想手动开始事务并完全控制回滚和提交,可以使用 DB 门面提供的 beginTransaction 方法:

1use Illuminate\Support\Facades\DB;
2 
3DB::beginTransaction();

你可以通过 rollBack 方法回滚事务:

1DB::rollBack();

最后,你可以通过 commit 方法提交事务:

1DB::commit();

DB 门面的事务方法同时控制 查询构建器Eloquent ORM 的事务。

连接数据库 CLI

如果你想连接到数据库的 CLI,可以使用 db Artisan 命令:

1php artisan db

如果需要,你可以指定一个数据库连接名称,以连接到非默认的数据库连接:

1php artisan db mysql

检查你的数据库

使用 db:showdb:table Artisan 命令,你可以深入了解你的数据库及其关联的表。要查看数据库的概览(包括其大小、类型、打开的连接数以及表摘要),可以使用 db:show 命令:

1php artisan db:show

你可以通过 --database 选项将数据库连接名称传递给命令,以指定要检查的数据库连接:

1php artisan db:show --database=pgsql

如果你想在命令的输出中包含表行数和数据库视图详情,可以分别提供 --counts--views 选项。在大型数据库上,检索行数和视图详情可能会很慢:

1php artisan db:show --counts --views

此外,你可以使用以下 Schema 方法来检查你的数据库:

1use Illuminate\Support\Facades\Schema;
2 
3$tables = Schema::getTables();
4$views = Schema::getViews();
5$columns = Schema::getColumns('users');
6$indexes = Schema::getIndexes('users');
7$foreignKeys = Schema::getForeignKeys('users');

如果你想检查非应用程序默认连接的数据库连接,可以使用 connection 方法:

1$columns = Schema::connection('sqlite')->getColumns('users');

表概览

如果你想获取数据库中单个表的概览,可以执行 db:table Artisan 命令。该命令提供了数据库表的常规概览,包括列、类型、属性、键和索引:

1php artisan db:table users

监控你的数据库

使用 db:monitor Artisan 命令,你可以指示 Laravel 在数据库管理的打开连接数超过指定数量时分发一个 Illuminate\Database\Events\DatabaseBusy 事件。

首先,你应该将 db:monitor 命令安排为 每分钟运行一次。该命令接受你想要监控的数据库连接配置名称,以及在分发事件前所能容忍的最大打开连接数:

1php artisan db:monitor --databases=mysql,pgsql --max=100

仅安排此命令不足以触发通知来提醒你打开的连接数。当命令遇到打开连接数超过阈值的数据库时,会分发一个 DatabaseBusy 事件。你应该在应用程序的 AppServiceProvider 中监听此事件,以便向你或你的开发团队发送通知:

1use App\Notifications\DatabaseApproachingMaxConnections;
2use Illuminate\Database\Events\DatabaseBusy;
3use Illuminate\Support\Facades\Event;
4use Illuminate\Support\Facades\Notification;
5 
6/**
7 * Bootstrap any application services.
8 */
9public function boot(): void
10{
11 Event::listen(function (DatabaseBusy $event) {
12 Notification::route('mail', '[email protected]')
13 ->notify(new DatabaseApproachingMaxConnections(
14 $event->connectionName,
15 $event->connections
16 ));
17 });
18}