Skip to content

数据库入门

介绍

几乎每个现代 Web 应用程序都与数据库交互。Laravel 使用原始 SQL、流畅的查询生成器Eloquent ORM 在各种受支持的数据库中与数据库进行交互变得极其简单。目前,Laravel 为五个数据库提供第一方支持:

配置

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

默认情况下,Laravel 的示例环境配置已准备好与 Laravel Sail 一起使用,Laravel 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

NOTE

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

Microsoft SQL Server 配置

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

使用 URL 进行配置

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

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

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

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

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

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

读取和写入连接

有时,您可能希望将一个数据库连接用于 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读取键和写入键具有包含单个键的数组值:host读取写入连接的其余数据库选项将从主 mysql 配置数组中合并。

如果您希望覆盖主 mysql 数组中的值,则只需将项目放在 readwrite 数组中。因此,在这种情况下,192.168.1.1 将用作 “read” 连接的主机,而 192.168.1.3 将用于 “write” 连接。主 mysql 数组中的数据库凭据、前缀、字符集和所有其他选项将在两个连接之间共享。当 host 配置数组中存在多个值时,将为每个请求随机选择一个数据库主机。

sticky 选项

sticky 选项是一个可选值,可用于允许在当前请求周期内立即读取已写入数据库的记录。如果启用了 sticky 选项,并且在当前请求周期内对数据库执行了 “write” 操作,则任何进一步的 “read” 操作都将使用 “write” 连接。这可确保在请求周期内写入的任何数据都可以在同一请求期间立即从数据库中读回。由您决定这是否是您的应用程序所需的行为。

运行 SQL 查询

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

运行 Select 查询

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

php
    <?php

    namespace App\Http\Controllers;

    use App\Http\Controllers\Controller;
    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]);
        }
    }

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

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

php
    use Illuminate\Support\Facades\DB;

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

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

选择标量值

有时,您的数据库查询可能会产生单个标量值。Laravel 允许您使用 scalar 方法直接检索该值,而不是需要从 record 对象中检索查询的标量结果:

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
    );

使用命名绑定

您可以使用 named bindings 执行查询,而不是使用 来表示您的参数绑定:

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

运行 Insert 语句

要执行 insert 语句,您可以在 DB Facade 上使用 insert 方法。与 select 一样,此方法接受 SQL 查询作为其第一个参数,并将 bindings 作为其第二个参数:

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 方法的连接名称应对应于 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
    {
        /**
         * 注册任何应用程序服务。
         */
        public function register(): void
        {
            // ...
        }

        /**
         * 引导任何应用程序服务。
         */
        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
    {
        /**
         * 注册任何应用程序服务。
         */
        public function register(): void
        {
            // ...
        }

        /**
         * 引导任何应用程序服务。
         */
        public function boot(): void
        {
            DB::whenQueryingForLongerThan(500, function (Connection $connection, QueryExecuted $event) {
                // Notify development team...
            });
        }
    }

数据库事务

您可以使用 DB Facade 提供的 transaction 方法在数据库事务中运行一组操作。如果在事务关闭中引发异常,则事务将自动回滚并重新引发异常。如果 closure 成功执行,则将自动提交事务。使用 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');
    }, 5);

手动使用事务

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

php
    use Illuminate\Support\Facades\DB;

    DB::beginTransaction();

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

php
    DB::rollBack();

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

php
    DB::commit();

NOTE

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;

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