入门篇(一):数据库连接配置和读写分离
今天开始讲如何在 Laravel 中操作数据库,Laravel 为我们提供了多种工具实现对数据库的增删改查,在我们使用 Laravel 提供的这些数据库工具之前,首先要连接到数据库。
数据库的连接配置文件位于 config/database.php
,和很多其他 Laravel 配置一样,你可以为数据库配置多个「连接」,然后决定将哪个「连接」作为默认连接。
基本配置
默认情况下,Laravel 为支持的每一种数据库定义了一个连接配置项:
'connections' => [
'sqlite' => [
'driver' => 'sqlite',
'database' => env('DB_DATABASE', database_path('database.sqlite')),
'prefix' => '',
],
'mysql' => [
'driver' => 'mysql',
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'strict' => true,
'engine' => null,
],
'pgsql' => [
'driver' => 'pgsql',
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '5432'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'charset' => 'utf8',
'prefix' => '',
'schema' => 'public',
'sslmode' => 'prefer',
],
'sqlsrv' => [
'driver' => 'sqlsrv',
'host' => env('DB_HOST', 'localhost'),
'port' => env('DB_PORT', '1433'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'charset' => 'utf8',
'prefix' => '',
],
],
包括 SQLite、MySQL、PostgresSQL、SQL Server,一般我们默认使用的都是 MySQL:
'default' => env('DB_CONNECTION', 'mysql'),
当然,默认数据库连接、数据库名称以及数据库用户名和密码等敏感信息都保存到 .env
文件中了,然后通过 env
辅助函数读取:
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=homestead
DB_USERNAME=homestead
DB_PASSWORD=secret
我们平时修改数据库连接信息的话修改这里就好了,默认配置值是针对 Homestead 开发环境配置的,如果你使用的是 Homestead 作为开发环境的话,开箱即用,不用做任何修改,如果不是的话则需要根据自己的环境做修改,比如学院君使用的是 Laradock,配置信息如下(数据库名称、用户名、密码以自己的环境为准,不要照搬):
DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=laravel57
DB_USERNAME=root
DB_PASSWORD=root
做好以上配置后,你就可以在 Laravel 项目中连接上 MySQL 数据库了。
配置多个数据库连接
有时候,我们的应用用到的不止一个数据库,或者做项目迁移的时候要做新老数据库之间的数据迁移,这个时候我们就可以配置多个数据库连接,如果我们的新老数据库使用的都是 MySQL 的话,可以在 config/database.php
的 connections
配置项中新增一个 MySQL 连接:
'mysql_old' => [
'driver' => 'mysql',
'host' => env('DB_HOST_OLD', '127.0.0.1'),
'port' => env('DB_PORT_OLD', '3306'),
'database' => env('DB_DATABASE_OLD', 'forge'),
'username' => env('DB_USERNAME_OLD', 'forge'),
'password' => env('DB_PASSWORD_OLD', ''),
'unix_socket' => env('DB_SOCKET_OLD', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'strict' => true,
'engine' => null,
],
然后在 .env
中新增对应配置项:
DB_CONNECTION_OLD=mysql
DB_HOST_OLD=mysql
DB_PORT_OLD=3306
DB_DATABASE_OLD=laravel56
DB_USERNAME_OLD=root
DB_PASSWORD_OLD=root
接下来,我们要怎样连上这个实例呢?默认情况下,我们在通过 Laravel 提供的数据库工具(DB 门面、查询构建器、Eloquent模型)连接数据库的时候,都没有显式指定连接,因为我们在配置文件中指定了默认的连接 mysql
。所以要连接上其它连接很简单,在查询的时候指定这个新的连接就好了,如果你使用的是 DB
门面执行原生 SQL 查询,可以这么连接老的数据库:
$users = DB::connection('mysql_old')->select(...);
DB::connection('mysql_old')->insert(...);
如果你使用的是查询构建器进行数据库操作,可以这么指定(和原生操作一样):
$users = DB::connection('mysql_old')->table('users')->where(...)->get();
DB::connection('mysql_old')->table('users')->insert(...);
如果你使用的 Eloquent 模型类,可以在对应模型类中设置 $connection
属性:
protected $connection = 'mysql_old';
这样,在模型类上执行查询、插入等操作时都会使用这个 mysql_old
数据库连接。
配置数据库读写分离连接
理论上来说,配置数据库读写分离连接也属于配置多个数据库连接的范畴,但是由于是一个比较特殊又很常见的使用场景,所以我们单独来讨论,Laravel 也对此进行了单独支持。
随着应用访问量的增长,对数据库进行读写分离可以有效的提升应用整体性能,关于数据库层面的读写分离配置不属于本教程讨论范畴,我们这里只讨论从应用层面如何在 Laravel 项目中配置读写分离连接。Laravel 框架数据库底层代码对数据库读写分离进行了支持,所以我们需要遵循底层实现进行读写分离配置:
'mysql' => [
'driver' => 'mysql',
'read' => [
'host' => env('DB_HOST_READ', '127.0.0.1'),
],
'write' => [
'host' => env('DB_HOST_WRITE', '127.0.0.1')
],
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'strict' => true,
'engine' => null,
],
read
项配置的是「读」连接,write
项配置的是「写」连接。然后在 .env
中新增 DB_HOST_READ
和 DB_HOST_WRITE
配置项。当然,对于 Web 应用而言,大多是读多写少,所以你还可以配置多个 read
主机,Laravel 底层的负载均衡机制是随机从配置的 IP 中挑一个连接:
'read' => [
'host' => [env('DB_HOST_READ_1'), env('DB_HOST_READ_2')],
],
有的人会问,如果我们读写数据库的用户名、密码不一样咋办?好办,上面这种配置默认读写连接使用的用户名密码一样,那我们在读写配置项中分别独立配置就好了:
'mysql' => [
'driver' => 'mysql',
'read' => [
'host' => env('DB_HOST_READ', '127.0.0.1'),
'username' => env('DB_USERNAME_READ', 'forge'),
'password' => env('DB_PASSWORD_READ', ''),
],
'write' => [
'host' => env('DB_HOST_WRITE', '127.0.0.1'),
'username' => env('DB_USERNAME_WRITE', 'forge'),
'password' => env('DB_PASSWORD_WRITE', ''),
],
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'forge'),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'strict' => true,
'engine' => null,
],
其他公共配置项也是同理,不再赘述。
针对读写分离数据库的连接,Laravel 数据库底层会自动判断,如果是查询语句会使用读连接,如果是数据库插入、更新、删除等操作会使用写连接。
读写分离本地模拟测试
我们可以在本地简单模拟测试下读写分离配置,我们使用同一个数据库主机,不同的数据库来进行读写分离,在数据库中创建一个新的数据库用作写数据库,并将其配置到 config/database.php
:
'mysql' => [
'driver' => 'mysql',
'read' => [
'host' => env('DB_HOST_READ', '127.0.0.1'),
'database' => env('DB_DATABASE', 'db_read'),
],
'write' => [
'host' => env('DB_HOST_WRITE', '127.0.0.1'),
'database' => env('DB_DATABASE_WRITE', 'db_write'),
],
'port' => env('DB_PORT', '3306'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'strict' => true,
'engine' => null,
],
然后我们在命令行运行 php artisan migrate
,就会将数据库迁移应用到写数据库,因为数据库更新也属于写操作,所以此时自动判读使用写连接。
然后我们通过 Tinker 插入一条记录(插入属于写操作,自动使用写连接):
然后你会在写数据库中看到这条记录,读数据库中没有,接下来,我们运行一条查询语句(查询属于读操作,自动使用读连接):
此时,由于我们并没有配置读写数据库之间的数据同步,所以只能查出来我们在上一篇教程中在读数据库中插入的记录。所以在 Laravel 中实现读写分离还是很方便的,我们只需要做好配置就好了,剩下的框架帮我们完成。
当然,和多个数据库连接类似,你也可以在使用时显式进行指定,以查询构建器为例:
DB::connection('read')->table('users')->where(...)->get();
DB::connection('write')->table('users')->insert(...);
如果通过 Eloquent 模型类调用的话,还可以这么指定:
User::on('read')->where(...)->get();
User::onWriteConnection()->save();
注:关于数据库的各种增删改查操作,后面会一一介绍。
读写分离配置中的 sticky
配置项
在读写分离配置中,我们注意到新增了一个 sticky
配置项,这个是用来干嘛的呢?
我们配置数据库读写分离的时候,会配置读数据库(从库)从写数据库(主库)同步数据,由于不同主机之间数据同步是需要时间的,虽然这个时间很短,但是对于并发量很大的应用,还是可能出现写入写数据库的数据不能立即从读数据库读取到的情况,sticky
配置项在这个时候就派上用场了。如果该配置项设置为 true
的话,在同一个请求生命周期中,写入的数据会被立刻读取到,底层原理其实就是读操作也从写数据库读取,因为写数据库始终是最新数据,从而避免主从同步延迟导致的数据不一致。
其它配置项
除了上面提到的数据库连接配置外,config/database.php
配置文件中还有一些其它配置项,你可以通过 migrations
配置项自定义数据库迁移表的名称,默认是 migrations
:
'migrations' => 'migrations',
还可以通过 redis
配置项配置 Redis 作为 NoSQL 数据库的连接配置:
'redis' => [
'client' => 'predis',
'default' => [
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD', null),
'port' => env('REDIS_PORT', 6379),
'database' => env('REDIS_DB', 0),
],
'cache' => [
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD', null),
'port' => env('REDIS_PORT', 6379),
'database' => env('REDIS_CACHE_DB', 1),
],
],
你可以看到 Redis 也支持多个连接,一个默认连接和一个用作缓存的 cache
连接。这一思想在 Laravel 配置中无处不在,很多服务都支持配置多个连接提供不同的驱动,比如 Session 支持文件、数据表等连接,缓存支持 Memcached、Redis 等连接,队列支持数据库、Beanstalkd 等连接。你可以为它们定义多个连接,然后指定一个默认连接,这样做的好处是,当某个连接出现问题,或者你想切换到其它实现,只需动动手指头修改下配置文件中的默认配置项就好了,极大的提高了系统的可维护性。
注:关于 Redis 我们会在后面单独讲,这里不做深入讨论。
20 Comments
evn和database.php的优先级哪个高?配置了其中一个另一个需要配置吗?或者说哪样子配置更好?
'read' => [ 'host' => env('DB_HOST_READ', '127.0.0.1'), 'username' => env('DB_USERNAME_READ', 'forge'), 'password' => env('DB_PASSWORD_READ', ''), ], 在分别配置不同连接的用户名密码的时候,多个host怎么写? 'read' => [ [ 'host' => env('DB_HOST_READ', '127.0.0.1'), 'username' => env('DB_USERNAME_READ', 'forge'), 'password' => env('DB_PASSWORD_READ', ''),],[ 'host' => env('DB_HOST_READ', '127.0.0.1'), 'username' => env('DB_USERNAME_READ', 'forge'), 'password' => env('DB_PASSWORD_READ', ''),], ],
我觉得我这就是瞎编
这段是单独配置一个host吧? 那配置多个的时候呢? 我感觉在不会的地方纠结太久了,这个哪怕跳过先应该没问题吧
配置 .env 即可 database 从 .env 读配置
.env
不放倒代码仓库 不同环境做不同配置 灵活性更高管理和维护起来多麻烦 累不累 一百台机器难道搞一百个用户名/密码 一般都是配置成一样的 不是说瞎写 你要看下 Laravel 底层支不支持这么写 看下源码就知道了 再不济自己测试下就知道行不行了 这种没必要贴出来问 明明自己就可以得到答案
配置多个 host 下面有 自己往下看
学院君你好,今天看了这篇文章。尝试了读写分离,database.php 配置如下
然后根据文章说明 显式进行指定 写库 $res = DB::connection("write")->table('oauth_clients')->get(); 该种写法 laravel 会报以下错误
InvalidArgumentException Database [write] not configured.
还请指点下 谢谢
你搞混了 读写连接 Laravel 底层会自动判断 显式指定的话是与最外层的
mysql
同级别的连接配置,write 在 mysql 里面肯定找不到好的, 感谢回答,那追问一下 如果我需要指定读取的时候使用主库(排除sticky),目前我想到了2中方法强转
请问 还有没有其它的方式 也可以实现呢?
记录一下,如果写在mysql里面了,执行失败,则需要重启一下tinker,否则一直执行不成功。