Skip to content
全部文档

数据库:入门

简介

几乎每个现代 Web 应用都会与数据库交互。Laravel 通过原始 SQL、流畅的查询构建器 以及 Eloquent ORM,让你在多种受支持的数据库上都能极其轻松地完成交互。目前,Laravel 对以下五种数据库提供官方支持:

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

配置

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

默认情况下,Laravel 的示例环境配置已可配合 Laravel Sail 使用;Sail 是一套用于在本机开发 Laravel 应用的 Docker 配置。不过,你也可以按本地数据库需要自行修改数据库配置。

SQLite 配置

SQLite 数据库以文件系统上的单个文件形式存在。可在终端使用 touch 命令创建新的 SQLite 数据库:touch database/database.sqlite。创建数据库后,将数据库的绝对路径放入 DB_DATABASE 环境变量,即可轻松让环境变量指向该数据库:

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

默认情况下,SQLite 连接会启用外键约束。若要禁用,可将 DB_FOREIGN_KEYS 环境变量设为 false

ini
DB_FOREIGN_KEYS=false

INFO

若使用 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 可能类似如下:

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

这些 URL 通常遵循标准 schema 约定:

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

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

读写连接

有时你可能希望 SELECT 语句使用一个数据库连接,而 INSERT、UPDATE、DELETE 语句使用另一个。Laravel 让这件事变得很轻松;无论你使用原始查询、查询构建器还是 Eloquent ORM,都会始终使用正确的连接。

要了解应如何配置读 / 写连接,请看下面的示例:

php
'mysql' => [
    'driver' => 'mysql',
    
    'read' => [
        'host' => [
            '192.168.1.1',
            '196.168.1.2',
        ],
    ],
    'write' => [
        'host' => [
            '192.168.1.3',
        ],
    ],
    'sticky' => true,
    
    'port' => env('DB_PORT', '3306'),
    '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([
        (PHP_VERSION_ID >= 80500 ? \Pdo\Mysql::ATTR_SSL_CA : \PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'),
    ]) : [],
],

注意配置数组中新增了三个键:readwritestickyreadwrite 的值是只含 host 一个键的数组。readwrite 连接的其余数据库选项会从主 mysql 配置数组合并而来。

仅当你希望覆盖主 mysql 数组中的值时,才需要在 readwrite 数组中放置项。因此在本例中,「读」连接会使用主机 192.168.1.1,「写」连接会使用 192.168.1.3。主 mysql 数组中的数据库凭证、前缀、字符集及其他所有选项会在两个连接间共享。当 host 配置数组中存在多个值时,每次请求会随机选择一个数据库主机。

sticky 选项

sticky 选项是一个可选值,可用于允许立即读取在当前请求周期内已写入数据库的记录。若启用 sticky,且当前请求周期内已对数据库执行过「写」操作,则后续任何「读」操作都会使用「写」连接。这可确保在同一请求中写入的数据能立即从数据库读回。是否采用该行为由你根据应用需求决定。

运行 SQL 查询

配置好数据库连接后,即可使用 DB facade 运行查询。DB facade 为每种查询类型提供了方法:selectupdateinsertdeletestatement

运行 Select 查询

要运行基本的 SELECT 查询,可使用 DB facade 的 select 方法:

php
<?php

namespace App\Http\Controllers;

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

class UserController extends Controller
{
    /**
     * Show a list of all of the application's users.
     */
    public function index(): View
    {
        $users = DB::select('select * from users where active = ?', [1]);

        return view('user.index', ['users' => $users]);
    }
}

传给 select 方法的第一个参数是 SQL 查询,第二个参数是需要绑定到查询的参数绑定。通常这些是 where 子句约束的值。参数绑定可防止 SQL 注入。

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

php
use Illuminate\Support\Facades\DB;

$users = DB::select('select * from users');

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

选择标量值

有时数据库查询可能只得到单个标量值。不必再从记录对象中取出查询的标量结果,Laravel 允许你使用 scalar 方法直接获取该值:

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

选择多个结果集

若应用调用会返回多个结果集的存储过程,可使用 selectResultSets 方法检索存储过程返回的全部结果集:

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

使用命名绑定

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

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

运行 Insert 语句

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

php
use Illuminate\Support\Facades\DB;

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

