原文地址:http://blog.onlywan.cc/14843810761202.html
Laravel Eloquent使用小記
今天因爲開發數據庫業務中間層需要,開始研究Laravel Eloquent,因爲剛開始使用laravel框架的時候,都是使用query,查詢構建器來寫sql類似於
DB::connection('mydb')->table('mylove')
->where( 'name', 'guowan' )
->get();
複雜一點的sql使用db::raw
DB::connection('mydb')->table('mylove')->select( DB::RAW( 'count("name") as mylovecount' ) )
->where( 'name', 'guowan' )
->get();
本着在工作中學習的態度開始研究Eloquent,對着laravel中文文檔,開始設計Eloquent Model。這裏給出表大概字段(因兼容老系統要求,表字段設計與當前業務不相符,這裏不與討論~)
表結構
CREATE TABLE `user_ext` (
`user_id` int(10) NOT NULL,
`realname` varchar(255) DEFAULT NULL,
`gender` int(11) NOT NULL DEFAULT '0',
`birthday` datetime DEFAULT NULL,
`comefrom` varchar(255) DEFAULT NULL,
`qq` varchar(255) DEFAULT NULL,
`weibo` varchar(255) DEFAULT NULL,
`blog` varchar(255) DEFAULT NULL,
`mobile` varchar(255) DEFAULT NULL,
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
CREATE TABLE `user` (
`user_id` int(10) NOT NULL AUTO_INCREMENT,
`username` varchar(100) DEFAULT NULL,
`email` varchar(255) DEFAULT NULL,
`user_img` varchar(255) DEFAULT NULL,
) ENGINE=InnoDB DEFAULT CHARSET=utf8
創建Eloqueue Model
- user
<?php
namespace App\Http\Models\Eloquent;
use Illuminate\Database\Eloquent\Model;
class CUser extends Model
{
/**
* 與模型關聯的數據表。
*
* @var string
*/
protected $table = 'user';
/*
* 數據庫表主鍵
*
* @var string
*/
protected $primaryKey = 'user_id';
/*
* 取消自動維護create_at,update_at字段
*
* @var string
*/
public $timestamps = false;
/*
* 獲取與指定用戶相關聯的擴展信息記錄
*/
public function hasOneExt()
{
return $this->hasOne( 'App\Http\Models\Eloquent\CUserExt', 'user_id', 'user_id' );
}
}
- user_ext
<?php
namespace App\Http\Models\Eloquent;
use Illuminate\Database\Eloquent\Model;
class CUserExt extends Model
{
/**
* 與模型關聯的數據表。
*
* @var string
*/
protected $table = 'ac_user_ext';
/*
* 數據庫表主鍵
*
* @var string
*/
protected $primaryKey = 'user_id';
/*
* 取消自動維護create_at,update_at字段
*
* @var string
*/
public $timestamps = false;
public function acUser()
{
return $this->belongsTo( 'App\Http\Models\Eloquent\CUser' );
}
}
user與user_ext表爲1對1關係。
注意
user model中的hasOneExt方法,之所以使用hasOneExt方法名,是通過方法命名,在調用方法的時候就可以知道和userExt表的關係。
hasOne函數,第一個參數是類路徑;第二個參數外鍵,也就是userExt表的主鍵;第三個參數纔是user表主鍵。自己使用的時候,沒有指定第二個和第三個參數,會出現錯誤
問題
下面纔是今天記錄的主要內容,在使用過程中,出現一些問題,以及問題相應的解決方法,可能有些問題還沒有解決或者解決的不好,這裏記錄一下,增加一下印象,也可以和其他同學一塊討論一下
1. 依賴方法hasOneExt
調用下面方法
$oUser = CUser::find( $sUMId )->hasOneExt();
結果竟然返回UserExt表中數據。我的本意本來想做相應的關聯查詢,查出兩個表的數據。然後在網上各種搜索Eloquent兩表聯查,返回兩表字段。
最終解決方案如下:
$oUser = CAcUser::with( 'hasOneExt' )->find( $sUMId );
查詢結果:
Array
(
[user_id] => 1
[username] => admin
[email] => wanguowan521@163.com
[user_img] => 201303/26132122j2lg.jpg
[has_one_ext] => Array
(
[user_id] => 1
[realname] => 瞌睡
[gender] => 1
[birthday] =>
[comefrom] => **,不限
[qq] =>
[weibo] =>
[blog] =>
[mobile] =>
)
)
這裏依賴表數據使用方法名作爲key成爲返回結果的一部分,這個對於業務接口,需要一維數組的時候還得需要翻譯。幸好對於業務層來說,希望屏蔽底層數據層字段細節,本來就需要做一次翻譯,所以這裏也就不是什麼大問題。
這裏with語法,是Eloquent中所謂的預加載語法,主要是爲了解決ORM(Object Relation Mapping) n+1次查詢問題–詳細說明。在網上查詢過程中,這裏雖然是1對1關係,但是如果這樣解決,會將一次join查詢,變成兩次查詢,對於將來高併發場景來說,有點不能接受。但是不這樣解決又沒有找到其他解決方法。
無奈,嘗試打印Eloquent執行的sql,查看具體的sql語句(打印laravel執行sql方法比較多,可以參考資料),代碼如下:
DB::enableQueryLog();
$oUser = CUser::with( 'hasOneExt' )->find( $sUMId );
print_r(
DB::getQueryLog()
);
打印結果如下:
Array
(
[0] => Array
(
[query] => select * from `user` where `user`.`user_id` = ? limit 1
[bindings] => Array
(
[0] => 1
)
[time] => 0.56
)
[1] => Array
(
[query] => select * from `user_ext` where `user_ext`.`user_id` in (?)
[bindings] => Array
(
[0] => 1
)
[time] => 0.32
)
)
可以看出,sql先根據user_id查詢到主標數據,然後在去依賴表中做in查詢,這樣確實解決了ORM n+1次查詢的問題,但是對於直接使用sql,還是多出一次查詢。
這裏發現一個比較有趣的事情,log裏有一個time值,難道這個是sql執行時間,如果這個是執行時間的話,那就可以簡單的驗證一下sql執行效率問題了,然後開始查詢資料,終於在源碼中找到了答案,源碼如下:詳細鏈接
/**
* Run a SQL statement and log its execution context.
*
* @param string $query
* @param array $bindings
* @param Closure $callback
* @return mixed
*
* @throws QueryException
*/
protected function run($query, $bindings, Closure $callback)
{
$start = microtime(true);
// To execute the statement, we'll simply call the callback, which will actually
// run the SQL against the PDO connection. Then we can calculate the time it
// took to execute and log the query SQL, bindings and time in our memory.
try
{
$result = $callback($this, $query, $bindings);
}
// If an exception occurs when attempting to run a query, we'll format the error
// message to include the bindings with SQL, which will make this exception a
// lot more helpful to the developer instead of just the database's errors.
catch (\Exception $e)
{
throw new QueryException($query, $bindings, $e);
}
// Once we have run the query we will calculate the time that it took to run and
// then log the query, bindings, and execution time so we will report them on
// the event that the developer needs them. We'll log time in milliseconds.
$time = $this->getElapsedTime($start);
$this->logQuery($query, $bindings, $time);
return $result;
}
/**
* Get the elapsed time since a given starting point.
*
* @param int $start
* @return float
*/
protected function getElapsedTime($start)
{
return round((microtime(true) - $start) * 1000, 2);
}
這裏可以看出time就是sql執行時間,而且單位是毫秒.
這裏就可以測試單條join和使用eloquent with查詢效率對比,代碼如下:
DB::enableQueryLog();
DB::table( 'user' )
->leftJoin( 'user_ext as ext', 'user.user_id', '=', 'ext.user_id' )
->where( 'user.user_id', 1 )
->get();
$oUser = CUser::with( 'hasOneExt' )->find( $sUMId );
print_r(
DB::getQueryLog()
);
結果如下:
Array
(
[0] => Array
(
[query] => select * from `user` as `user` left join `user_ext` as `ext` on `user`.`user_id` = `ext`.`user_id` where `user`.`user_id` = ?
[bindings] => Array
(
[0] => 1
)
[time] => 0.65
)
[1] => Array
(
[query] => select * from `user` where `user`.`user_id` = ? limit 1
[bindings] => Array
(
[0] => 1
)
[time] => 0.35
)
[2] => Array
(
[query] => select * from `user_ext` where `user_ext`.`user_id` in (?)
[bindings] => Array
(
[0] => 1
)
[time] => 0.35
)
)
從結果可以看出,執行一條時間相比執行兩條時間,差距不是很大,但是客觀來說,這說明不了什麼問題;首先,測試基於本地數據庫,一次請求和兩次請求的網絡影響及延遲會比線上差距要小很多;其次,本地測試數據庫,兩個表數據量都在1k,數據量太小,無法反應真實線上數據查詢效率。所以這裏查詢結果僅供參考,後期詳細結果,會在本地僞造100w左右數據量進行測試觀察,並諮詢公司dba,對於大數據量對連表查詢效率影響情況。
總結
對於今天解決問題的過程,雖然感覺沒有得到完美的答案,但是在查詢過程中也學習到不少東西,在這裏做一下記錄。以備後期溫故學習。
這裏記錄一下幾個小細節:
數據庫查詢過程中,爲了節省應用服務器與數據庫服務器之間網絡流量及數據庫服務器的IO,數據庫查詢原則是隻查詢返回有用字段,對於無用的大字段,特別是text等,不需要時,儘量不查詢。數據庫查詢儘量不要使用selec *
Eloquent 聯合查詢指定字段
- 方法1
$oUser = CUser::with( [ 'hasOneExt' => function( $query ) {
$query->select( 'user_id', 'realname', 'gender', 'birthday' );
} ] )->find( $sUMId, [ 'user_id', 'username', 'email', 'user_img' ] );
其中
- 方法2
public function hasOneExt() {
return $this->hasOne( 'App\Http\Models\Eloquent\CUserExt', 'user_id', 'user_id' )
->select( 'user_id', 'realname', 'gender', 'birthday' );
}
$oUser = CUser::with( 'hasOneExt' )->find( $sUMId, [ 'user_id', 'username', 'email', 'user_img' ] );
執行sql結果:
Array
(
[0] => Array
(
[query] => select `user_id`, `username`, `email`, `user_img` from `user` where `user`.`user_id` = ? limit 1
[bindings] => Array
(
[0] => 1
)
[time] => 0.5
)
[1] => Array
(
[query] => select `user_id`, `realname`, `gender`, `birthday` from `user_ext` where `user_ext`.`user_id` in (?)
[bindings] => Array
(
[0] => 1
)
[time] => 0.33
)
)
歡迎添加公衆號: