Thinkphp 第一章:數(shù)據(jù)庫架構(gòu)基礎(chǔ)

本章我們首先從ThinkPHP5.0的數(shù)據(jù)庫訪問層架構(gòu)設(shè)計原理開始,然后熟悉下數(shù)據(jù)庫的配置慕购,并掌握如何進行基礎(chǔ)的查詢操作蜡吧,并簡單介紹了分布式、存儲過程及事務(wù)征绎,學(xué)習(xí)內(nèi)容主要包括:

數(shù)據(jù)庫架構(gòu)設(shè)計

使用框架開發(fā)應(yīng)用蹲姐,一般不需要直接操作數(shù)據(jù)庫,而是通過框架封裝好的數(shù)據(jù)庫中間層對數(shù)據(jù)庫進行操作人柿。這樣的好處主要有兩個:一是簡化數(shù)據(jù)庫操作柴墩,二是做到跨數(shù)據(jù)庫的一致性。這種設(shè)計的中間層通常稱之為數(shù)據(jù)庫訪問抽象層凫岖,簡稱數(shù)據(jù)訪問層(DAL)江咳,ThinkPHP5的數(shù)據(jù)訪問層是基于PHP內(nèi)置的PDO對象實現(xiàn)。一般抽象層本身并不直接操作數(shù)據(jù)庫隘截,而是通過驅(qū)動來實現(xiàn)具體的數(shù)據(jù)庫操作扎阶。

ThinkPHP5.0的數(shù)據(jù)庫設(shè)計相比之前版本更加合理汹胃,數(shù)據(jù)訪問層劃分的更細化婶芭,把數(shù)據(jù)訪問對象分成了連接器、查詢器着饥、生成器等多個對象犀农,并通過數(shù)據(jù)庫訪問入口類統(tǒng)一調(diào)用,分工更明確宰掉,各司其職呵哨,欲知詳情且聽我慢慢道來。

ThinkPHP數(shù)據(jù)訪問層設(shè)計示意圖:


5.1版本的架構(gòu)略微進行了一些調(diào)整轨奄,變成:

數(shù)據(jù)庫入口類Db

平常我們的數(shù)據(jù)庫操作使用的類庫一般都是數(shù)據(jù)庫的入口類think\Db孟害。這個類非常的簡單,主要就是一個connect方法挪拟,根據(jù)數(shù)據(jù)庫配置參數(shù)連接數(shù)據(jù)庫(注意這里的連接并非真正的連接數(shù)據(jù)庫挨务,只是做好了隨時連接的準(zhǔn)備工作,只有在實際查詢的時候才會真正去連接數(shù)據(jù)庫玉组,是一種惰性連接)并獲取到數(shù)據(jù)庫連接對象的實例谎柄。

Db類都是靜態(tài)方法調(diào)用,但看起來這個類啥都沒實現(xiàn)惯雳,那是怎么操作數(shù)據(jù)庫的呢朝巫,其實就是封裝了數(shù)據(jù)庫操作方法的靜態(tài)調(diào)用(利用__callStatic方法),下面是代碼實現(xiàn):

// 調(diào)用驅(qū)動類的方法
public static function __callStatic($method, $params)
{
    // 自動初始化數(shù)據(jù)庫
    return call_user_func_array([self::connect(), $method], $params);
}

理論上來說石景,框架并不依賴Db類劈猿,該類的存在只是為了簡化數(shù)據(jù)庫抽象層的操作而提供的一個工廠類拙吉,否則你就需要單獨實例化不同的數(shù)據(jù)庫連接類。因此糙臼,看似可有可無的Db類就成了數(shù)據(jù)訪問層實現(xiàn)的點睛之筆了庐镐。

所有的數(shù)據(jù)庫操作都是經(jīng)過Db類調(diào)用,并且Db類是一個靜態(tài)類变逃,但Db類自身只有一個公共方法connect必逆。

連接器類Connection

顧名思義,連接類的作用就是連接數(shù)據(jù)庫揽乱,也稱為連接器名眉。我們知道,不同的數(shù)據(jù)庫的連接方式和參數(shù)都是不同的凰棉,連接類就是要解決這個差異問題损拢。

數(shù)據(jù)庫入口類里面實例化的類其實就是對應(yīng)數(shù)據(jù)庫的連接類,連接類的基類是think\db\Connection撒犀。例如福压,需要連接Mysql數(shù)據(jù)庫的話,就必須定義一個Mysql連接類(內(nèi)置由think\db\connector\Mysql類實現(xiàn)或舞,繼承了think\db\Connection類)荆姆,當(dāng)然具體的連接類名沒有固定的規(guī)范(例如,MongoDb的連接類就是think\mongo\Connection)映凳。如果某個數(shù)據(jù)庫的連接擴展類沒有繼承think\db\Connection胆筒,那就意味著所有的數(shù)據(jù)庫底層操作有可能被接管,在個別特殊的數(shù)據(jù)庫的擴展中就有類似的實現(xiàn)诈豌,例如MongoDb數(shù)據(jù)庫擴展仆救。

數(shù)據(jù)庫連接都是惰性的,只有最終執(zhí)行SQL的時候才會進行連接矫渔。

