Laravel 5 配置數據庫主從讀寫分離和源碼分析

原文地址:Laravel5配置讀寫分離和源碼分析


一,配置過程:

Laravel5讀寫分離配置比較簡單,只需修改config/database.php,下面以MySQL數據庫爲例 內容如下 'mysql' => [

'read' => [
    'host' => '192.168.1.1'
],
'write' => [
    'host' => '196.168.1.2'
],
'driver'    => 'mysql',
'database'  => 'database',
'username'  => 'root',
'password'  => '',
'charset'   => 'utf8',
'collation' => 'utf8_unicode_ci',
'prefix'    => '',

]

設置完畢之後,Laravel5默認將select的語句讓read指定的數據庫執行,insert/update/delete則交給write指定的數據庫,達到讀寫分離的作用。 這些設置對原始查詢raw queries,查詢生成器query builder,以及對象映射 Eloquent 都生效。 官網解釋如下: Sometimes you may wish to use one database connection for SELECT statements, and another for INSERT, UPDATE, and DELETE statements. Laravel makes this a breeze, and the proper connections will always be used whether you are using raw queries, the query builder, or the Eloquent ORM

二,實現原理

Laravel5讀寫分離主要有兩個過程: 第一步,根據database.php配置,創建寫庫和讀庫的鏈接connection 第二步,調用select時先判斷使用讀庫還是寫庫,而insert/update/delete統一使用寫庫

三,源碼分析:根據database.php配置,創建寫庫和讀庫的鏈接connection

主要文件:Illuminate/Database/Connectors/ConnectionFactory.php 來看看幾個重要的函數:

1,判斷database.php是否配置了讀寫分離數據庫

/**
 * Establish a PDO connection based on the configuration.
 *
 * @param  array   $config
 * @param  string  $name
 * @return \Illuminate\Database\Connection
 */
public function make(array $config, $name = null)
{
    $config = $this->parseConfig($config, $name);
    // 如果配置了讀寫分離,則同時創建讀庫和寫庫的鏈接
    if (isset($config['read'])) {
        return $this->createReadWriteConnection($config);
    }
    // 如果沒有配置,默認創建單個數據庫鏈接
    return $this->createSingleConnection($config);
}

2,看看如何創建讀庫和寫庫的鏈接

/**
 * Create a single database connection instance.
 *
 * @param  array  $config
 * @return \Illuminate\Database\Connection
 */
protected function createReadWriteConnection(array $config)
{
    // 獲取寫庫的配置信息,並創建鏈接
    $connection = $this->createSingleConnection($this->getWriteConfig($config));
    // 創建讀庫的鏈接
    return $connection->setReadPdo($this->createReadPdo($config));
}

3,多個讀庫會選擇哪個呢

/**
 * Get the read configuration for a read / write connection.
 *
 * @param  array  $config
 * @return array
 */
protected function getReadConfig(array $config)
{
    $readConfig = $this->getReadWriteConfig($config, 'read');

    // 如果數組即多個讀庫,那麼通過隨機函數array_rand()挑一個,默認取第一個
    if (isset($readConfig['host']) && is_array($readConfig['host'])) {
        $readConfig['host'] = count($readConfig['host']) > 1
            ? $readConfig['host'][array_rand($readConfig['host'])]
            : $readConfig['host'][0];
    }
    return $this->mergeReadWriteConfig($config, $readConfig);
}

4,寫庫也是隨機選擇的

/**
 * Get a read / write level configuration.
 *
 * @param  array   $config
 * @param  string  $type
 * @return array
 */
protected function getReadWriteConfig(array $config, $type)
{

    // 如果多個,那麼通過隨機函數array_rand()挑一個
    if (isset($config[$type][0])) {
        return $config[$type][array_rand($config[$type])];
    }
    return $config[$type];
}

總結:

1,可以設置多個讀庫和多個寫庫,或者不同組合,比如一個寫庫兩個讀庫

2,每次只創建一個讀庫鏈接和一個寫庫鏈接,從多個庫中隨機選擇一個;

四,源碼分析:調用select時先判斷使用讀庫還是寫庫,而insert/update/delete統一使用寫庫

主要文件:Illuminate/Database/Connection.php 看看幾個重要的函數

1,select函數根據第三個輸入參數判斷使用讀庫還是寫庫

 /**
 * Run a select statement against the database.
 *
 * @param  string  $query
 * @param  array  $bindings
 * @param  bool  $useReadPdo
 * @return array
 */
public function select($query, $bindings = [], $useReadPdo = true)
{
    return $this->run($query, $bindings, function ($me, $query, $bindings) use ($useReadPdo) {
        if ($me->pretending()) {
            return [];
        }

        // For select statements, we'll simply execute the query and return an array
        // of the database result set. Each element in the array will be a single
        // row from the database table, and will either be an array or objects.
        // 根據$useReadPdo參數,判斷使用讀庫還是寫庫;
        // true使用讀庫,false使用寫庫;默認使用讀庫
        $statement = $this->getPdoForSelect($useReadPdo)->prepare($query);

        $statement->execute($me->prepareBindings($bindings));

        $fetchArgument = $me->getFetchArgument();

        return isset($fetchArgument) ?
            $statement->fetchAll($me->getFetchMode(), $fetchArgument, $me->getFetchConstructorArgument()) :
            $statement->fetchAll($me->getFetchMode());
    });
}

