[ Laravel 从入门到精通 ] 数据库和 Eloquent 入门 —— 数据库连接配置和读写分离

今天开始讲如何在 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.phpconnections 配置项中新增一个 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_READDB_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 我们会在后面单独讲,这里不做深入讨论。

学院君 has written 1269 articles

Laravel学院院长,终身学习者

积分:177237 等级:P12 职业:手艺人 城市:杭州

19 条回复

  1. 学院君 学院君 says:
    @ gp3098

    你要描述下你的状况和具体问题 不然别人搞不清楚你的困惑是什么 可以搜索下「学会提问」去看看

  2. gp3098 gp3098 says:

    可以怎么连接

    不知道这样说好不好,不好以后就不说了

  3. 王晖 王晖 says:

    那读写分离的事物 怎么做?难道是手动指定 写库 为读?

登录后才能进行评论,立即登录?