連接器是數(shù)據(jù)訪問層的基礎(chǔ)彤蔽,基于PHP本身的PDO實現(xiàn)(如果你還不了解PDO,請參考PHP官方手冊中PDO部分庙洼,不在本書的討論范疇)顿痪,連接類的主要作用就是連接具體的數(shù)據(jù)庫,以及完成基本的數(shù)據(jù)庫底層操作送膳,包括對分布式员魏、存儲過程和事務(wù)的完善處理。而更多的數(shù)據(jù)操作則交由查詢類完成叠聋。

框架內(nèi)置的連接類包括:

數(shù)據(jù)庫 連接類
Mysql think\db\connector\Mysql
Pgsql think\db\connector\Pgsql
Sqlite think\db\connector\Sqlite
Sqlsrv think\db\connector\Sqlsrv

如果是僅僅使用原生SQL查詢的話撕阎,只需要使用連接類就可以了(通過調(diào)用Db類完成)

連接器類的作用小結(jié):

  • 連接數(shù)據(jù)庫;
  • 獲取數(shù)據(jù)表和字段信息碌补;
  • 基礎(chǔ)查詢(原生查詢);
  • 事務(wù)支持;
  • 分布式支持求橄;

查詢器類Query

除了基礎(chǔ)的原生查詢可以在連接類完成之外,其它的查詢操作都是調(diào)用查詢類的方法照藻,查詢類內(nèi)完成了數(shù)據(jù)訪問層最重要的工作,銜接了連接類和生成類汗侵,統(tǒng)一了數(shù)據(jù)庫的查詢用法幸缕,所以查詢類是不需要單獨驅(qū)動配合的,我們也稱之為查詢器晰韵。無論采用什么數(shù)據(jù)庫发乔,我們的查詢方式是統(tǒng)一的,因為數(shù)據(jù)訪問層核心只有一個唯一的查詢類:think\db\Query雪猪。

Query類封裝了所有的數(shù)據(jù)庫CURD方法的優(yōu)雅實現(xiàn)栏尚,包括鏈?zhǔn)椒椒案鞣N查詢,并自動使用了PDO參數(shù)綁定(參數(shù)自動綁定是在生成器類解析生成SQL時完成)只恨,最大程度地保護你的程序避免受數(shù)據(jù)庫注入攻擊译仗,查詢操作會調(diào)用生成類生成對應(yīng)數(shù)據(jù)庫的SQL語句,然后再調(diào)用連接類提供的底層原生查詢方法執(zhí)行最終的數(shù)據(jù)庫查詢操作官觅。

所有的數(shù)據(jù)庫查詢都使用了PDO的預(yù)處理和參數(shù)綁定機制纵菌。你所看到的大部分?jǐn)?shù)據(jù)庫方法都來自于查詢類而并非Db類,這一點很關(guān)鍵缰猴,也就是說雖然我們始終使用Db類操作數(shù)據(jù)庫产艾,而實際上大部分方法都是由查詢器類提供的方法疤剑。

生成器類Builder

生成類的作用是接收Query類的所有查詢參數(shù)滑绒,并負責(zé)解析生成對應(yīng)數(shù)據(jù)庫的原生SQL語法,然后返回給Query類進行后續(xù)的處理(包括交給連接類進行SQL執(zhí)行和返回結(jié)果處理)隘膘,也稱為(語法)生成器疑故。生成類的作用其實就是解決不同的數(shù)據(jù)庫查詢語法之間的差異。查詢類實現(xiàn)了統(tǒng)一的查詢接口弯菊,而生成類負責(zé)數(shù)據(jù)庫底層的查詢對接纵势。

生成類一般不需要自己調(diào)用,而是由查詢類自動調(diào)用的管钳。也可以這么理解钦铁,生成類和查詢類是一體的,事實上它們合起來就是通常我們所說的查詢構(gòu)造器(因為實際的查詢操作還是在連接器中執(zhí)行的)才漆。

通常每一個數(shù)據(jù)庫連接類都會對應(yīng)一個生成類牛曹,框架內(nèi)置的生成類包括:

數(shù)據(jù)庫 生成類
Mysql think\db\builder\Mysql
Pgsql think\db\builder\Pgsql
Sqlite think\db\builder\Sqlite
Sqlsrv think\db\builder\Sqlsrv

這些生成類都繼承了核心提供的生成器基類think\db\Builder,每個生成器類只需要提供差異部分的實現(xiàn)醇滥。

數(shù)據(jù)庫配置

數(shù)據(jù)庫的配置參數(shù)有很大的學(xué)問黎比,也是你掌握數(shù)據(jù)庫操作的基礎(chǔ)超营,主要用于數(shù)據(jù)庫的連接以及查詢的相關(guān)設(shè)置。

數(shù)據(jù)庫的配置參數(shù)用于連接類的架構(gòu)方法阅虫,而由于我們并不直接操作連接類演闭,所以,配置參數(shù)主要通過Db類傳入并設(shè)置到當(dāng)前的數(shù)據(jù)庫連接類颓帝。

