简介

几乎所有的应用程序都需要和数据库进行交互。Laravel 为此提供了一套非常简单易用的数据库交互方式。开发者可以使用原生 SQL,查询构造器,以及Eloquent ORM,等方式与数据库交互。目前,Laravel 为以下五种数据库提供了官方支持:

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

配置

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

在默认情况下,Laravel 的示例 环境配置 使用了 Laravel Sail,Laravel Sail 是一种用于在本地开发 Laravel 应用的 Docker 配置。但你依然可以根据本地数据库的需要修改数据库配置。

SQLite 配置
SQLite数据库包含在文件系统的单个文件中。你可以在终端上使用 php touch 命令创建一个新的SQLite数据库:php touch database/database.sqlite。创建数据库之后,你可以通过在环境变量 php DB_DATABASE 中设置数据库的绝对路径,轻松地配置环境变量来指向这个数据库:

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

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

  1. DB_FOREIGN_KEYS=false


[!注意]
如果你使用Laravel安装程序来创建你的Laravel应用程序,并选择SQLite作为你的数据库,Laravel将自动创建一个 php database/database.sqlite 文件运行默认的数据库迁移。

Microsoft SQL Server 配置
要使用Microsoft SQL Server数据库,您应该确保安装了 php sqlsrv and php pdo_sqlsrv PHP扩展以及它们可能需要的任何依赖项,例如Microsoft SQL ODBC驱动程序。

使用URL配置
通常,数据库连接使用多个配置值进行配置,例如 php host, php database, php username, php password 等。这些配置值中的每一个都有自己相应的环境变量。这意味着在生产服务器上配置数据库连接信息时,需要管理几个环境变量。

部分数据库托管平台(如 AWS 和 Heroku)会提供了包含所有连接信息的数据库「URL」。它们通常看起来像这样:

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

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

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

为了方便起见,Laravel 支持使用这些 URL 替代传统的配置项来配置你的数据库。如果配置项 php url (或其对应的环境变量 php DATABASE_URL )存在,那么 Laravel 将会尝试从 URL 中提取数据库连接以及凭证信息。

读写分离

有时候你可能会希望使用一个数据库连接来执行 php SELECT 语句,而 php INSERTphp UPDATEphp DELETE 语句则由另一个数据库连接来执行。在 Laravel 中,无论你是使用原生 SQL 查询、查询构造器 或是 php Eloquent ORM,都能轻松实现读写分离。

为了弄明白如何配置读写分离,我们先来看个例子:

  1. 'mysql' => [
  2. 'read' => [
  3. 'host' => [
  4. '192.168.1.1',
  5. '196.168.1.2',
  6. ],
  7. ],
  8. 'write' => [
  9. 'host' => [
  10. '196.168.1.3',
  11. ],
  12. ],
  13. 'sticky' => true,
  14. 'database' => env('DB_DATABASE', 'laravel'),
  15. 'username' => env('DB_USERNAME', 'root'),
  16. 'password' => env('DB_PASSWORD', ''),
  17. 'unix_socket' => env('DB_SOCKET', ''),
  18. 'charset' => env('DB_CHARSET', 'utf8mb4'),
  19. 'collation' => env('DB_COLLATION', 'utf8mb4_0900_ai_ci'),
  20. 'prefix' => '',
  21. 'prefix_indexes' => true,
  22. 'strict' => true,
  23. 'engine' => null,
  24. 'options' => extension_loaded('pdo_mysql') ? array_filter([
  25. PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
  26. ]) : [],
  27. ],

请注意,我们在数据库配置中加入了三个键,分别是: php read, php write 以及 php stickyphp readphp write 的值是一个只包含 php host 键的数组。这代表其他的数据库选项将会从主 php mysql 配置中获取。

如果你想要覆写主 php mysql 配置,只需要将需要覆写的值放到 php readphp write数组里即可。所以,在这个例子中,php 192.168.1.1 将会被用作「读」连接主机,而 php 192.168.1.3 将作为「写」连接主机。这两个连接将共享 php mysql 数组中的各项配置,如数据库凭证(用户名、密码)、前缀、字符编码等。如果 php host 数组中存在多个值,php Laravel 将会为每个连接随机选取所使用的数据库主机。

sticky 选项
php sticky 是一个 可选 值,它用于允许 php Laravel 立即读取在当前请求周期内写入到数据库的记录。若 php sticky 选项被启用,且在当前请求周期中执行过「写」操作,那么在这之后的所有「读」操作都将使用「写」连接。这样可以确保同一个请求周期中写入的数据库可以被立即读取到,从而避免主从同步延迟导致的数据不一致。不过是否启用它取决于项目的实际需求。

执行原生 SQL 查询

一旦配置好数据库连接,你就可以使用 php DB Facade 来执行查询。php DB Facade 为每种类型的查询都提供了相应的方法:php selectphp updatephp insertphp delete 以及 php statement

执行 SELECT 查询
你可以使用 php DB Facade 的 php select 方法来执行一个基础的 SELECT 查询:

  1. <?php
  2. namespace App\Http\Controllers;
  3. use App\Http\Controllers\Controller;
  4. use Illuminate\Support\Facades\DB;
  5. use Illuminate\View\View;
  6. class UserController extends Controller
  7. {
  8. /**
  9. * 显示所有应用程序用户的列表。
  10. */
  11. public function index(): View
  12. {
  13. $users = DB::select('select * from users where active = ?', [1]);
  14. return view('user.index', ['users' => $users]);
  15. }
  16. }

传递给 php select 方法的第一个参数是一个原生 SQL 查询语句,而第二个参数则是需要绑定到查询中的参数值。通常,这些值用于约束 php where 语句。使用参数绑定可以有效防止 SQL 注入。

php select 方法将始终返回一个包含查询结果的数组。数组中的每个结果都对应一个数据库记录的 php stdClass 对象:

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

选择标量值
有时你的数据库查询可能得到一个单一的标量值。而不是需要从记录对象中检索查询的标量结果,Laravel 允许你直接使用 php scalar 方法检索此值:

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

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

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

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

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

执行 Insert 语句
你可以使用 php DB Facade 的 php insert 方法来执行语句。跟 php select 方法一样,该方法的第一个和第二个参数分别是原生 SQL 语句和绑定的数据:

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

执行 Update 语句
php update 方法用于更新数据库中现有的记录。该方法将会返回受到本次操作影响的记录行数:

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

执行 Delete 语句
php delete 函数被用于删除数据库中的记录。它的返回值与 php update 函数相同,返回本次操作受影响的总行数。

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

执行指定的 SQL
部分 SQL 语句不返回任何值。在这种情况下,你可能需要使用 php DB::statement($sql) 来执行你的 SQL 语句。

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

直接执行 SQL
有时候你可能想执行一段 SQL 语句,但不需要进行 SQL 预处理绑定。这种情况下你可以使用 php DB::unprepared($sql) 来执行你的 SQL 语句。

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


注意
未经过预处理 SQL 的语句不绑定参数,它们可能容易受到 SQL 注入的攻击。在没有必要的理由的情况下,你不应直接在 SQL 中使用用户传入的数据。

在事务中的隐式提交
在事务中使用 php DB::statement($sql)php DB::unprepared($sql) 时,你必须要谨慎处理,避免 SQL 语句产生 隐式提交。这些语句会导致数据库引擎间接地提交整个事务,让 Laravel 丢失数据库当前的事务级别。创建数据库表就是一个这样的语句的例子:

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

请参考 MySQL 官方手册以了解更多隐式提交的信息。

使用多数据库连接

如果你在配置文件 php config/database.php 中定义了多个数据库连接的话,你可以通过 php DB Facade 的 php connection 方法来使用它们。传递给 php connection 方法的连接名称应该是你在 php config/database.php 里或者通过 php config 助手函数在运行时配置的连接之一:

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

