• 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 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' ] );
    

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

    欢迎加入公众号:


  • 相关阅读:
    [ES6]react中使用es6语法
    [前端自动化]grunt的简单使用
    [react-native]react-native填坑笔记
    [CSS布局]简单的CSS三列布局
    简单说下cookie,LocalStorage与SessionStorage.md
    [算法学习]开始leetcode之旅
    monorepo和multrepo的简介
    异步请求(ajax,http) 之 逐渐完善的大全
    Java中Synchronized的用法
    Node.js小白开路(一)-- fs篇
  • 原文地址:https://www.cnblogs.com/wzjhoutai/p/7371679.html
Copyright © 2020-2023  润新知