原文地址: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 AppHttpModelsEloquent;
use IlluminateDatabaseEloquentModel;
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( 'AppHttpModelsEloquentCUserExt', 'user_id', 'user_id' );
}
}
- user_ext
<?php
namespace AppHttpModelsEloquent;
use IlluminateDatabaseEloquentModel;
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( 'AppHttpModelsEloquentCUser' );
}
}
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。对于大数据量对连表查询效率影响情况。
总结
对于今天解决这个问题的过程,尽管感觉没有得到完美的答案,可是在查询过程中也学习到不少东西。在这里做一下记录。
以备后期温故学习。
这里记录一下几个小细节:
数据库查询过程中。为了节省应用server与数据库server之间网络流量及数据库server的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( 'AppHttpModelsEloquentCUserExt', '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
)
)
欢迎加入公众号: