免费视频淫片aa毛片_日韩高清在线亚洲专区vr_日韩大片免费观看视频播放_亚洲欧美国产精品完整版

打開APP
userphoto
未登錄

開通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開通VIP
【Laravel系列4.5】主從庫(kù)配置和語法生成

主從庫(kù)配置和語法生成

對(duì)于我們線上的運(yùn)行環(huán)境來說,經(jīng)常會(huì)有的一種情況就是需要主從分離。關(guān)于主從分離有什么好處,怎么配之類的內(nèi)容不是我們學(xué)習(xí)框架的重點(diǎn)。但是你要知道的是,Laravel 以及現(xiàn)代化的所有框架都是可以方便地配置主從分離的。另外,我們還要再回去 查詢構(gòu)造器 中,看一下我們的原生 SQL 語句的拼裝語法到底是如何生成的。

主從數(shù)據(jù)庫(kù)連接

其實(shí)配置非常簡(jiǎn)單,我們先來簡(jiǎn)單的看一下。之后,我們?cè)偕钊朐创a,看看它是怎么做到寫入走主庫(kù),讀取走從庫(kù)的。

'mysql2' => [
    'driver' => 'mysql',
    'read' => [
        'host'=>[
            '192.168.56.101'
        ]
    ],
    'write' => [
        'host'=>[
            env('DB_HOST''127.0.0.1'),
        ]
    ],
    'url' => env('DATABASE_URL'),
//            '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'''),
    'sticky' => true,
    'charset' => 'utf8mb4',
    'collation' => 'utf8mb4_unicode_ci',
    'prefix' => '',
    'prefix_indexes' => true,
    'strict' => true,
    'engine' => null,
    'options' => extension_loaded('pdo_mysql') ? array_filter([
        PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
    ]) : [],
],

我們這里是修改的 config/database.php 文件??梢钥吹?,和原始配置不同的是我們注釋掉了原來的 hosts ,然后增加了 read 和 write ,在這兩個(gè)屬性里面可以以數(shù)組的形式指定 hosts 。這樣,我們的查詢語句和增刪改語句就實(shí)現(xiàn)了分離,查詢語句會(huì)走 read 的配置,而其它語句則會(huì)走 write 的配置。同時(shí),我們還多增加了一個(gè) sticky 并設(shè)置為 true 。它的作用是,在同一次的請(qǐng)求中,如果執(zhí)行了增刪改的操作,那么緊接著的查詢也會(huì)走 write 也就是主庫(kù)的查詢。這也是因?yàn)槲覀冊(cè)谀承I(yè)務(wù)中,需要在操作完數(shù)據(jù)后馬上查詢,主從之間的延遲可能會(huì)導(dǎo)致查詢的從庫(kù)數(shù)據(jù)不正確(這在現(xiàn)實(shí)業(yè)務(wù)中很常見)。因此,在一次增刪改操作后如果緊接著有查詢的話,我們當(dāng)前的這個(gè)請(qǐng)求流程還是會(huì)繼續(xù)查詢主庫(kù)。

接下來,我們定義兩個(gè)路由來測(cè)試。

Route::get('ms/test/insert'function(){
    \Illuminate\Support\Facades\DB::connection('mysql2')->table('db_test')->insert(['name'=>'Lily''sex'=>2]);
    dd( \Illuminate\Support\Facades\DB::connection('mysql2')->table('db_test')->get()->toArray());
});

Route::get('ms/test/list'function(){
    dd( \Illuminate\Support\Facades\DB::connection('mysql2')->table('db_test')->get()->toArray());
});

在執(zhí)行第一個(gè)路由之后,dd() 打印的數(shù)據(jù)中我們會(huì)看到新添加成功的數(shù)據(jù)。接著去請(qǐng)求第二個(gè)路由,會(huì)發(fā)現(xiàn)數(shù)據(jù)還是原來的,并沒有增加新的數(shù)據(jù)。因?yàn)槲覀儾]有在 MySQL 配置主從同步,這也是為了方便我們的調(diào)試查看。很明顯,第二個(gè)路由的查詢語句走的就是另一個(gè)數(shù)據(jù)庫(kù)了。

對(duì)于如何實(shí)現(xiàn)的讀寫分離,我們從 原生查詢 的 select() 方法來看。找到 laravel/framework/src/Illuminate/Database/Connection.php 中的 select() 方法,可以看到它還有第三個(gè)參數(shù)。

public function select($query, $bindings = [], $useReadPdo = true)
{
    return $this->run($query, $bindings, function ($query, $bindings) use ($useReadPdo) {
        if ($this->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.
        $statement = $this->prepared(
            $this->getPdoForSelect($useReadPdo)->prepare($query)
        );

        $this->bindValues($statement, $this->prepareBindings($bindings));

        $statement->execute();

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

protected function getPdoForSelect($useReadPdo = true)
{
    return $useReadPdo ? $this->getReadPdo() : $this->getPdo();
}

$useReadPdo 這個(gè)參數(shù)默認(rèn)就是一個(gè) true 值,方法體內(nèi)部,getPdoForSelect() 方法使用了這個(gè)參數(shù)。我們繼續(xù)向下看。

public function getReadPdo()
{
    if ($this->transactions > 0) {
        return $this->getPdo();
    }

    if ($this->recordsModified && $this->getConfig('sticky')) {
        return $this->getPdo();
    }

    if ($this->readPdo instanceof Closure) {
        return $this->readPdo = call_user_func($this->readPdo);
    }

    return $this->readPdo ?: $this->getPdo();
}

// $this->readPdo laravel/framework/src/Illuminate/Database/Connectors/ConnectionFactory.php createPdoResolverWithHosts

這個(gè)方法中,其實(shí)沒有做別的,最核心的就是使用 call_user_func() 去調(diào)用這個(gè) $this->readPdo 方法,從這里可以看出這個(gè) $this->readPdo 應(yīng)該是一個(gè)回調(diào)函數(shù)。打印出來可以看到,它返回的是 laravel/framework/src/Illuminate/Database/Connectors/ConnectionFactory.php 的 createPdoResolverWithHosts() 方法所生成的一個(gè)回調(diào)函數(shù)。而其它的代碼都是在判斷在什么情況下直接去使用主庫(kù)的 PDO 連接。那么 $this->readPdo 是在什么時(shí)候定義的呢?在當(dāng)前這個(gè)文件中,我們找不到答案,Connection.php 中只有一個(gè) setReadPdo() 方法,但沒有調(diào)用設(shè)置它的代碼。

public function setReadPdo($pdo)
{
    $this->readPdo = $pdo;

    return $this;
}

那么我們就向上追溯,直接去 laravel/framework/src/Illuminate/Database/Connectors/ConnectionFactory.php 連接工廠類看看,發(fā)現(xiàn) createReadWriteConnection() 這個(gè)方法中調(diào)用了 setReadPdo() 方法。

public function make(array $config, $name = null)
{
    $config = $this->parseConfig($config, $name);

    if (isset($config['read'])) {
        return $this->createReadWriteConnection($config);
    }

    return $this->createSingleConnection($config);
}

protected function createReadWriteConnection(array $config)
{
    $connection = $this->createSingleConnection($this->getWriteConfig($config));

    return $connection->setReadPdo($this->createReadPdo($config));
}

protected function createReadWriteConnection(array $config)
{
    $connection = $this->createSingleConnection($this->getWriteConfig($config));

    return $connection->setReadPdo($this->createReadPdo($config));
}

protected function createReadPdo(array $config)
{
    return $this->createPdoResolver($this->getReadConfig($config));
}

protected function createPdoResolver(array $config)
{
    return array_key_exists('host', $config)
                        ? $this->createPdoResolverWithHosts($config)
                        : $this->createPdoResolverWithoutHosts($config);
}

protected function getReadConfig(array $config)
{
    return $this->mergeReadWriteConfig(
        $config, $this->getReadWriteConfig($config, 'read')
    );
}

protected function getReadWriteConfig(array $config, $type)
{
    return isset($config[$type][0])
                    ? Arr::random($config[$type])
                    : $config[$type];
}

很明顯,在創(chuàng)建連接時(shí),make() 方法體內(nèi)根據(jù)配置文件是否有 read 配置,來調(diào)用這個(gè) createReadWriteConnection() 方法。然后順著我貼出的代碼,可以一路看到就是如果有read 配置,那么就會(huì)先使用 write 配置創(chuàng)建一個(gè)主連接,接著調(diào)用這個(gè)主連接的 setReadPdo() 方法并根據(jù) read 配置又創(chuàng)建了一個(gè)從數(shù)據(jù)庫(kù)連接。主對(duì)象是我們的 write 連接對(duì)象,而 read 連接對(duì)象是它的一個(gè)子對(duì)象。

在 createPdoResolver() 方法中,我們看到了上面發(fā)現(xiàn)的那個(gè)生成回調(diào)函數(shù)的 createPdoResolverWithHosts() 方法的使用。這一下大家應(yīng)該就真相大白了吧。如果還沒弄清楚的同學(xué),可以自己設(shè)置一下斷點(diǎn)調(diào)試調(diào)試,畢竟代碼位置和文件都給出了。

從這里我們可以看出,Laravel 是根據(jù)參數(shù)來判斷是否使用從庫(kù)連接進(jìn)行查詢的,而我之前看過其它框架的源碼,是 Yii 還是 TP 什么來著,有根據(jù)查詢語句是否有 SELECT 字符來判斷走從庫(kù)去查詢的,也很有意思,大家可以自己去研究下哈。

語法生成

講完連接了我們?cè)倩貋碇v講數(shù)據(jù)庫(kù)連接中非常重要的一個(gè)東西,那就是 SQL 語句是怎么生成的。這里使用的是 語法 這個(gè)高大上的詞匯,實(shí)際上簡(jiǎn)單的理解就是 查詢構(gòu)造器 是如何生成 SQL 語句的。原生查詢 就不用多說了,都是我們自己寫 SQL 語句讓 PDO 執(zhí)行就好了。但是 查詢構(gòu)造器 以及上層的 Eloquent ORM 都是之前講過的面向?qū)ο笫降逆準(zhǔn)缴蓪?duì)象之后完成數(shù)據(jù)庫(kù)查詢的,這其中,肯定有 SQL 語句的生成過程,這就是我們接下來要學(xué)習(xí)的內(nèi)容。

其實(shí)我們?cè)?查詢構(gòu)造器 那篇文章中就已經(jīng)看到過 Laravel 是如何生成 SQL 語句了,還記得我們分析的那個(gè) update() 方法嗎?如果不記得的小伙伴可以回去看一下 【Laravel系列4.2】查詢構(gòu)造器https://mp.weixin.qq.com/s/vUImsLTpEtELgdCTWI6k2A 。在執(zhí)行 update() 操作時(shí),我們最后進(jìn)入了 laravel/framework/src/Illuminate/Database/Query/Grammars/Grammar.php 這個(gè)對(duì)象中。從名稱就可以看出,這是一個(gè) 語法 對(duì)象。在這個(gè)對(duì)象中會(huì)負(fù)責(zé)拼接真正的 SQL 語句。比如我再來看一下 insert() 最終到達(dá)的 compileInsert() 方法。

public function compileInsert(Builder $query, array $values)
{
    // Essentially we will force every insert to be treated as a batch insert which
    // simply makes creating the SQL easier for us since we can utilize the same
    // basic routine regardless of an amount of records given to us to insert.
    $table = $this->wrapTable($query->from);

    if (empty($values)) {
        return "insert into {$table} default values";
    }

    if (! is_array(reset($values))) {
        $values = [$values];
    }

    $columns = $this->columnize(array_keys(reset($values)));

    // We need to build a list of parameter place-holders of values that are bound
    // to the query. Each insert should have the exact same amount of parameter
    // bindings so we will loop through the record and parameterize them all.
    $parameters = collect($values)->map(function ($record) {
        return '('.$this->parameterize($record).')';
    })->implode(', ');

    return "insert into $table ($columns) values $parameters";
}

最終返回的這個(gè) SQL 語句,會(huì)交給連接,也就是 laravel/framework/src/Illuminate/Database/Connection.php 中的 insert() 方法來執(zhí)行。這個(gè)就是我們最早學(xué)習(xí)使用過的那個(gè)原生查詢所調(diào)用的方法。接下來,我們?cè)倏匆幌?get() 方法,也就是獲得查詢結(jié)果集的方法。在 Builder 中,get() 方法會(huì)調(diào)用一個(gè) runSelect() 方法,這個(gè)方法里面會(huì)再調(diào)用一個(gè) toSql() 方法,就是獲得原始查詢語句的方法。

public function toSql()
{
    return $this->grammar->compileSelect($this);
}

可以看到,toSql() 又到語法對(duì)象中調(diào)用了 compileSelect() 方法。

public function compileSelect(Builder $query)
{
    if ($query->unions && $query->aggregate) {
        return $this->compileUnionAggregate($query);
    }

    // If the query does not have any columns set, we'll set the columns to the
    // * character to just get all of the columns from the database. Then we
    // can build the query and concatenate all the pieces together as one.
    $original = $query->columns;

    if (is_null($query->columns)) {
        $query->columns = ['*'];
    }

    // To compile the query, we'll spin through each component of the query and
    // see if that component exists. If it does we'll just call the compiler
    // function for the component which is responsible for making the SQL.
    $sql = trim($this->concatenate(
        $this->compileComponents($query))
    );

    if ($query->unions) {
        $sql = $this->wrapUnion($sql).' '.$this->compileUnions($query);
    }

    $query->columns = $original;

    return $sql;
}