/**
 * Get the PDO connection to use for a select query.
 *
 * @param  bool  $useReadPdo
 * @return \PDO
 */
protected function getPdoForSelect($useReadPdo = true)
{
    // 根據$useReadPdo參數,選擇PDO即判斷使用讀庫還是寫庫;
    // true使用讀庫getReadPdo,false使用寫庫getPdo; 
   return $useReadPdo ? $this->getReadPdo() : $this->getPdo();
}

2, insert/update/delete統一使用寫庫

/**
 * Run an insert statement against the database.
 *
 * @param  string  $query
 * @param  array   $bindings
 * @return bool
 */
public function insert($query, $bindings = [])
{
    return $this->statement($query, $bindings);
}

/**
 * Run an update statement against the database.
 *
 * @param  string  $query
 * @param  array   $bindings
 * @return int
 */
public function update($query, $bindings = [])
{
    return $this->affectingStatement($query, $bindings);
}

/**
 * Run a delete statement against the database.
 *
 * @param  string  $query
 * @param  array   $bindings
 * @return int
 */
public function delete($query, $bindings = [])
{
    return $this->affectingStatement($query, $bindings);
}

/**
 * Execute an SQL statement and return the boolean result.
 *
 * @param  string  $query
 * @param  array   $bindings
 * @return bool
 */
public function statement($query, $bindings = [])
{
    return $this->run($query, $bindings, function ($me, $query, $bindings) {
        if ($me->pretending()) {
            return true;
        }

        $bindings = $me->prepareBindings($bindings);
        // 直接調用寫庫
        return $me->getPdo()->prepare($query)->execute($bindings);
    });
}

/**
 * Run an SQL statement and get the number of rows affected.
 *
 * @param  string  $query
 * @param  array   $bindings
 * @return int
 */
public function affectingStatement($query, $bindings = [])
{
    return $this->run($query, $bindings, function ($me, $query, $bindings) {
        if ($me->pretending()) {
            return 0;
        }

        // For update or delete statements, we want to get the number of rows affected
        // by the statement and return that back to the developer. We'll first need
        // to execute the statement and then we'll use PDO to fetch the affected.
        // 直接調用寫庫
        $statement = $me->getPdo()->prepare($query);

        $statement->execute($me->prepareBindings($bindings));

        return $statement->rowCount();
    });
}

總結:

1,getReadPdo()獲得讀庫鏈接,getPdo()獲得寫庫鏈接;

2,select()函數根據第三個參數判斷使用讀庫還是寫庫;

五,強制使用寫庫

有時候,我們需要讀寫實時一致,寫完數據庫後,想馬上讀出來,那麼讀寫都指定一個數據庫即可。 雖然Laravel5配置了讀寫分離,但也提供了另外的方法強制讀寫使用同一個數據庫。

實現原理:上面$this->select()時指定使用寫庫的鏈接,即第三個參數useReadPdo設置爲false即可

有幾個方法可實現 1,調用方法 DB::table('posts')->selectFromWriteConnection('*')->where('id', $id);

源碼解釋:通過selectFromWriteConnection()函數 主要文件:Illuminate/Database/Connection.php

/**
 * Run a select statement against the database.
 *
 * @param  string  $query
 * @param  array   $bindings
 * @return array
 */
public function selectFromWriteConnection($query, $bindings = [])
{
    ,
    // 上面有解釋$this->select()函數的第三個參數useReadPdod的意義
    // 第三個參數是 false,所以 select 時會使用寫庫,而不是讀庫
    return $this->select($query, $bindings, false);
}

2,調用方法

User::onWriteConnection()->find($id);

源碼解釋:通過onWriteConnection()函數 主要文件:Illuminate/Database/Eloquent/Model

/**
 * Begin querying the model on the write connection.
 *
 * @return \Illuminate\Database\Query\Builder
 */
public static function onWriteConnection()
{
    $instance = new static;
    // query builder 指定使用寫庫
    return $instance->newQuery()->useWritePdo();
}

看看query builder如何指定使用寫庫 主要文件:Illuminate/Database/Query/Builder

/**
 * Use the write pdo for query.
 *
 * @return $this
 */
public function useWritePdo()
{
    // 指定使用寫庫,useWritePdo 爲true
    $this->useWritePdo = true;

    return $this;
}

/**
 * Run the query as a "select" statement against the connection.
 *
 * @return array
 */
protected function runSelect()
{
    // 執行select時,useWritePdo原值爲true,這裏取反,被改成false;
    // 即$this->select()函數第三個參數爲false,所以使用寫庫;
    return $this->connection->select($this->toSql(), $this->getBindings(), ! $this->useWritePdo);
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章