Lzh on GitHub

简介

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

  • MariaDB 10.3+(版本策略)
  • MySQL 5.7+(版本策略)
  • PostgreSQL 10.0+(版本策略)
  • SQLite 3.26.0+
  • SQL Server 2017+(版本策略)

此外,MongoDB 可以通过官方维护的 mongodb/laravel-mongodb 包支持。更多信息请参阅 Laravel MongoDB 文档。

配置

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

默认情况下,Laravel 的示例环境配置可与 Laravel Sail 配合使用,Sail 是一个用于在本地机器上开发 Laravel 应用的 Docker 配置。不过,你可以根据本地数据库的实际情况自由修改配置。

SQLite 配置

SQLite 数据库是一个单文件数据库。你可以通过在终端中使用 touch 命令创建一个新的 SQLite 数据库,例如:

touch database/database.sqlite

创建数据库后,你可以通过设置环境变量 DB_DATABASE 指向该数据库的绝对路径来配置数据库:

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

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

DB_FOREIGN_KEYS=false

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

Microsoft SQL Server 配置

使用 Microsoft SQL Server 数据库时,应确保已安装 sqlsrvpdo_sqlsrv PHP 扩展,以及它们可能依赖的 Microsoft SQL ODBC 驱动。

使用 URL 配置

通常,数据库连接需要配置多个参数,如 host、database、username、password 等,每个配置项都有对应的环境变量。这意味着在生产服务器上配置数据库时,需要管理多个环境变量。

一些托管数据库提供商(如 AWS 和 Heroku)提供单个数据库 URL,其中包含所有数据库连接信息,例如:

mysql://root:password@127.0.0.1/forge?charset=UTF-8

这些 URL 通常遵循以下标准格式:

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

为方便起见,Laravel 支持使用此类 URL 作为配置数据库的替代方式。如果配置选项 url(或对应的环境变量 DB_URL)存在,Laravel 会从中提取数据库连接和凭据信息。

读写连接

有时你可能希望对 SELECT 语句使用一个数据库连接,而对 INSERT、UPDATE 和 DELETE 语句使用另一个数据库连接。Laravel 可以轻松实现这一点,无论你使用的是原生查询、查询构建器还是 Eloquent ORM,都会自动使用正确的连接。

下面是一个配置示例,展示了如何设置读/写连接:

'mysql' => [
    'read' => [
        'host' => [
            '192.168.1.1',
            '196.168.1.2',
        ],
    ],
    'write' => [
        'host' => [
            '196.168.1.3',
        ],
    ],
    'sticky' => true,
 
    'database' => env('DB_DATABASE', 'laravel'),
    'username' => env('DB_USERNAME', 'root'),
    'password' => env('DB_PASSWORD', ''),
    'unix_socket' => env('DB_SOCKET', ''),
    'charset' => env('DB_CHARSET', 'utf8mb4'),
    'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
    'prefix' => '',
    'prefix_indexes' => true,
    'strict' => true,
    'engine' => null,
    'options' => extension_loaded('pdo_mysql') ? array_filter([
        PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
    ]) : [],
],

注意在配置数组中新增了三个键:readwritesticky

  • readwrite 的值是数组,包含 host 键,用于指定对应的数据库主机。
  • 其他数据库配置选项(如数据库名、用户名、密码、前缀、字符集等)会从主 mysql 配置数组中合并。

你只需在 readwrite 数组中放置需要覆盖主 mysql 配置的内容即可。例如,上例中 192.168.1.1 用作 读连接 的主机,而 196.168.1.3 用作 写连接 的主机。若 host 数组中存在多个值,Laravel 会在每次请求时随机选择一个数据库主机。

sticky 选项

sticky 是一个可选配置,用于在当前请求周期内保证刚写入的数据可以立即被读取。

  • sticky 启用并且在当前请求周期中执行过 写操作 时,后续的 读操作 将使用 写连接
  • 这样可以确保当前请求中写入的数据能够立即从数据库中读取出来。

是否启用 sticky 取决于你对应用行为的需求。

执行 SQL 查询

配置好数据库连接后,你可以使用 DB 门面 来执行查询。DB 门面提供了多种方法来执行不同类型的查询:selectupdateinsertdeletestatement

执行 SELECT 查询

使用 select 方法可以执行基本的 SELECT 查询:

<?php

namespace App\Http\Controllers;

use Illuminate\Support\Facades\DB;
use Illuminate\View\View;

class UserController extends Controller
{
    /**
     * 显示应用的所有用户列表。
     */
    public function index(): View
    {
        $users = DB::select('select * from users where active = ?', [1]);

        return view('user.index', ['users' => $users]);
    }
}
  • 第一个参数是 SQL 查询语句,第二个参数是查询绑定的参数(通常是 where 条件的值)。
  • 参数绑定可以防止 SQL 注入
  • select 方法总是返回一个结果数组,每个结果都是一个 PHP stdClass 对象,表示数据库中的一条记录:
$users = DB::select('select * from users');

foreach ($users as $user) {
    echo $user->name;
}

查询单一标量值

当查询结果是单个标量值时,可以使用 scalar 方法直接获取:

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

查询多个结果集

调用存储过程返回多个结果集时,可以使用 selectResultSets 方法:

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

使用命名绑定

除了 ? 占位符,也可以使用命名绑定:

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

执行 INSERT 语句

使用 insert 方法执行插入操作:

DB::insert('insert into users (id, name) values (?, ?)', [1, 'Marc']);

执行 UPDATE 语句

使用 update 方法更新数据库记录,返回受影响的行数:

$affected = DB::update(
    'update users set votes = 100 where name = ?',
    ['Anita']
);

执行 DELETE 语句

