入门指南
简介
几乎每个现代 Web 应用都要跟数据库打交道。Laravel 让在各种受支持的数据库上进行交互变得非常简单——无论用原始 SQL、流式查询构造器还是 Eloquent ORM 都一样顺手。目前,Laravel 对以下 5 种数据库提供官方支持:
此外,MongoDB 通过 mongodb/laravel-mongodb 扩展包提供支持,该扩展包由 MongoDB 官方维护。详情请查阅 Laravel MongoDB 文档。
配置
Laravel 数据库服务的配置放在应用的 config/database.php 文件里。你可以在这个文件里定义所有数据库连接,并指定默认使用的连接。文件里的大部分配置项都由环境变量的值驱动。Laravel 支持的大多数数据库系统在这个文件里都给出了示例。
默认情况下,Laravel 示例的环境配置是为 Laravel Sail 准备的——Sail 是用来在本地开发 Laravel 应用的 Docker 配置。当然,你也可以根据本地数据库的实际情况自由修改数据库配置。
SQLite 配置 {#sqlite-configuration}
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 安装器创建 Laravel 应用并选择 SQLite 作为数据库,Laravel 会自动为你创建 database/database.sqlite 文件,并跑一遍默认的数据库迁移。
Microsoft SQL Server 配置 {#mssql-configuration}
要使用 Microsoft SQL Server 数据库,请确保安装了 sqlsrv 和 pdo_sqlsrv PHP 扩展,以及它们依赖的组件(比如 Microsoft SQL ODBC 驱动)。
使用 URL 配置连接 {#configuration-using-urls}
通常,数据库连接是通过一堆配置项来配的,比如 host、database、username、password 等等。每一项都有对应的环境变量。这意味着在生产服务器上配置数据库连接信息时,你要维护好几个环境变量。
某些托管数据库服务商(比如 AWS 和 Heroku)会提供一个单一的数据库「URL」,把整套连接信息塞在一个字符串里。示例 URL 大概长这样:
mysql://root:password@127.0.0.1/forge?charset=UTF-8
这类 URL 一般遵循下面的标准 schema:
driver://username:password@host:port/database?options
为了方便,Laravel 支持用这种 URL 作为多项配置的替代方式。如果配置里存在 url 项(或对应的 DB_URL 环境变量),它就会被用来解析出数据库连接和凭据信息。
读写分离
有时候你希望用一个连接来跑 SELECT 语句,用另一个连接来跑 INSERT、UPDATE 和 DELETE 语句。Laravel 轻松搞定这件事——不管你用的是原始查询、查询构造器还是 Eloquent ORM,总能自动挑对正确的连接。
我们来看一个读写连接的配置示例:
'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'),
]) : [],
],
注意,配置数组里多了三个键:read、write 和 sticky。read 和 write 的值都是数组,各自只包含一个 host 键。read 和 write 连接其余的数据库选项,都会从上级的 mysql 主配置数组里合并进来。
只有需要覆盖 mysql 主数组中的值时,才需要把选项放进 read 和 write 数组。所以在这个例子里,192.168.1.1 会被用作「读」连接的主机,而 192.168.1.3 用作「写」连接的主机。数据库凭据、表前缀、字符集以及 mysql 主数组里的所有其他选项都会在两个连接之间共享。当 host 配置数组里存在多个值时,每次请求会随机挑选一个数据库主机。
sticky 选项 {#the-sticky-option}
sticky 是一个可选选项,用来让你在当前请求周期内立即读回刚写入数据库的记录。如果启用了 sticky,并且当前请求周期内已经执行过「写」操作,那么后续所有的「读」操作都会走「写」连接。这样能保证同一个请求周期内写入的数据可以被立即读回来。至于这是不是你想要的行为,就由你自己决定了。
PostgreSQL 连接池
许多托管的 PostgreSQL 服务商通过 PgBouncer 之类的服务或连接代理提供事务模式的连接池。这种连接池特别适合应用查询,但某些 schema 操作、迁移和维护类命令需要一个直连的数据库连接。
想在 PostgreSQL 上使用事务连接池,只需照常配置好连接池连接,同时通过 direct 配置项提供直连信息:
'pgsql' => [
'driver' => 'pgsql',
// ...
'pooled' => env('DB_POOLED', false),
'direct' => array_filter([
'host' => env('DB_DIRECT_HOST'),
'port' => env('DB_DIRECT_PORT'),
'username' => env('DB_DIRECT_USERNAME'),
'password' => env('DB_DIRECT_PASSWORD'),
'sslmode' => env('DB_DIRECT_SSLMODE'),
]),
],
当一个 PostgreSQL 连接配置为连接池模式时,Laravel 会自动为该连接开启模拟预处理(emulated prepares)。直连连接则会继承 direct 配置里没有显式定义的所有选项,并默认使用原生预处理(native prepares)。
Laravel 会自动为迁移、schema 导出与还原以及 db:wipe、db:show、db:table 等命令使用直连连接。启用连接池模式且配置了直连时,db 命令默认也会走直连;如果你想连到连接池上,可以传 --pooled 选项:
php artisan db --pooled
如果你需要在应用中显式使用直连,可以在连接名后面加上 ::direct 后缀:
DB::connection('pgsql::direct')->statement('create extension if not exists "uuid-ossp"');
执行 SQL 查询
配置好数据库连接后,你就可以通过 DB 门面执行查询了。DB 门面为各种类型的查询提供了对应方法:select、update、insert、delete 和 statement。
执行 Select 查询
要执行一个基本的 SELECT 查询,你可以在 DB 门面上调用 select() 方法:
<?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 对象,表示数据库中的一条记录:
use Illuminate\Support\Facades\DB;
$users = DB::select('select * from users');
foreach ($users as $user) {
echo $user->name;
}
取标量值
有时候查询结果就是单个的标量值。Laravel 允许你用 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() 方法。跟 select() 一样,这个方法的第一个参数是 SQL 查询,第二个参数是绑定值:
use Illuminate\Support\Facades\DB;
DB::insert('insert into users (id, name) values (?, ?)', [1, 'Marc']);
执行 Update 语句
update() 方法用于更新数据库中已有的记录。语句影响的行数会作为返回值:
use Illuminate\Support\Facades\DB;
$affected = DB::update(
'update users set votes = 100 where name = ?',
['Anita']
);
执行 Delete 语句
delete() 方法用于从数据库中删除记录。跟 update() 一样,该方法会返回受影响的行数:
use Illuminate\Support\Facades\DB;
$deleted = DB::delete('delete from users');
执行通用语句
有些数据库语句没有任何返回值。对于这类操作,你可以用 DB 门面的 statement() 方法:
DB::statement('drop table users');
执行未预处理语句
有时你想执行一条不绑定任何参数的 SQL 语句,可以用 DB 门面的 unprepared() 方法:
DB::unprepared('update users set votes = 100 where name = "Dries"');
注意
未预处理语句不做参数绑定,因此可能存在 SQL 注入风险。永远不要在未预处理语句中拼接用户可控的值。
隐式提交
在事务中使用 DB 门面的 statement() 和 unprepared() 方法时,一定要小心避开会触发隐式提交的语句。这类语句会让数据库引擎间接提交整个事务,而 Laravel 无法感知数据库当前的事务层级。创建数据库表就是一个典型例子:
DB::unprepared('create table a (col varchar(1) null)');
会触发隐式提交的完整语句列表请参阅 MySQL 手册相关文档。
使用多个数据库连接
如果你在 config/database.php 配置文件中定义了多个连接,可以通过 DB 门面提供的 connection() 方法来访问每一个连接。传给 connection() 方法的连接名要与 config/database.php 中列出的某个连接对应,或者是通过 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
{
/**
* 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
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 门面提供的 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);
手动使用事务
如果你想手动开启事务,完全掌控回滚和提交,可以使用 DB 门面提供的 beginTransaction() 方法:
use Illuminate\Support\Facades\DB;
DB::beginTransaction();
可以通过 rollBack() 方法回滚事务:
DB::rollBack();
最后,可以通过 commit() 方法提交事务:
DB::commit();
信息
DB 门面的事务方法同时管控查询构造器 和 Eloquent ORM 的事务。
连接到数据库 CLI
如果你想连接到数据库的 CLI,可以使用 db Artisan 命令:
php artisan db
如有需要,你可以指定一个数据库连接名,连到非默认的连接上:
php artisan db mysql
检查数据库
借助 db:show 和 db: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;
/**
* 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
));
});
}