# 第一章:數據庫架構基礎
本章我們首先從`ThinkPHP5.0`的數據庫訪問層架構設計原理開始,然后熟悉下數據庫的配置,并掌握如何進行基礎的查詢操作,并簡單介紹了分布式、存儲過程及事務,學習內容主要包括:
[TOC=2,2]
## 數據庫架構設計
使用框架開發應用,一般不需要直接操作數據庫,而是通過框架封裝好的數據庫中間層對數據庫進行操作。這樣的好處主要有兩個:一是簡化數據庫操作,二是做到跨數據庫的一致性。這種設計的中間層通常稱之為數據庫訪問抽象層,簡稱數據訪問層(`DAL`),ThinkPHP5的數據訪問層是基于PHP內置的`PDO`對象實現。一般抽象層本身并不直接操作數據庫,而是通過驅動來實現具體的數據庫操作。
`ThinkPHP5.0`的數據庫設計相比之前版本更加合理,數據訪問層劃分的更細化,把數據訪問對象分成了連接器、查詢器、生成器等多個對象,并通過數據庫訪問入口類統一調用,分工更明確,各司其職,欲知詳情且聽我慢慢道來。
ThinkPHP數據訪問層設計示意圖:

> 5.1版本的架構略微進行了一些調整,變成:

### 數據庫入口類`Db`
平常我們的數據庫操作使用的類庫一般都是數據庫的入口類`think\Db`。這個類非常的簡單,主要就是一個`connect`方法,根據數據庫配置參數連接數據庫(注意這里的連接并非真正的連接數據庫,只是做好了隨時連接的準備工作,只有在實際查詢的時候才會真正去連接數據庫,是一種惰性連接)并獲取到數據庫連接對象的實例。
`Db`類都是靜態方法調用,但看起來這個類啥都沒實現,那是怎么操作數據庫的呢,其實就是封裝了數據庫操作方法的**靜態調用**(利用`__callStatic`方法),下面是代碼實現:
~~~
// 調用驅動類的方法
public static function __callStatic($method, $params)
{
// 自動初始化數據庫
return call_user_func_array([self::connect(), $method], $params);
}
~~~
理論上來說,框架并不依賴`Db`類,該類的存在只是為了簡化數據庫抽象層的操作而提供的一個工廠類,否則你就需要單獨實例化不同的數據庫連接類。因此,看似可有可無的`Db`類就成了數據訪問層實現的點睛之筆了^_^
>[danger] 所有的數據庫操作都是經過`Db`類調用,并且`Db`類是一個靜態類,但`Db`類自身只有一個公共方法`connect`。
### 連接器類`Connection`
顧名思義,連接類的作用就是連接數據庫,也稱為連接器。我們知道,不同的數據庫的連接方式和參數都是不同的,連接類就是要解決這個差異問題。
數據庫入口類里面實例化的類其實就是對應數據庫的連接類,連接類的基類是`think\db\Connection`。例如,需要連接`Mysql`數據庫的話,就必須定義一個`Mysql`連接類(內置由`think\db\connector\Mysql`類實現,繼承了`think\db\Connection`類),當然具體的連接類名沒有固定的規范(例如,`MongoDb`的連接類就是`think\mongo\Connection`)。如果某個數據庫的連接擴展類沒有繼承`think\db\Connection`,那就意味著所有的數據庫底層操作有可能被接管,在個別特殊的數據庫的擴展中就有類似的實現,例如`MongoDb`數據庫擴展。
>[danger] 數據庫連接都是惰性的,只有最終執行SQL的時候才會進行連接。
連接器是數據訪問層的基礎,基于PHP本身的`PDO`實現(如果你還不了解`PDO`,請參考PHP官方手冊中[PDO](http://php.net/manual/zh/book.pdo.php)部分,不在本書的討論范疇),連接類的主要作用就是連接具體的數據庫,以及完成基本的數據庫底層操作,包括對分布式、存儲過程和事務的完善處理。而更多的數據操作則交由查詢類完成。
框架內置的連接類包括:
|數據庫|連接類|
|---|---|
|Mysql|think\db\connector\Mysql|
|Pgsql|think\db\connector\Pgsql|
|Sqlite|think\db\connector\Sqlite|
|Sqlsrv|think\db\connector\Sqlsrv|
>如果是僅僅使用原生SQL查詢的話,只需要使用連接類就可以了(通過調用Db類完成)
連接器類的作用小結:
* 連接數據庫;
* 獲取數據表和字段信息;
* 基礎查詢(原生查詢);
* 事務支持;
* 分布式支持;
### 查詢器類`Query`
除了基礎的原生查詢可以在連接類完成之外,其它的查詢操作都是調用查詢類的方法,查詢類內完成了數據訪問層最重要的工作,銜接了連接類和生成類,統一了數據庫的查詢用法,所以查詢類是不需要單獨驅動配合的,我們也稱之為查詢器。無論采用什么數據庫,我們的查詢方式是統一的,因為數據訪問層核心只有一個唯一的查詢類:`think\db\Query`。
`Query`類封裝了所有的數據庫`CURD`方法的優雅實現,包括鏈式方法及各種查詢,并自動使用了`PDO`參數綁定(參數自動綁定是在生成器類解析生成SQL時完成),最大程度地保護你的程序避免受數據庫注入攻擊,查詢操作會調用生成類生成對應數據庫的SQL語句,然后再調用連接類提供的底層原生查詢方法執行最終的數據庫查詢操作。
>[danger] 所有的數據庫查詢都使用了`PDO`的預處理和參數綁定機制。你所看到的大部分數據庫方法都來自于查詢類而并非`Db`類,這一點很關鍵,也就是說雖然我們始終使用`Db`類操作數據庫,而實際上大部分方法都是由查詢器類提供的方法。
### 生成器類`Builder`
生成類的作用是接收`Query`類的所有查詢參數,并負責解析生成對應數據庫的原生`SQL`語法,然后返回給`Query`類進行后續的處理(包括交給連接類進行`SQL`執行和返回結果處理),也稱為(語法)生成器。生成類的作用其實就是解決不同的數據庫查詢語法之間的差異。查詢類實現了統一的查詢接口,而生成類負責數據庫底層的查詢對接。
>[danger] 生成類一般不需要自己調用,而是由查詢類自動調用的。也可以這么理解,生成類和查詢類是一體的,事實上它們合起來就是通常我們所說的查詢構造器(因為實際的查詢操作還是在連接器中執行的)。
通常每一個數據庫連接類都會對應一個生成類,框架內置的生成類包括:
|數據庫|生成類|
|---|---|
|Mysql|think\db\builder\Mysql|
|Pgsql|think\db\builder\Pgsql|
|Sqlite|think\db\builder\Sqlite|
|Sqlsrv|think\db\builder\Sqlsrv|
這些生成類都繼承了核心提供的生成器基類`think\db\Builder`,每個生成器類只需要提供差異部分的實現。
## 數據庫配置
數據庫的配置參數有很大的學問,也是你掌握數據庫操作的基礎,主要用于數據庫的連接以及查詢的相關設置。
數據庫的配置參數用于連接類的架構方法,而由于我們并不直接操作連接類,所以,配置參數主要通過`Db`類傳入并設置到當前的數據庫連接類。
數據庫配置分為**靜態配置**和**動態配置**兩種方式,靜態配置是指在數據庫配置文件中進行配置,動態配置是指在Db類或者`Query`類的`connect`方法中傳入動態的配置參數。
安裝好ThinkPHP5之后,默認在`application`目錄下面會有一個`database.php`文件,這就是應用的數據庫配置文件,如果你的模塊需要單獨的數據庫配置文件,那么只需要在模塊目錄下面創建一個`database.php`文件即可,并且只需要定義和應用數據庫配置文件有差異的部分。
>[danger] 數據庫配置文件中配置的是默認的數據庫連接配置,如果你有多個數據庫連接,額外的數據庫連接是在應用配置文件中完成的(參考后面的動態數據庫連接)。
~~~
├─application
│ ├─index
│ │ ├─database.php (模塊)數據庫配置文件
│ │ └─ ...
│ ├─database.php (應用)數據庫配置文件
│ └─ ...
~~~
> 我們下面的數據庫配置文件都以應用數據庫配置文件為例說明。
默認的應用數據庫配置文件如下:
~~~
return [
// 數據庫類型
'type' => 'mysql',
// 服務器地址
'hostname' => '127.0.0.1',
// 數據庫名
'database' => '',
// 用戶名
'username' => 'root',
// 密碼
'password' => '',
// 端口
'hostport' => '',
// 連接dsn
'dsn' => '',
// 數據庫連接參數
'params' => [],
// 數據庫編碼默認采用utf8
'charset' => 'utf8',
// 數據庫表前綴
'prefix' => '',
// 數據庫調試模式
'debug' => true,
// 數據庫部署方式:0 集中式(單一服務器),1 分布式(主從服務器)
'deploy' => 0,
// 數據庫讀寫是否分離 主從式有效
'rw_separate' => false,
// 讀寫分離后 主服務器數量
'master_num' => 1,
// 指定從服務器序號
'slave_no' => '',
// 是否嚴格檢查字段是否存在
'fields_strict' => true,
// 數據集返回類型
'resultset_type' => 'array',
// 自動寫入時間戳字段
'auto_timestamp' => false,
// 時間字段取出后的默認時間格式
'datetime_format' => 'Y-m-d H:i:s',
// 是否需要進行SQL性能分析
'sql_explain' => false,
// Builder類
'builder' => '',
// Query類
'query' => '\\think\\db\\Query',
];
~~~
最關鍵的參數就是下面幾個(其它參數后面會陸續涉及):
|參數名|作用|
|---|---|
|type|數據庫類型或者連接類名|
|hostname|數據庫服務器地址(一般是IP地址,默認為`127.0.0.1`)|
|username|數據庫用戶名(默認為`root`)|
|password|數據庫用戶密碼(默認為空)|
|database|使用的數據庫名稱|
|charset|數據庫編碼(默認為`utf8`)|
`type`參數嚴格來說其實配置的是連接類名(而不是數據庫類型),支持命名空間完整定義,不帶命名空間定義的話,默認采用`\think\db\connector`作為命名空間(內置連接類的命名空間)。你完全可以在應用中擴展自己的數據庫連接類,例如配置為:
~~~
// 配置數據庫類型(連接類)為自定義
'type' => '\app\db\Mysql',
~~~
這樣就可以自己替換或者擴展一些額外的數據庫操作方法。
>[danger] 自定義連接類的時候,請注意設置數據庫配置中的`builder`參數避免找不到對應生成器類。
`ThinkPHP5.0`采用PDO來統一操作數據庫,而連接類的最關鍵的作用就是通過配置連接到數據庫,PDO的連接方法參數如下:
>[info] #### PDO::__construct ( 'DSN' ,'用戶名','密碼','連接參數(數組)' )
數據庫的數據源名稱(`DSN`)是最關鍵的一個參數,連接類負責把數據庫配置參數自動轉換為一個有效的`DSN`數據源名稱。如果你有特殊的連接語法需求,則可以通過配置數據庫配置文件中的`dsn`參數來解決,該配置參數的值會直接用于PDO連接,例如:
~~~
// 連接dsn
'dsn' => 'mysql:unix_socket=/tmp/mysql.sock;dbname=demo',
~~~
數據庫支持斷線重連機制(默認關閉),可以設置(`V5.0.6+`版本僅支持Mysql數據庫,`V5.0.9+`版本開始支持內置所有數據庫):
~~~
// 開啟斷線重連
'break_reconnect' => true,
~~~
除了`DSN`數據源名稱,`PDO`的連接參數也可以單獨設置,每個連接驅動都有自己的連接參數設置,`Mysql`連接器內置采用的參數包括如下:
~~~
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,
~~~
可以在數據庫配置文件中設置`params`參數,會和內置的連接參數合并,例如:
~~~
// 數據庫連接參數
'params' => [
// 使用長連接
\PDO::ATTR_PERSISTENT => true,
// 數據表字段統一轉換為小寫
\PDO::ATTR_CASE => \PDO::CASE_LOWER,
],
~~~
常用數據庫連接參數(params)可以參考[PHP在線手冊](http://php.net/manual/zh/pdo.constants.php)中的以`PDO::ATTR_`開頭的常量。
## 如何開始查詢
在開始學習查詢之前,我們首先在`demo`數據庫中創建一個`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 ;
~~~
然后設置數據庫配置文件內容為(如果有密碼請自行修改):
~~~
return [
// 數據庫類型
'type' => 'mysql',
// 服務器地址
'hostname' => '127.0.0.1',
// 數據庫名
'database' => 'demo',
// 用戶名
'username' => 'root',
// 密碼
'password' => '',
// 開啟數據庫調試
'debug' => true,
];
~~~
> 特別注意我們在配置中開啟了`debug`參數,表示開啟數據庫的調試模型,開啟后會記錄數據庫的連接信息和SQL日志,數據庫的調試模式和應用的調試模式是兩個不同的概念。
配置完數據庫連接信息后,我們就可以直接使用`Db`類進行數據庫運行原生SQL操作了,你無需關心數據庫的連接操作,系統會自動使用數據庫配置參數進行數據庫的連接操作。
`Db`類的方法都是靜態調用(不需要去實例化`think\Db`類),`Db`類的查詢方法有很多(大部分查詢都是使用的查詢構造器),本章內容暫時只講兩個用于原生查詢的方法,包括`query`(查詢操作)和`execute`(寫入操作),更多的查詢方法會在查詢構造器章節作出詳細講解。
數據庫查詢的所有示例都需要寫到一個控制器的方法里面,我們現在假設你已經定義了一個下面的控制器操作方法:
~~~
<?php
namespace app\index\controller;
use think\Db;
class Index
{
public function index()
{
// 這里是數據庫操作的測試代碼
// ...
return;
}
}
~~~
> 一般來說并不建議在控制器的操作方法中直接操作數據庫Db類,但由于我們還沒涉及到模型章節的內容,因此,目前的寫法僅為了演示數據庫的示例代碼。
并且在應用配置文件中開啟頁面Trace顯示:
~~~
// 應用Trace
'app_trace' => true,
~~~
> 開啟頁面Trace的作用是為了方便我們查看當前請求的SQL語句信息以及執行時間(需要開啟數據庫調試模式后有效)。
然后在`index`操作方法中添加下面測試代碼:
~~~
Db::execute('insert into data (id, name) values (1, "hinkphp")');
Db::query('select * from data where id=1');
~~~
> 對數據表的CURD操作,除了`select`和存儲過程調用使用`query`方法之外,其它的操作都使用`execute`方法,這里就不再一一演示了。
訪問頁面后,顯示空白,點擊右下角的 
就可以打開頁面Trace信息,切換到SQL一欄,可以看到下面的類似信息

第一條表示數據庫的連接信息(連接消耗時間以及連接的DSN),后面的兩條就表示當前操作執行的SQL語句,由于我們使用的是原生查詢,所以SQL語句和你的代碼里面的SQL語句是一致的,每條SQL語句最后會顯示該SQL語句的執行消耗時間。
細心的朋友會發現`Db`類里面并沒有`query`和`execute`方法,其實在調用`Db`類的方法(`connect`方法除外)之前,都會先調用`connect`方法進行數據庫的初始化(前面提過的`__callStatic`方法),由于`connect`方法會返回一個數據庫連接類的對象實例(根據配置參數實現了單例),所以`Db`類調用的`query`和`execute`方法其實就是連接器類(`Connection`)的方法,這一點必須理解,否則你很難理解數據庫的查詢操作。
## 使用參數綁定
上面的例子是實際開發中其實并不建議,原則上我們在使用原生查詢的時候最好使用參數綁定避免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]);
~~~
>[danger] 參數綁定的變量不需要使用引號
同樣顯示的實際執行SQL如下:

我們看到查詢語句中的id的值是字符串的,由于參數綁定默認都是使用的字符串,如果需要指定為數字類型,可以使用下面的方式:
~~~
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]]);
~~~
這次查看實際的執行SQL會有細微的變化

PDO命名占位綁定不支持一個參數多處綁定,下面的用法會報錯:
~~~
Db::execute('insert into data (name) values (:name),(:name)',['name'=>'thinkphp']);
~~~

該錯誤信息表示你的參數綁定參數數量不符。
## 查詢返回值
使用`Db`類查詢數據庫的話,`query`方法的返回值是一個二維數組的數據集,每個元素就是一條記錄,例如:
~~~
array (size=1)
0 =>
array (size=5)
'id' => int 8
'name' => string 'thinkphp' (length=8)
~~~
>[danger] 如果沒有查詢到任何數據,返回值就是一個空數組。
相比`query`方法,`execute`方法的返回值就比較單純,一般就是返回影響(包括新增和更新)的記錄數,如果沒有影響任何記錄,則返回值為`0`,所以千萬不要用布爾值來判斷`execute`是否執行成功,事實上,在`5.0`里面不需要判斷是否成功,因為如果發生錯誤一定會拋出異常。
## 動態連接數據庫
當你需要使用多個數據庫連接的時候,就需要使用`connect`方法動態切換到另外一個數據庫連接,假設存在另外一個數據庫`test`,并且復制`data`過去更名為`test`,然后測試下面的示例:
~~~
Db::query('select * from data where id = 2');
Db::connect([
// 數據庫類型
'type' => 'mysql',
// 服務器地址
'hostname' => '127.0.0.1',
// 數據庫名
'database' => 'test',
// 用戶名
'username' => 'root',
// 密碼
'password' => '',
// 開啟調試模式
'debug' => true,
])->query('select * from test where id = 1');
Db::query('select * from data where id = 3');
~~~
頁面Trace的顯示信息可以看出來使用了兩次數據庫連接和執行了三次查詢,并且數據庫連接切換并沒有影響默認的查詢(第三個查詢還是使用的默認數據庫配置連接,`test`數據庫中并不存在`data`表,如果連接的還是第二個數據庫連接的話肯定會報錯)。

有時候,我們只需要設置一些基本的數據庫配置參數,可以簡化成一個字符串格式定義(該格式為ThinkPHP使用規范,而不是PDO連接規范,不要和`DSN`混淆起來):
~~~
Db::connect('mysql://root@127.0.0.1/demo#utf8')
->query('select * from data where id = 1');
~~~
字符串格式的連接信息規范格式如下:
>[info]#### 數據庫類型://用戶名[:用戶密碼]@數據庫服務器地址[:端口]/數據庫名[?參數1=值&參數2=值]#數據庫編碼
`Db`類的`connect`方法會返回一個數據庫連接對象實例,相同的連接參數返回的是同一個對象實例,除非你強制重新實例化,例如:
~~~
Db::connect([
// 數據庫類型
'type' => 'mysql',
// 服務器地址
'hostname' => '127.0.0.1',
// 數據庫名
'database' => 'demo',
// 用戶名
'username' => 'root',
// 密碼
'password' => '',
],true)->query('select * from data where id = 1');
~~~
這樣,每次調用都會重新實例化數據庫的連接類。
為了便于統一管理,你可以把數據庫配置參數納入配置文件,例如在應用配置文件中添加:
~~~
'db_config' => [
// 數據庫類型
'type' => 'mysql',
// 服務器地址
'hostname' => '127.0.0.1',
// 數據庫名
'database' => 'demo',
// 用戶名
'username' => 'root',
// 密碼
'password' => '',
],
~~~
或者使用字符串方式定義
~~~
'db_config' => 'mysql://root@127.0.0.1/demo',
~~~
>[danger] 上面的`db_config`配置參數不是在數據庫配置文件中定義,而是在應用配置文件或者模塊配置文件中定義。
然后,使用下面的方式來動態連接獲取切換連接
~~~
Db::connect('db_config')
->query('select * from data where id=:id', ['id'=>3]);
~~~
當`connect`方法傳入的連接參數是字符串并且不包含`/`等特殊符號的話,表示使用的是預定義數據庫配置參數。
## 分布式支持
數據訪問層支持分布式數據庫,包括讀寫分離,要啟用分布式數據庫,需要開啟數據庫配置文件中的`deploy`參數:
~~~
return [
// 啟用分布式數據庫
'deploy' => 1,
// 數據庫類型
'type' => 'mysql',
// 服務器地址
'hostname' => '192.168.1.1,192.168.1.2',
// 數據庫名
'database' => 'demo',
// 數據庫用戶名
'username' => 'root',
// 數據庫密碼
'password' => '',
// 數據庫連接端口
'hostport' => '',
];
~~~
啟用分布式數據庫后,`hostname`參數是關鍵,`hostname`的個數決定了分布式數據庫的數量,默認情況下第一個地址就是主服務器。
主從服務器支持設置不同的連接參數,包括:
|連接參數|
|---|
|username|
|password|
|hostport|
|database|
|dsn|
|charset|
如果主從服務器的上述參數一致的話,只需要設置一個,對于不同的參數,可以分別設置,例如:
~~~
return [
// 啟用分布式數據庫
'deploy' => 1,
// 數據庫類型
'type' => 'mysql',
// 服務器地址
'hostname' => '192.168.1.1,192.168.1.2,192.168.1.3',
// 數據庫名
'database' => 'demo',
// 數據庫用戶名
'username' => 'root,slave,slave',
// 數據庫密碼
'password' => '123456',
// 數據庫連接端口
'hostport' => '',
// 數據庫字符集
'charset' => 'utf8',
];
~~~
>記住,要么相同,要么每個都要設置。
還可以設置分布式數據庫的讀寫是否分離,默認的情況下讀寫不分離,也就是每臺服務器都可以進行讀寫操作,對于主從式數據庫而言,需要設置讀寫分離,通過下面的設置就可以:
~~~
'rw_separate' => true,
~~~
在讀寫分離的情況下,默認第一個數據庫配置是主服務器的配置信息,負責寫入數據,如果設置了`master_num`參數,則可以支持多個主服務器寫入(每次隨機連接其中一個主服務器)。其它的地址都是從數據庫,負責讀取數據,數量不限制。每次連接從服務器并且進行讀取操作的時候,系統會隨機進行在從服務器中選擇。同一個數據庫連接的每次請求只會連接一次主服務器和從服務器,如果某次請求的從服務器連接不上,會自動切換到主服務器進行查詢操作。
如果不希望隨機讀取,或者某種情況下其它從服務器暫時不可用,還可以設置`slave_no` 指定固定服務器進行讀操作,`slave_no`指定的序號表示`hostname`中數據庫地址的序號,從`0`開始。
調用查詢類或者模型的`CURD`操作的話,系統會自動判斷當前執行的方法是讀操作還是寫操作并自動連接主從服務器,如果你用的是原生SQL,那么需要注意系統的默認規則: 寫操作必須用數據庫的`execute`方法,讀操作必須用數據庫的`query`方法,否則會發生主從讀寫錯亂的情況。
發生下列情況的話,會自動連接主服務器:
* 使用了數據庫的寫操作方法(`execute`/`insert`/`update`/`delete`以及衍生方法);
* 如果調用了數據庫事務方法的話,會自動連接主服務器;
* 從服務器連接失敗,會自動連接主服務器;
* 調用了查詢構造器的`lock`方法;
* 調用了查詢構造器的`master`方法
>[danger] 主從數據庫的數據同步工作不在框架實現,需要數據庫考慮自身的同步或者復制機制。如果在大數據量或者特殊的情況下寫入數據后可能會存在同步延遲的情況,可以調用`master()`方法進行主庫查詢操作。
>[info] 在實際生產環境中,很多云主機的數據庫分布式實現機制和本地開發會有所區別,但通常會采下面用兩種方式:
>
> * 第一種:提供了寫IP和讀IP(一般是虛擬IP),進行數據庫的讀寫分離操作;
> * 第二種:始終保持同一個IP連接數據庫,內部會進行讀寫分離IP調度(阿里云就是采用該方式)。
## 存儲過程調用
數據訪問層支持存儲過程調用,調用數據庫存儲過程使用下面的方法:
~~~
$resultSet = Db::query('call procedure_name');
foreach ($resultSet as $result) {
}
~~~
存儲過程返回的是一個數據集,如果你的存儲過程不需要返回任何的數據,那么也可以使用`execute`方法:
~~~
Db::execute('call procedure_name');
~~~
存儲過程可以支持輸入和輸出參數,以及進行參數綁定操作。
~~~
$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],
]);
~~~
輸出參數的綁定必須額外使用`PDO::PARAM_INPUT_OUTPUT`,并且可以和輸入參數公用一個參數。
>[danger] 無論存儲過程內部做了什么操作,每次存儲過程調用僅僅被當成一次查詢。
## 數據庫事務
5.0對數據庫事務的封裝更為完善,事務的支持由連接器類來完成,但查詢器類中也對事務進行了封裝調用,不過我們仍然只需要通過Db類便可完成事務操作。
>[danger] 使用事務處理的話,需要數據庫引擎支持事務處理。比如`MySQL`的`MyISAM`類型不支持事務處理,需要使用`InnoDB`引擎。
最簡單的方法是使用`transaction`方法操作數據庫事務,會自動控制事務處理,當發生任何異常會自動回滾,例如:
~~~
Db::transaction(function () {
Db::table('user')->find(1);
Db::table('user')->where('id', 1)->save(['name' => 'thinkphp']);
Db::table('user')->delete(1);
});
~~~
也可以手動控制事務,例如:
~~~
// 啟動事務
Db::startTrans();
try{
Db::table('user')->find(1);
Db::table('user')->where('id',1)->save(['name'=>'thinkphp']);
Db::table('user')->delete(1);
// 提交事務
Db::commit();
} catch (\Exception $e) {
// 回滾事務
Db::rollback();
}
~~~
>[danger] 在事務操作的時候,確保你的數據庫連接是同一個,否則事務會失效,`V5.0.9`版本之前的`db`助手函數都是默認重新鏈接數據庫的,請不要在事務中使用。
## 總結
通過本章的學習,你應該了解了5.0的數據庫架構設計和數據庫抽象訪問層的組成,以及如何配置數據庫信息和使用基礎的原生查詢,掌握了用`Db`類的`connect`方法切換不同的數據庫連接,基本了解了存儲過程及事務的用法。后面一章,我們會先來了解下數據庫的創建和數據遷移,之后就會進入真正的數據庫查詢的學習了。