Laravel Eloquent使用小记

原文地址: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。对于大数据量对连表查询效率影响情况。

总结

对于今天解决这个问题的过程,尽管感觉没有得到完美的答案,可是在查询过程中也学习到不少东西。在这里做一下记录。

以备后期温故学习。

这里记录一下几个小细节:

数据库查询过程中。为了节省应用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' ] );

当中query>select(userid,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
        )

)

欢迎加入公众号:


posted @ 2017-08-16 09:45  wzjhoutai  阅读(491)  评论(0编辑  收藏  举报