數(shù)據(jù)庫配置分為靜態(tài)配置動態(tài)配置兩種方式米碰,靜態(tài)配置是指在數(shù)據(jù)庫配置文件中進行配置,動態(tài)配置是指在Db類或者Query類的connect方法中傳入動態(tài)的配置參數(shù)购城。

安裝好ThinkPHP5之后见间,默認(rèn)在application目錄下面會有一個database.php文件,這就是應(yīng)用的數(shù)據(jù)庫配置文件工猜,如果你的模塊需要單獨的數(shù)據(jù)庫配置文件米诉,那么只需要在模塊目錄下面創(chuàng)建一個database.php文件即可,并且只需要定義和應(yīng)用數(shù)據(jù)庫配置文件有差異的部分篷帅。

數(shù)據(jù)庫配置文件中配置的是默認(rèn)的數(shù)據(jù)庫連接配置史侣,如果你有多個數(shù)據(jù)庫連接,額外的數(shù)據(jù)庫連接是在應(yīng)用配置文件中完成的(參考后面的動態(tài)數(shù)據(jù)庫連接)魏身。

├─application           
│  ├─index              
│  │  ├─database.php     (模塊)數(shù)據(jù)庫配置文件
│  │  └─ ...            
│  ├─database.php       (應(yīng)用)數(shù)據(jù)庫配置文件
│  └─ ...        

我們下面的數(shù)據(jù)庫配置文件都以應(yīng)用數(shù)據(jù)庫配置文件為例說明惊橱。

默認(rèn)的應(yīng)用數(shù)據(jù)庫配置文件如下:

return [
    // 數(shù)據(jù)庫類型
    'type'            => 'mysql',
    // 服務(wù)器地址
    'hostname'        => '127.0.0.1',
    // 數(shù)據(jù)庫名
    'database'        => '',
    // 用戶名
    'username'        => 'root',
    // 密碼
    'password'        => '',
    // 端口
    'hostport'        => '',
    // 連接dsn
    'dsn'             => '',
    // 數(shù)據(jù)庫連接參數(shù)
    'params'          => [],
    // 數(shù)據(jù)庫編碼默認(rèn)采用utf8
    'charset'         => 'utf8',
    // 數(shù)據(jù)庫表前綴
    'prefix'          => '',
    // 數(shù)據(jù)庫調(diào)試模式
    'debug'           => true,
    // 數(shù)據(jù)庫部署方式:0 集中式(單一服務(wù)器),1 分布式(主從服務(wù)器)
    'deploy'          => 0,
    // 數(shù)據(jù)庫讀寫是否分離 主從式有效
    'rw_separate'     => false,
    // 讀寫分離后 主服務(wù)器數(shù)量
    'master_num'      => 1,
    // 指定從服務(wù)器序號
    'slave_no'        => '',
    // 是否嚴(yán)格檢查字段是否存在
    'fields_strict'   => true,
    // 數(shù)據(jù)集返回類型
    'resultset_type'  => 'array',
    // 自動寫入時間戳字段
    'auto_timestamp'  => false,
    // 時間字段取出后的默認(rèn)時間格式
    'datetime_format' => 'Y-m-d H:i:s',
    // 是否需要進行SQL性能分析
    'sql_explain'     => false,
    // Builder類
    'builder'         => '',
    // Query類
    'query'           => '\\think\\db\\Query',
];

最關(guān)鍵的參數(shù)就是下面幾個(其它參數(shù)后面會陸續(xù)涉及):