使用 delete 方法删除记录,同样返回受影响的行数:

$deleted = DB::delete('delete from users');

执行普通语句

某些 SQL 语句不返回值,可以使用 statement 方法:

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

执行未预处理语句

如果不希望绑定参数,可以使用 unprepared 方法:

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

⚠️ 注意:未预处理语句不绑定参数,可能存在 SQL 注入风险,不要使用用户提供的值。

隐式提交

在事务中使用 statementunprepared 方法时,要注意避免导致 隐式提交 的语句,这类语句会让数据库自动提交整个事务,而 Laravel 无法感知事务状态。例如创建表语句:

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

有关会触发隐式提交的 SQL 语句列表,请参考 MySQL 官方文档。

使用多个数据库连接

如果你的应用在 config/database.php 配置文件中定义了多个数据库连接,可以通过 DB 门面的 connection 方法 来访问每个连接。传递给 connection 方法的连接名应对应配置文件中列出的某个连接,或者通过 config 辅助函数在运行时动态配置:

use Illuminate\Support\Facades\DB;

$users = DB::connection('sqlite')->select(/* ... */);

你也可以通过连接实例的 getPdo 方法访问底层的原生 PDO 实例:

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

监听查询事件

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

<?php

namespace App\Providers;

use Illuminate\Database\Events\QueryExecuted;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * 注册应用服务。
     */
    public function register(): void
    {
        // ...
    }

    /**
     * 启动应用服务。
     */
    public function boot(): void
    {
        DB::listen(function (QueryExecuted $query) {
            // $query->sql;      // 查询的 SQL 语句
            // $query->bindings; // 绑定的参数
            // $query->time;     // 查询执行时间(毫秒)
            // $query->toRawSql();// 将 SQL 和绑定参数组合成原始 SQL
        });
    }
}

监控累计查询时间

现代 Web 应用的一个常见性能瓶颈是数据库查询耗费的时间。幸运的是,Laravel 提供了 whenQueryingForLongerThan 方法,允许你在单次请求中数据库查询耗时过长时,调用你指定的闭包或回调函数。

你可以在服务提供者的 boot 方法中使用此方法,提供一个查询时间阈值(以毫秒为单位)以及相应的闭包:

<?php

namespace App\Providers;

use Illuminate\Database\Connection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\ServiceProvider;
use Illuminate\Database\Events\QueryExecuted;

class AppServiceProvider extends ServiceProvider
{
    /**
     * 注册应用服务。
     */
    public function register(): void
    {
        // ...
    }

    /**
     * 启动应用服务。
     */
    public function boot(): void
    {
        DB::whenQueryingForLongerThan(500, function (Connection $connection, QueryExecuted $event) {
            // 通知开发团队或记录慢查询...
        });
    }
}

在这个例子中,如果查询时间超过 500 毫秒,就会执行闭包中的逻辑,你可以用它来记录慢查询或触发告警。

数据库事务

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

use Illuminate\Support\Facades\DB;

DB::transaction(function () {
    DB::update('update users set votes = 1');

    DB::delete('delete from posts');
});

处理死锁

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

use Illuminate\Support\Facades\DB;

DB::transaction(function () {
    DB::update('update users set votes = 1');

    DB::delete('delete from posts');
}, attempts: 5);

手动使用事务

如果你希望手动开启事务,并完全控制回滚和提交,可以使用 DBbeginTransaction 方法:

use Illuminate\Support\Facades\DB;

DB::beginTransaction();

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

DB::rollBack();

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

DB::commit();

注意: DB 门面提供的事务方法可以同时控制查询构建器(Query Builder)和 Eloquent ORM 的事务行为。

连接到数据库命令行界面(CLI)

如果你想连接到数据库的命令行界面(CLI),可以使用 db Artisan 命令

php artisan db

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

php artisan db mysql

查看数据库

使用 db:showdb:table Artisan 命令,你可以获取数据库及其相关表的详细信息。

要查看数据库的概览,包括数据库大小、类型、打开连接数以及表的汇总信息,可以使用 db:show 命令:

php artisan db:show

你可以通过 --database 选项指定要检查的数据库连接:

php artisan db:show --database=pgsql

如果希望在输出中包含表的行数统计和视图详情,可以分别使用 --counts--views 选项。对于大型数据库,获取行数和视图详情可能会比较慢:

php artisan db:show --counts --views

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

use Illuminate\Support\Facades\Schema;

$tables = Schema::getTables();
$views = Schema::getViews();
$columns = Schema::getColumns('users');
$indexes = Schema::getIndexes('users');
$foreignKeys = Schema::getForeignKeys('users');

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

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

表概览

如果想查看数据库中某个表的详细概览,可以使用 db:table Artisan 命令。此命令会显示表的列、类型、属性、键和索引信息:

php artisan db:table users

监控数据库

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

首先,你应该将 db:monitor 命令调度为每分钟执行一次。该命令接受要监控的数据库连接名称,以及在触发事件前允许的最大打开连接数:

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

仅调度此命令本身并不足以提醒你打开连接数过多。当命令检测到数据库的打开连接数超过阈值时,会触发一个 DatabaseBusy 事件。你应在应用的 AppServiceProvider 中监听此事件,以便向你或开发团队发送通知:

use App\Notifications\DatabaseApproachingMaxConnections;
use Illuminate\Database\Events\DatabaseBusy;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Notification;

/**
 * 启动应用服务。
 */
public function boot(): void
{
    Event::listen(function (DatabaseBusy $event) {
        Notification::route('mail', 'dev@example.com')
            ->notify(new DatabaseApproachingMaxConnections(
                $event->connectionName,
                $event->connections
            ));
    });
}