你也可以使用一个连接实例上的 php getPdo 方法来获取底层的 PDO 实例:

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

监听查询事件

如果你想要获取程序执行的每一条 SQL 语句,可以使用 Db facade 的 listen 方法。该方法对查询日志和调试非常有用,你可以在服务提供者中使用 boot 方法注册查询监听器。

  1. <?php
  2. namespace App\Providers;
  3. use Illuminate\Database\Events\QueryExecuted;
  4. use Illuminate\Support\Facades\DB;
  5. use Illuminate\Support\ServiceProvider;
  6. class AppServiceProvider extends ServiceProvider
  7. {
  8. /**
  9. * 注册任意应用服务
  10. */
  11. public function register(): void
  12. {
  13. // ...
  14. }
  15. /**
  16. * 引导任意应用服务
  17. */
  18. public function boot(): void
  19. {
  20. DB::listen(function (QueryExecuted $query) {
  21. // $query->sql;
  22. // $query->bindings;
  23. // $query->time;
  24. });
  25. }
  26. }

监控累积查询时间

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

  1. <?php
  2. namespace App\Providers;
  3. use Illuminate\Database\Connection;
  4. use Illuminate\Support\Facades\DB;
  5. use Illuminate\Support\ServiceProvider;
  6. use Illuminate\Database\Events\QueryExecuted;
  7. class AppServiceProvider extends ServiceProvider
  8. {
  9. /**
  10. * 注册任意应用服务
  11. */
  12. public function register(): void
  13. {
  14. // ...
  15. }
  16. /**
  17. * 引导任意应用服务
  18. */
  19. public function boot(): void
  20. {
  21. DB::whenQueryingForLongerThan(500, function (Connection $connection, QueryExecuted $event) {
  22. // 通知开发团队...
  23. });
  24. }
  25. }

数据库事务

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

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

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

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

手动执行事务
如果你想要手动处理事务并完全控制回滚和提交,可以使用 php DB facade 提供的 php beginTransaction 方法:

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

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

  1. DB::rollBack();

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

  1. DB::commit();


技巧
DB 门面的事务方法还可以用于控制查询构造器 和 Eloquent ORM。

连接到数据库 CLI

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

  1. php artisan db

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

  1. php artisan db mysql

检查你的数据库

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

  1. php artisan db:show

你可以通过 php --database 选项向命令提供数据库连接名称来指定应该检查哪个数据库连接:

  1. php artisan db:show --database=pgsql

如果希望在命令的输出中包含表行计数和数据库视图详细信息,你可以分别提供 php --countsphp --views 选项。在大型数据库上,检索行数和视图详细信息可能很慢:

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

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

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

你可以使用 php connection 方法指定数据库连接:

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

表的摘要信息
如果你想获得数据库中单张表的概览,你可以执行 php db:table Artisan 命令。这个命令提供了一个数据库表的概览,包括它的列、类型、属性、键和索引:

  1. php artisan db:table users

监视数据库

使用 php db:monitor Artisan 命令,如果你的数据库正在管理超过指定数量的打开连接,可以通过 Laravel 调度触发 php Illuminate\Database\Events\DatabaseBusy 事件。

开始,你应该将 php db:monitor 命令安排为每分钟运行一次。 该命令接受要监视的数据库连接配置的名称,以及在分派事件之前应允许的最大打开连接数:

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

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

  1. use App\Notifications\DatabaseApproachingMaxConnections;
  2. use Illuminate\Database\Events\DatabaseBusy;
  3. use Illuminate\Support\Facades\Event;
  4. use Illuminate\Support\Facades\Notification;
  5. /**
  6. * 为应用程序注册任何其他事件。
  7. */
  8. public function boot(): void
  9. {
  10. Event::listen(function (DatabaseBusy $event) {
  11. Notification::route('mail', '[email protected]')
  12. ->notify(new DatabaseApproachingMaxConnections(
  13. $event->connectionName,
  14. $event->connections
  15. ));
  16. });
  17. }