參數(shù)名 作用
type 數(shù)據(jù)庫類型或者連接類名
hostname 數(shù)據(jù)庫服務(wù)器地址(一般是IP地址,默認(rèn)為127.0.0.1
username 數(shù)據(jù)庫用戶名(默認(rèn)為root
password 數(shù)據(jù)庫用戶密碼(默認(rèn)為空)
database 使用的數(shù)據(jù)庫名稱
charset 數(shù)據(jù)庫編碼(默認(rèn)為utf8

type參數(shù)嚴(yán)格來說其實配置的是連接類名(而不是數(shù)據(jù)庫類型)箭昵,支持命名空間完整定義税朴,不帶命名空間定義的話,默認(rèn)采用\think\db\connector作為命名空間(內(nèi)置連接類的命名空間)家制。你完全可以在應(yīng)用中擴展自己的數(shù)據(jù)庫連接類正林,例如配置為:

// 配置數(shù)據(jù)庫類型(連接類)為自定義
'type'        => '\app\db\Mysql',

這樣就可以自己替換或者擴展一些額外的數(shù)據(jù)庫操作方法。

自定義連接類的時候颤殴,請注意設(shè)置數(shù)據(jù)庫配置中的builder參數(shù)避免找不到對應(yīng)生成器類觅廓。

ThinkPHP5.0采用PDO來統(tǒng)一操作數(shù)據(jù)庫,而連接類的最關(guān)鍵的作用就是通過配置連接到數(shù)據(jù)庫涵但,PDO的連接方法參數(shù)如下:

PDO::__construct ( 'DSN' ,'用戶名','密碼','連接參數(shù)(數(shù)組)' )

數(shù)據(jù)庫的數(shù)據(jù)源名稱(DSN)是最關(guān)鍵的一個參數(shù)杈绸,連接類負責(zé)把數(shù)據(jù)庫配置參數(shù)自動轉(zhuǎn)換為一個有效的DSN數(shù)據(jù)源名稱。如果你有特殊的連接語法需求矮瘟,則可以通過配置數(shù)據(jù)庫配置文件中的dsn參數(shù)來解決瞳脓,該配置參數(shù)的值會直接用于PDO連接,例如:

// 連接dsn
'dsn'   => 'mysql:unix_socket=/tmp/mysql.sock;dbname=demo',

數(shù)據(jù)庫支持?jǐn)嗑€重連機制(默認(rèn)關(guān)閉)澈侠,可以設(shè)置(V5.0.6+版本僅支持Mysql數(shù)據(jù)庫劫侧,V5.0.9+版本開始支持內(nèi)置所有數(shù)據(jù)庫):

// 開啟斷線重連
'break_reconnect' => true,

除了DSN數(shù)據(jù)源名稱,PDO的連接參數(shù)也可以單獨設(shè)置埋涧,每個連接驅(qū)動都有自己的連接參數(shù)設(shè)置板辽,Mysql連接器內(nèi)置采用的參數(shù)包括如下:

PDO::ATTR_CASE              => PDO::CASE_NATURAL,
PDO::ATTR_ERRMODE           => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_ORACLE_NULLS      => PDO::NULL_NATURAL,
PDO::ATTR_STRINGIFY_FETCHES => false,
PDO::ATTR_EMULATE_PREPARES  => false,

可以在數(shù)據(jù)庫配置文件中設(shè)置params參數(shù)奇瘦,會和內(nèi)置的連接參數(shù)合并,例如:

    // 數(shù)據(jù)庫連接參數(shù)
    'params'          => [
      // 使用長連接
      \PDO::ATTR_PERSISTENT   => true,
      // 數(shù)據(jù)表字段統(tǒng)一轉(zhuǎn)換為小寫
      \PDO::ATTR_CASE         => \PDO::CASE_LOWER,    
    ],

常用數(shù)據(jù)庫連接參數(shù)(params)可以參考PHP在線手冊中的以PDO::ATTR_開頭的常量劲弦。

如何開始查詢

在開始學(xué)習(xí)查詢之前耳标,我們首先在demo數(shù)據(jù)庫中創(chuàng)建一個data測試表。

CREATE TABLE IF NOT EXISTS `data`(
    `id` int(8) unsigned NOT NULL AUTO_INCREMENT,
    `name` varchar(255) NOT NULL COMMENT '名稱',
    PRIMARY KEY (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 ;

然后設(shè)置數(shù)據(jù)庫配置文件內(nèi)容為(如果有密碼請自行修改):

return [
    // 數(shù)據(jù)庫類型
    'type'            => 'mysql',
    // 服務(wù)器地址
    'hostname'        => '127.0.0.1',
    // 數(shù)據(jù)庫名
    'database'        => 'demo',
    // 用戶名
    'username'        => 'root',
    // 密碼
    'password'        => '',
    // 開啟數(shù)據(jù)庫調(diào)試
    'debug'        =>   true,
];

特別注意我們在配置中開啟了debug參數(shù)邑跪,表示開啟數(shù)據(jù)庫的調(diào)試模型次坡,開啟后會記錄數(shù)據(jù)庫的連接信息和SQL日志,數(shù)據(jù)庫的調(diào)試模式和應(yīng)用的調(diào)試模式是兩個不同的概念画畅。

配置完數(shù)據(jù)庫連接信息后砸琅,我們就可以直接使用Db類進行數(shù)據(jù)庫運行原生SQL操作了,你無需關(guān)心數(shù)據(jù)庫的連接操作轴踱,系統(tǒng)會自動使用數(shù)據(jù)庫配置參數(shù)進行數(shù)據(jù)庫的連接操作症脂。

Db類的方法都是靜態(tài)調(diào)用(不需要去實例化think\Db類),Db類的查詢方法有很多(大部分查詢都是使用的查詢構(gòu)造器)淫僻,本章內(nèi)容暫時只講兩個用于原生查詢的方法诱篷,包括query(查詢操作)和execute(寫入操作),更多的查詢方法會在查詢構(gòu)造器章節(jié)作出詳細講解雳灵。

數(shù)據(jù)庫查詢的所有示例都需要寫到一個控制器的方法里面棕所,我們現(xiàn)在假設(shè)你已經(jīng)定義了一個下面的控制器操作方法:

<?php

namespace app\index\controller;

use think\Db;

class Index
{
    public function index()
    {
        // 這里是數(shù)據(jù)庫操作的測試代碼
        // ...
        return;
    }
}

一般來說并不建議在控制器的操作方法中直接操作數(shù)據(jù)庫Db類,但由于我們還沒涉及到模型章節(jié)的內(nèi)容悯辙,因此琳省,目前的寫法僅為了演示數(shù)據(jù)庫的示例代碼。

并且在應(yīng)用配置文件中開啟頁面Trace顯示:

    // 應(yīng)用Trace
    'app_trace'              => true,

開啟頁面Trace的作用是為了方便我們查看當(dāng)前請求的SQL語句信息以及執(zhí)行時間(需要開啟數(shù)據(jù)庫調(diào)試模式后有效)躲撰。

然后在index操作方法中添加下面測試代碼:

Db::execute('insert into data (id, name) values (1, "hinkphp")');
Db::query('select * from data where id=1');

對數(shù)據(jù)表的CURD操作针贬,除了select和存儲過程調(diào)用使用query方法之外,其它的操作都使用execute方法茴肥,這里就不再一一演示了坚踩。

訪問頁面后荡灾,顯示空白瓤狐,點擊右下角的

就可以打開頁面Trace信息,切換到SQL一欄批幌,可以看到下面的類似信息

第一條表示數(shù)據(jù)庫的連接信息(連接消耗時間以及連接的DSN)础锐,后面的兩條就表示當(dāng)前操作執(zhí)行的SQL語句,由于我們使用的是原生查詢荧缘,所以SQL語句和你的代碼里面的SQL語句是一致的皆警,每條SQL語句最后會顯示該SQL語句的執(zhí)行消耗時間。

細心的朋友會發(fā)現(xiàn)Db類里面并沒有queryexecute方法截粗,其實在調(diào)用Db類的方法(connect方法除外)之前信姓,都會先調(diào)用connect方法進行數(shù)據(jù)庫的初始化(前面提過的__callStatic方法)鸵隧,由于connect方法會返回一個數(shù)據(jù)庫連接類的對象實例(根據(jù)配置參數(shù)實現(xiàn)了單例),所以Db類調(diào)用的queryexecute方法其實就是連接器類(Connection)的方法意推,這一點必須理解豆瘫,否則你很難理解數(shù)據(jù)庫的查詢操作。

使用參數(shù)綁定

上面的例子是實際開發(fā)中其實并不建議菊值,原則上我們在使用原生查詢的時候最好使用參數(shù)綁定避免SQL注入外驱,例如:

Db::execute('insert into data (id, name) values (?, ?)',[2,'kancloud']);
Db::query('select * from data where id=?',[2]);

頁面Trace信息中會顯示實際運行的SQL語句

也支持命名占位符綁定,例如:

Db::execute('insert into data (id, name) values (:id, :name)',['id'=>3,'name'=>'topthink']);
Db::query('select * from data where id=:id',['id'=>3]);

參數(shù)綁定的變量不需要使用引號

同樣顯示的實際執(zhí)行SQL如下:

我們看到查詢語句中的id的值是字符串的腻窒,由于參數(shù)綁定默認(rèn)都是使用的字符串昵宇,如果需要指定為數(shù)字類型,可以使用下面的方式:

Db::execute('insert into data (id, name) values (:id, :name)',['id'=>[4,\PDO::PARAM_INT],'name'=>'onethink']);
Db::query('select * from data where id=:id',['id'=>[4,\PDO::PARAM_INT]]);

這次查看實際的執(zhí)行SQL會有細微的變化

PDO命名占位綁定不支持一個參數(shù)多處綁定儿子,下面的用法會報錯:

Db::execute('insert into data (name) values (:name),(:name)',['name'=>'thinkphp']);

該錯誤信息表示你的參數(shù)綁定參數(shù)數(shù)量不符瓦哎。

查詢返回值

使用Db類查詢數(shù)據(jù)庫的話,query方法的返回值是一個二維數(shù)組的數(shù)據(jù)集柔逼,每個元素就是一條記錄杭煎,例如:

array (size=1)
  0 => 
    array (size=5)
      'id' => int 8
      'name' => string 'thinkphp' (length=8)

如果沒有查詢到任何數(shù)據(jù),返回值就是一個空數(shù)組卒落。

相比query方法羡铲,execute方法的返回值就比較單純,一般就是返回影響(包括新增和更新)的記錄數(shù)儡毕,如果沒有影響任何記錄也切,則返回值為0,所以千萬不要用布爾值來判斷execute是否執(zhí)行成功腰湾,事實上雷恃,在5.0里面不需要判斷是否成功,因為如果發(fā)生錯誤一定會拋出異常费坊。

動態(tài)連接數(shù)據(jù)庫

當(dāng)你需要使用多個數(shù)據(jù)庫連接的時候倒槐,就需要使用connect方法動態(tài)切換到另外一個數(shù)據(jù)庫連接,假設(shè)存在另外一個數(shù)據(jù)庫test附井,并且復(fù)制data過去更名為test讨越,然后測試下面的示例:

Db::query('select * from data where id = 2');
Db::connect([
    // 數(shù)據(jù)庫類型
    'type'     => 'mysql',
    // 服務(wù)器地址
    'hostname' => '127.0.0.1',
    // 數(shù)據(jù)庫名
    'database' => 'test',
    // 用戶名
    'username' => 'root',
    // 密碼
    'password' => '',
    // 開啟調(diào)試模式
    'debug'    => true,
])->query('select * from test where id = 1');
Db::query('select * from data where id = 3');

頁面Trace的顯示信息可以看出來使用了兩次數(shù)據(jù)庫連接和執(zhí)行了三次查詢,并且數(shù)據(jù)庫連接切換并沒有影響默認(rèn)的查詢(第三個查詢還是使用的默認(rèn)數(shù)據(jù)庫配置連接永毅,test數(shù)據(jù)庫中并不存在data表把跨,如果連接的還是第二個數(shù)據(jù)庫連接的話肯定會報錯)。

有時候沼死,我們只需要設(shè)置一些基本的數(shù)據(jù)庫配置參數(shù)着逐,可以簡化成一個字符串格式定義(該格式為ThinkPHP使用規(guī)范,而不是PDO連接規(guī)范,不要和DSN混淆起來):

Db::connect('mysql://root@127.0.0.1/demo#utf8')
    ->query('select * from data where id = 1');

字符串格式的連接信息規(guī)范格式如下:

數(shù)據(jù)庫類型://用戶名[:用戶密碼]@數(shù)據(jù)庫服務(wù)器地址[:端口]/數(shù)據(jù)庫名[?參數(shù)1=值&參數(shù)2=值]#數(shù)據(jù)庫編碼

Db類的connect方法會返回一個數(shù)據(jù)庫連接對象實例耸别,相同的連接參數(shù)返回的是同一個對象實例健芭,除非你強制重新實例化,例如:

Db::connect([
    // 數(shù)據(jù)庫類型
    'type'            => 'mysql',
    // 服務(wù)器地址
    'hostname'        => '127.0.0.1',
    // 數(shù)據(jù)庫名
    'database'        => 'demo',
    // 用戶名
    'username'        => 'root',
    // 密碼
    'password'        => '',
],true)->query('select * from data where id = 1');

這樣秀姐,每次調(diào)用都會重新實例化數(shù)據(jù)庫的連接類吟榴。

為了便于統(tǒng)一管理,你可以把數(shù)據(jù)庫配置參數(shù)納入配置文件囊扳,例如在應(yīng)用配置文件中添加:

'db_config' =>  [
    // 數(shù)據(jù)庫類型
    'type'            => 'mysql',
    // 服務(wù)器地址
    'hostname'        => '127.0.0.1',
    // 數(shù)據(jù)庫名
    'database'        => 'demo',
    // 用戶名
    'username'        => 'root',
    // 密碼
    'password'        => '',
],

或者使用字符串方式定義

'db_config' =>  'mysql://root@127.0.0.1/demo',

上面的db_config配置參數(shù)不是在數(shù)據(jù)庫配置文件中定義吩翻,而是在應(yīng)用配置文件或者模塊配置文件中定義。

然后锥咸,使用下面的方式來動態(tài)連接獲取切換連接

Db::connect('db_config')
    ->query('select * from data where id=:id', ['id'=>3]);

當(dāng)connect方法傳入的連接參數(shù)是字符串并且不包含/等特殊符號的話狭瞎,表示使用的是預(yù)定義數(shù)據(jù)庫配置參數(shù)。

分布式支持

數(shù)據(jù)訪問層支持分布式數(shù)據(jù)庫搏予,包括讀寫分離熊锭,要啟用分布式數(shù)據(jù)庫,需要開啟數(shù)據(jù)庫配置文件中的deploy參數(shù):

return [
    // 啟用分布式數(shù)據(jù)庫
    'deploy'    =>  1,
    // 數(shù)據(jù)庫類型
    'type'        => 'mysql',
    // 服務(wù)器地址
    'hostname'    => '192.168.1.1,192.168.1.2',
    // 數(shù)據(jù)庫名
    'database'    => 'demo',
    // 數(shù)據(jù)庫用戶名
    'username'    => 'root',
    // 數(shù)據(jù)庫密碼
    'password'    => '',
    // 數(shù)據(jù)庫連接端口
    'hostport'    => '',
];

啟用分布式數(shù)據(jù)庫后雪侥,hostname參數(shù)是關(guān)鍵碗殷,hostname的個數(shù)決定了分布式數(shù)據(jù)庫的數(shù)量,默認(rèn)情況下第一個地址就是主服務(wù)器速缨。

主從服務(wù)器支持設(shè)置不同的連接參數(shù)锌妻,包括:

連接參數(shù)
username
password
hostport
database
dsn
charset

如果主從服務(wù)器的上述參數(shù)一致的話,只需要設(shè)置一個旬牲,對于不同的參數(shù)仿粹,可以分別設(shè)置,例如:

return [
    // 啟用分布式數(shù)據(jù)庫
    'deploy'   => 1,
    // 數(shù)據(jù)庫類型
    'type'     => 'mysql',
    // 服務(wù)器地址
    'hostname' => '192.168.1.1,192.168.1.2,192.168.1.3',
    // 數(shù)據(jù)庫名
    'database' => 'demo',
    // 數(shù)據(jù)庫用戶名
    'username' => 'root,slave,slave',
    // 數(shù)據(jù)庫密碼
    'password' => '123456',
    // 數(shù)據(jù)庫連接端口
    'hostport' => '',
    // 數(shù)據(jù)庫字符集
    'charset'  => 'utf8',
];

記住原茅,要么相同吭历,要么每個都要設(shè)置。

還可以設(shè)置分布式數(shù)據(jù)庫的讀寫是否分離擂橘,默認(rèn)的情況下讀寫不分離晌区,也就是每臺服務(wù)器都可以進行讀寫操作,對于主從式數(shù)據(jù)庫而言通贞,需要設(shè)置讀寫分離朗若,通過下面的設(shè)置就可以:

    'rw_separate' => true,

在讀寫分離的情況下,默認(rèn)第一個數(shù)據(jù)庫配置是主服務(wù)器的配置信息滑频,負責(zé)寫入數(shù)據(jù)捡偏,如果設(shè)置了master_num參數(shù),則可以支持多個主服務(wù)器寫入(每次隨機連接其中一個主服務(wù)器)峡迷。其它的地址都是從數(shù)據(jù)庫,負責(zé)讀取數(shù)據(jù),數(shù)量不限制绘搞。每次連接從服務(wù)器并且進行讀取操作的時候彤避,系統(tǒng)會隨機進行在從服務(wù)器中選擇。同一個數(shù)據(jù)庫連接的每次請求只會連接一次主服務(wù)器和從服務(wù)器夯辖,如果某次請求的從服務(wù)器連接不上琉预,會自動切換到主服務(wù)器進行查詢操作。

如果不希望隨機讀取蒿褂,或者某種情況下其它從服務(wù)器暫時不可用圆米,還可以設(shè)置slave_no 指定固定服務(wù)器進行讀操作,slave_no指定的序號表示hostname中數(shù)據(jù)庫地址的序號啄栓,從0開始娄帖。

調(diào)用查詢類或者模型的CURD操作的話,系統(tǒng)會自動判斷當(dāng)前執(zhí)行的方法是讀操作還是寫操作并自動連接主從服務(wù)器昙楚,如果你用的是原生SQL近速,那么需要注意系統(tǒng)的默認(rèn)規(guī)則: 寫操作必須用數(shù)據(jù)庫的execute方法,讀操作必須用數(shù)據(jù)庫的query方法堪旧,否則會發(fā)生主從讀寫錯亂的情況削葱。

發(fā)生下列情況的話,會自動連接主服務(wù)器:

  • 使用了數(shù)據(jù)庫的寫操作方法(execute/insert/update/delete以及衍生方法)淳梦;
  • 如果調(diào)用了數(shù)據(jù)庫事務(wù)方法的話析砸,會自動連接主服務(wù)器;
  • 從服務(wù)器連接失敗爆袍,會自動連接主服務(wù)器干厚;
  • 調(diào)用了查詢構(gòu)造器的lock方法;
  • 調(diào)用了查詢構(gòu)造器的master方法

主從數(shù)據(jù)庫的數(shù)據(jù)同步工作不在框架實現(xiàn)螃宙,需要數(shù)據(jù)庫考慮自身的同步或者復(fù)制機制蛮瞄。如果在大數(shù)據(jù)量或者特殊的情況下寫入數(shù)據(jù)后可能會存在同步延遲的情況,可以調(diào)用master()方法進行主庫查詢操作谆扎。

在實際生產(chǎn)環(huán)境中挂捅,很多云主機的數(shù)據(jù)庫分布式實現(xiàn)機制和本地開發(fā)會有所區(qū)別,但通常會采下面用兩種方式:

  • 第一種:提供了寫IP和讀IP(一般是虛擬IP)堂湖,進行數(shù)據(jù)庫的讀寫分離操作闲先;
  • 第二種:始終保持同一個IP連接數(shù)據(jù)庫,內(nèi)部會進行讀寫分離IP調(diào)度(阿里云就是采用該方式)无蜂。

存儲過程調(diào)用

數(shù)據(jù)訪問層支持存儲過程調(diào)用伺糠,調(diào)用數(shù)據(jù)庫存儲過程使用下面的方法:

$resultSet = Db::query('call procedure_name');
foreach ($resultSet as $result) {

}

存儲過程返回的是一個數(shù)據(jù)集,如果你的存儲過程不需要返回任何的數(shù)據(jù)斥季,那么也可以使用execute方法:

Db::execute('call procedure_name');

存儲過程可以支持輸入和輸出參數(shù)训桶,以及進行參數(shù)綁定操作累驮。

$resultSet = Db::query('call procedure_name(:in_param1,:in_param2,:out_param)', [
    'in_param1' => $param1,
    'in_param2' => [$param2, PDO::PARAM_INT],
    'out_param' => [$outParam, PDO::PARAM_STR | PDO::PARAM_INPUT_OUTPUT, 4000],
]);

輸出參數(shù)的綁定必須額外使用PDO::PARAM_INPUT_OUTPUT,并且可以和輸入?yún)?shù)公用一個參數(shù)舵揭。

無論存儲過程內(nèi)部做了什么操作谤专,每次存儲過程調(diào)用僅僅被當(dāng)成一次查詢。

數(shù)據(jù)庫事務(wù)

5.0對數(shù)據(jù)庫事務(wù)的封裝更為完善午绳,事務(wù)的支持由連接器類來完成置侍,但查詢器類中也對事務(wù)進行了封裝調(diào)用,不過我們?nèi)匀恢恍枰ㄟ^Db類便可完成事務(wù)操作拦焚。

使用事務(wù)處理的話蜡坊,需要數(shù)據(jù)庫引擎支持事務(wù)處理。比如MySQLMyISAM類型不支持事務(wù)處理赎败,需要使用InnoDB引擎秕衙。

最簡單的方法是使用transaction方法操作數(shù)據(jù)庫事務(wù),會自動控制事務(wù)處理螟够,當(dāng)發(fā)生任何異常會自動回滾灾梦,例如:

Db::transaction(function () {
    Db::table('user')->find(1);
    Db::table('user')->where('id', 1)->save(['name' => 'thinkphp']);
    Db::table('user')->delete(1);
});

也可以手動控制事務(wù),例如:

// 啟動事務(wù)
Db::startTrans();
try{
    Db::table('user')->find(1);
    Db::table('user')->where('id',1)->save(['name'=>'thinkphp']);
    Db::table('user')->delete(1);
    // 提交事務(wù)
    Db::commit();    
} catch (\Exception $e) {
    // 回滾事務(wù)
    Db::rollback();
}

在事務(wù)操作的時候妓笙,確保你的數(shù)據(jù)庫連接是同一個若河,否則事務(wù)會失效,V5.0.9版本之前的db助手函數(shù)都是默認(rèn)重新鏈接數(shù)據(jù)庫的寞宫,請不要在事務(wù)中使用萧福。

總結(jié)

通過本章的學(xué)習(xí),你應(yīng)該了解了5.0的數(shù)據(jù)庫架構(gòu)設(shè)計和數(shù)據(jù)庫抽象訪問層的組成辈赋,以及如何配置數(shù)據(jù)庫信息和使用基礎(chǔ)的原生查詢鲫忍,掌握了用Db類的connect方法切換不同的數(shù)據(jù)庫連接,基本了解了存儲過程及事務(wù)的用法钥屈。后面一章悟民,我們會先來了解下數(shù)據(jù)庫的創(chuàng)建和數(shù)據(jù)遷移,之后就會進入真正的數(shù)據(jù)庫查詢的學(xué)習(xí)了篷就。

下一篇:第二章:數(shù)據(jù)創(chuàng)建和遷移

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末射亏,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子竭业,更是在濱河造成了極大的恐慌智润,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,542評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件未辆,死亡現(xiàn)場離奇詭異窟绷,居然都是意外死亡,警方通過查閱死者的電腦和手機咐柜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評論 3 385
  • 文/潘曉璐 我一進店門兼蜈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來攘残,“玉大人,你說我怎么就攤上這事饭尝】贤螅” “怎么了献宫?”我有些...
    開封第一講書人閱讀 158,021評論 0 348
  • 文/不壞的土叔 我叫張陵钥平,是天一觀的道長。 經(jīng)常有香客問我姊途,道長涉瘾,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,682評論 1 284
  • 正文 為了忘掉前任捷兰,我火速辦了婚禮立叛,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘贡茅。我一直安慰自己秘蛇,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,792評論 6 386
  • 文/花漫 我一把揭開白布顶考。 她就那樣靜靜地躺著赁还,像睡著了一般。 火紅的嫁衣襯著肌膚如雪驹沿。 梳的紋絲不亂的頭發(fā)上艘策,一...
    開封第一講書人閱讀 49,985評論 1 291
  • 那天,我揣著相機與錄音渊季,去河邊找鬼朋蔫。 笑死,一個胖子當(dāng)著我的面吹牛却汉,可吹牛的內(nèi)容都是我干的驯妄。 我是一名探鬼主播,決...
    沈念sama閱讀 39,107評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼合砂,長吁一口氣:“原來是場噩夢啊……” “哼青扔!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起既穆,我...
    開封第一講書人閱讀 37,845評論 0 268
  • 序言:老撾萬榮一對情侶失蹤赎懦,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后幻工,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體励两,經(jīng)...
    沈念sama閱讀 44,299評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,612評論 2 327
  • 正文 我和宋清朗相戀三年囊颅,在試婚紗的時候發(fā)現(xiàn)自己被綠了当悔。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片傅瞻。...
    茶點故事閱讀 38,747評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖盲憎,靈堂內(nèi)的尸體忽然破棺而出嗅骄,到底是詐尸還是另有隱情,我是刑警寧澤饼疙,帶...
    沈念sama閱讀 34,441評論 4 333
  • 正文 年R本政府宣布溺森,位于F島的核電站,受9級特大地震影響窑眯,放射性物質(zhì)發(fā)生泄漏屏积。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,072評論 3 317
  • 文/蒙蒙 一磅甩、第九天 我趴在偏房一處隱蔽的房頂上張望炊林。 院中可真熱鬧,春花似錦卷要、人聲如沸渣聚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽奕枝。三九已至,卻和暖如春彪标,著一層夾襖步出監(jiān)牢的瞬間倍权,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評論 1 267
  • 我被黑心中介騙來泰國打工捞烟, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留薄声,地道東北人。 一個月前我還...
    沈念sama閱讀 46,545評論 2 362
  • 正文 我出身青樓题画,卻偏偏與公主長得像默辨,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子苍息,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,658評論 2 350