运行 Update 语句

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

php
use Illuminate\Support\Facades\DB;

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

运行 Delete 语句

应使用 delete 方法从数据库删除记录。与 update 一样,方法会返回影响的行数:

php
use Illuminate\Support\Facades\DB;

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

运行通用语句

有些数据库语句不返回任何值。对于此类操作,可使用 DB facade 的 statement 方法:

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

运行未预处理语句

有时你可能想在不绑定任何值的情况下执行 SQL 语句。可使用 DB facade 的 unprepared 方法完成:

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

WARNING

由于未预处理语句不绑定参数,它们可能易受 SQL 注入攻击。切勿在未预处理语句中允许用户可控的值。

隐式提交

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

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

请参阅 MySQL 手册获取会触发隐式提交的全部语句列表

使用多个数据库连接

若应用在 config/database.php 配置文件中定义了多个连接,可通过 DB facade 提供的 connection 方法访问每个连接。传给 connection 方法的连接名称应对应 config/database.php 中列出的某个连接,或在运行时用 config 助手配置的连接:

php
use Illuminate\Support\Facades\DB;

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

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

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

监听查询事件

若希望为应用执行的每条 SQL 查询指定一个被调用的闭包,可使用 DB facade 的 listen 方法。该方法对记录查询或调试很有用。可在服务提供者boot 方法中注册查询监听闭包:

php
<?php

namespace App\Providers;

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

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        // ...
    }

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        DB::listen(function (QueryExecuted $query) {
            // $query->sql;
            // $query->bindings;
            // $query->time;
            // $query->toRawSql();
        });
    }
}

监控累计查询时间

现代 Web 应用常见的性能瓶颈之一,是花在查询数据库上的时间。幸好,当单次请求中查询数据库耗时过长时,Laravel 可调用你指定的闭包或回调。首先,向 whenQueryingForLongerThan 方法提供查询时间阈值(毫秒)和闭包。可在服务提供者boot 方法中调用该方法:

php
<?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
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        // ...
    }

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        DB::whenQueryingForLongerThan(500, function (Connection $connection, QueryExecuted $event) {
            // Notify development team...
        });
    }
}

数据库事务

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

php
use Illuminate\Support\Facades\DB;

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

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

处理死锁

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

php
use Illuminate\Support\Facades\DB;

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

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

手动使用事务

若希望手动开启事务并完全控制回滚与提交,可使用 DB facade 提供的 beginTransaction 方法:

php
use Illuminate\Support\Facades\DB;

DB::beginTransaction();

可通过 rollBack 方法回滚事务:

php
DB::rollBack();

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

php
DB::commit();

INFO

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

连接到数据库 CLI

若要连接到数据库的 CLI,可使用 db Artisan 命令:

shell
php artisan db

如有需要,可指定数据库连接名称,以连接到非默认连接:

shell
php artisan db mysql

检查数据库

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

shell
php artisan db:show

可通过 --database 选项向命令提供数据库连接名称,指定要检查的连接:

shell
php artisan db:show --database=pgsql

若希望在命令输出中包含表行数与数据库视图详情,可分别提供 --counts--views 选项。在大型数据库上,获取行数与视图详情可能会较慢:

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

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

php
use Illuminate\Support\Facades\Schema;

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

若要检查非应用默认连接的数据库连接,可使用 connection 方法:

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

表概览

若要获取数据库中单个表的概览,可执行 db:table Artisan 命令。该命令提供数据库表的总体概览,包括列、类型、属性、键与索引:

shell
php artisan db:table users

监控数据库

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

首先,应将 db:monitor 命令调度为每分钟运行。该命令接受你希望监控的数据库连接配置名称,以及在派发事件前可容忍的最大打开连接数:

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

仅调度该命令并不足以触发关于打开连接数的通知。当命令发现某个数据库的打开连接数超过你的阈值时,会派发 DatabaseBusy 事件。你应在应用的 AppServiceProvider 中监听该事件,以便向你或开发团队发送通知:

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

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Event::listen(function (DatabaseBusy $event) {
        Notification::route('mail', 'dev@example.com')
            ->notify(new DatabaseApproachingMaxConnections(
                $event->connectionName,
                $event->connections
            ));
    });
}