其中,基礎(chǔ)的 SELECT 語句的拼接是在 compileComponents() 中完成的,我們繼續(xù)進(jìn)入這個(gè)方法。

protected function compileComponents(Builder $query)
{
    $sql = [];

    foreach ($this->selectComponents as $component) {
        if (isset($query->$component)) {
            $method = 'compile'.ucfirst($component);

            $sql[$component] = $this->$method($query, $query->$component);
        }
    }

    return $sql;
}

貌似有點(diǎn)看不明白呀?這一個(gè)循環(huán)是在干嘛?其實(shí),從代碼中我們可以看,它在遍歷一個(gè)本地屬性 selectComponents ,并根據(jù)這個(gè)屬性里面的內(nèi)容去調(diào)用自身的這些方法。我們查看 selectComponents 屬性會(huì)發(fā)現(xiàn)它就是一系列方法名的預(yù)備信息。

protected $selectComponents = [
    'aggregate',
    'columns',
    'from',
    'joins',
    'wheres',
    'groups',
    'havings',
    'orders',
    'limit',
    'offset',
    'lock',
];

在循環(huán)中拼接的結(jié)果就是 compileAggregate() 、compileColumns() .... 這一系列方法,這堆方法在當(dāng)前的這個(gè)語法文件中我們都可以找到。每個(gè)方法需要的額外參數(shù)是通過 $query->$component 傳遞進(jìn)去的,這里我們也可以再回到 Builder 類中查看,在這個(gè)類中,有與 selectComponents 相對(duì)應(yīng)的各個(gè)屬性。在我們定義 查詢構(gòu)造器 的時(shí)候,這些對(duì)應(yīng)的屬性都會(huì)建立并賦值。這些 compile 方法執(zhí)行完成之后,再通過 concatenate() 方法將 compileComponents() 中獲得的那個(gè) $sql 數(shù)組轉(zhuǎn)換成一個(gè)字符串,查詢的 SQL 語句就拼接完成了。

protected function concatenate($segments)
{
    return implode(' ', array_filter($segments, function ($value) {
        return (string) $value !== '';
    }));
}

你想要知道的 Where 條件、Join 語句是怎么拼接的,就全在這些 compileWheres()、compileJoins() 方法中了。這里我就不貼代碼了,剩下的東西就看大家自己怎么發(fā)掘咯!

總結(jié)

今天的內(nèi)容其實(shí)相對(duì)來說輕松一些,畢竟關(guān)于 Laravel 數(shù)據(jù)庫(kù)方面的內(nèi)容重點(diǎn)在于之前學(xué)習(xí)過的 模型 和 查詢構(gòu)造器 上。對(duì)于主從數(shù)據(jù)庫(kù)來說,一般中大型的業(yè)務(wù)項(xiàng)目會(huì)應(yīng)用得比較廣泛,它實(shí)現(xiàn)的原理其實(shí)也并不復(fù)雜。而 語法生成 這里我們主要是看了一下查詢語句的語法生成,相比增刪改來說,查詢語句因?yàn)榇嬖?where/join/order by/group by 等功能,所以會(huì)更加的復(fù)雜一些。當(dāng)然,更復(fù)雜的東西其實(shí)還是在構(gòu)造器中,畢竟在語法生成這里其實(shí)是已經(jīng)到了最后的拼裝階段了。有興趣的同學(xué)可以多深入研究一下 Builder 對(duì)象中關(guān)于上述功能的方法實(shí)現(xiàn)。相信經(jīng)過這一系列的學(xué)習(xí),這個(gè)文件的內(nèi)容對(duì)你已經(jīng)不陌生了,也相信你已經(jīng)可以自己獨(dú)立的分析剩下的內(nèi)容了。后面我們還要再學(xué)習(xí)兩篇簡(jiǎn)單的和數(shù)據(jù)庫(kù)相關(guān)的內(nèi)容,分別是事務(wù)與PDO屬性設(shè)置,以及 Redis 的簡(jiǎn)單使用。

參考文檔:

https://learnku.com/docs/laravel/8.x/database/9400#e05dce

本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
Node.js中JavaScript操作MySQL的常用方法整理
laravel查看執(zhí)行的sql語句
完整的PHP MYSQL數(shù)據(jù)庫(kù)類
【log】laravel中的錯(cuò)誤與日志
SqlServer數(shù)據(jù)庫(kù)語句大全(一)
MySQL配置主主及主從備份
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長(zhǎng)圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服