数据库: 快速入门
简介
几乎所有的应用程序都需要和数据库进行交互。Laravel 为此提供了一套非常简单易用的数据库交互方式。开发者可以使用原生 SQL,查询构造器,以及 Eloquent ORM 等方式与数据库交互。目前,Laravel 为以下五种数据库提供了官方支持::
- MariaDB 10.3+ (版本策略)
- MySQL 5.7+ (版本策略)
- PostgreSQL 10.0+ (版本策略)
- SQLite 3.8.8+
- SQL Server 2017+ (版本策略)
配置
Laravel数据库服务的配置位于应用程序的config/database.php
配置文件中。在此文件中,您可以定义所有数据库连接,并指定默认情况下应使用的连接。此文件中的大多数配置选项由应用程序环境变量的值驱动。本文件提供了Laravel支持的大多数数据库系统的示例。
在默认情况下,Laravel 的示例 环境配置 使用了 Laravel Sail,Laravel 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
环境变量设置为 true
:
DB_FOREIGN_KEYS=true
Microsoft SQL Server 配置
在使用 SQL Server 数据库前,你需要先确保你已安装并启用了 sqlsrv
和 pdo_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
(或其对应的环境变量 DATABASE_URL
)存在,那么 Laravel 将会尝试从 URL 中提取数据库连接以及凭证信息。
读写分离
有时候你可能会希望使用一个数据库连接来执行 SELECT
语句,而 INSERT
、UPDATE
和 DELETE
语句则由另一个数据库连接来执行。在 Laravel 中,无论你是使用原生 SQL 查询、查询构造器 或是 Eloquent ORM
,都能轻松实现读写分离。
为了弄明白如何配置读写分离,我们先来看个例子:
'mysql' => [
'read' => [
'host' => [
'192.168.1.1',
'196.168.1.2',
],
],
'write' => [
'host' => [
'196.168.1.3',
],
],
'sticky' => true,
'driver' => 'mysql',
'database' => 'database',
'username' => 'root',
'password' => '',
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
],
请注意,我们在数据库配置中加入了三个键,分别是: read
, write
以及 sticky
。read
和 write
的值是一个只包含 host
键的数组。这代表其他的数据库选项将会从主 mysql
配置中获取。
如果你想要覆写主 mysql
配置,只需要将需要覆写的值放到 read
和 write
数组里即可。所以,在这个例子中,192.168.1.1
将会被用作「读」连接主机,而 192.168.1.3
将作为「写」连接主机。这两个连接将共享 mysql
数组中的各项配置,如数据库凭证(用户名、密码)、前缀、字符编码等。如果 host
数组中存在多个值,Laravel
将会为每个连接随机选取所使用的数据库主机。
sticky
选项
sticky
是一个 可选 值,它用于允许 Laravel 立即读取在当前请求周期内写入到数据库 的记录。若 sticky
选项被启用,且在当前请求周期中执行过「写」操作,那么在这之后的所有「读」操作都将使用「写」连接。这样可以确保同一个请求周期中写入的数据库可以被立即读取到,从而避免主从同步延迟导致的数据不一致。不过是否启用它取决于项目的实际需求。
执行原生SQL查询
一旦配置好数据库连接,你就可以使用 DB
Facade 来执行查询。DB
Facade 为每种类型的查询都提供了相应的方法:select
、update
、insert
、delete
以及 statement
。
执行 SELECT 查询
你可以使用 DB
Facade 的 select
方法来执行一个基础的 SELECT 查询:
<?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
方法将始终返回一个包含查询结果的数组。数组中的每个结果都对应一个数据库记录的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"
);
使用命名绑定
除了使用?
表示参数绑定外,你还可以使用命名绑定的形式来执行一个查询:
$results = DB::select('select * from users where id = :id', ['id' => 1]);
执行 Insert 语句
你可以使用DB
Facade 的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');
执行指定的 SQL
部分 SQL 语句不返回任何值。在这种情况下,你可能需要使用 DB::statement($sql)
来执行你的 SQL 语句。
DB::statement('drop table users');
直接执行 SQL
有时候你可能想执行一段 SQL 语句,但不需要进行 SQL 预处理绑定。这种情况下你可以使用 DB::unprepared($sql)
来执行你的 SQL 语句。
DB::unprepared('update users set votes = 100 where name = "Dries"');
未经过预处理 SQL 的语句不绑定参数,它们可能容易受到 SQL 注入的攻击。在没有必要的理由的情况下,你不应直接在 SQL 中使用用户传入的数据。
在事务中的隐式提交
在事务中使用 DB::statement($sql)
与 DB::unprepared($sql)
时,你必须要谨慎处理,避免 SQL 语句产生隐式提交。这些语句会导致数据库引擎间接地提交整个事务, 让 Laravel 丢失数据库当前的事务级别。下面是一个会产生隐式提交的示例 SQL:创建一个数据库表。
DB::unprepared('create table a (col varchar(1) null)');
请参考MySQL 官方手册以了解更多隐式提交的信息。
使用多数据库连接
如果你在配置文件 config/database.php
中定义了多个数据库连接的话,你可以通过 DB Facade 的 connection
方法来使用它们。传递给 connection
方法的连接名称应该是你在 config/database.php
里或者通过 config
助手函数在运行时配置的连接之一:
use Illuminate\Support\Facades\DB;
$users = DB::connection('sqlite')->select(/* ... */);
你也可以使用一个连接实例上的 getPdo
方法来获取底层的 PDO 实例:
$pdo = DB::connection()->getPdo();
监听查询事件
如果你想要获取程序执行的每一条 SQL 语句,可以使用 Db
facade 的 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;
// $query->bindings;
// $query->time;
});
}
}
监控累积查询时间
现代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) {
// 通知开发团队...
});
}
}
数据库事务
想要在数据库事务中运行一系列操作,你可以使用 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');
}, 5);
手动执行事务
如果你想要手动处理事务并完全控制回滚和提交,可以使用 DB
门面提供的 beginTransaction
方法:
use Illuminate\Support\Facades\DB;
DB::beginTransaction();
你可以通过 rollBack
方法回滚事务:
DB::rollBack();
最后,你可以通过 commit
方法提交事务:
DB::commit();
DB
门面的事务方法还可以用于控制 查询构造器 and Eloquent ORM.
连接到数据库 CLI
如果你想连接到数据库的 CLI,则可以使用 db
Artisan 命令:
php artisan db
如果需要,你可以指定数据库连接名称以连接到不是默认连接的数据库连接:
php artisan db mysql