CodeIgniter源碼分析 7.1 - 數(shù)據(jù)庫(kù)驅(qū)動(dòng)

本節(jié)我們看下數(shù)據(jù)庫(kù)驅(qū)動(dòng)相關(guān)的源碼褪秀,本質(zhì)上來(lái)說(shuō)數(shù)據(jù)庫(kù)驅(qū)動(dòng)其實(shí)就是對(duì)適配器模式的應(yīng)用而已:在抽象層統(tǒng)一好暴露給外界的接口,在驅(qū)動(dòng)內(nèi)部封裝差異化的細(xì)節(jié)慕爬。

image.png

CI框架數(shù)據(jù)庫(kù)驅(qū)動(dòng)的架構(gòu)是由這幾部分構(gòu)成的:

  • 數(shù)據(jù)庫(kù)連接台囱,源碼位于 DB.php 中氏淑。
  • 緩存處理,源碼位于 DB_cache.php 中。
  • 驅(qū)動(dòng)抽象層骇径,源碼位于 DB_driver.php 中躯肌。
  • 用來(lái)建表的數(shù)據(jù)庫(kù)工廠,源碼位于 DB_forge.php 中破衔。
  • 查詢(xún)構(gòu)造器清女,源碼位于 DB_query_builder.php 中。
  • 查詢(xún)結(jié)果處理晰筛,源碼位于 DB_result.php 中嫡丙。
  • 工具函數(shù),源碼位于 DB_utility.php 中读第。
  • 以及一些驅(qū)動(dòng)器曙博,位于 systems/database/drivers 下。
image.png

由于這部分的源碼太多怜瞒,我們選擇性的將一些關(guān)鍵點(diǎn)的源碼分析一下父泳,只分析驅(qū)動(dòng) mysqli 的情況,主要圍繞如下幾點(diǎn):

  • 驅(qū)動(dòng)是如何適配的吴汪?
  • 數(shù)據(jù)庫(kù)是怎么連接上的惠窄?連接是怎么處理的?
  • 原生的 sql 語(yǔ)句是怎么處理的浇坐?
  • 為什么要有查詢(xún)構(gòu)造器睬捶?查詢(xún)構(gòu)造器是什么封裝的?
  • 事務(wù)是怎么封裝的近刘?事務(wù)的自動(dòng)提交與回滾又是怎么做的擒贸?
  • 數(shù)據(jù)庫(kù)緩存的是sql還是sql結(jié)果集?


驅(qū)動(dòng)加載

$this->load->database() 可以實(shí)現(xiàn)連接數(shù)據(jù)庫(kù)觉渴;位于加載器 Loader.php 中 介劫,該函數(shù)可以支持通過(guò)加載器自動(dòng)載入(也就是自動(dòng)連接數(shù)據(jù)庫(kù),之前在加載器源碼分析中看到過(guò))案淋,也可以支持手動(dòng)調(diào)用實(shí)現(xiàn)數(shù)據(jù)庫(kù)連接座韵。

/*
     * $param 參數(shù)是可選的,它是數(shù)據(jù)庫(kù)配置踢京,如果沒(méi)傳誉碴,那么它就會(huì)讀取配置文件 database.php 中的配置。
     * $return 決定是否返回?cái)?shù)據(jù)庫(kù)對(duì)象瓣距,該參數(shù)的意義在于當(dāng)你手動(dòng)連接多個(gè)數(shù)據(jù)庫(kù)時(shí)黔帕,你就不能再通過(guò)$this->db
     *       獲取數(shù)據(jù)庫(kù)對(duì)象了,因?yàn)?this->db不知道你要引用哪個(gè)數(shù)據(jù)庫(kù),你只能在該函數(shù)中將數(shù)據(jù)庫(kù)返回蹈丸。
     * $query_builder 決定是否啟用查詢(xún)構(gòu)造器成黄,因?yàn)樵谝恍╊?lèi)庫(kù)的函數(shù)中依賴(lài)查詢(xún)構(gòu)造器訪問(wèn)數(shù)據(jù)庫(kù)呐芥;該參數(shù)會(huì)覆蓋配置文件中的 query_builder。
     * */
    public function database($params = '', $return = FALSE, $query_builder = NULL)
    {
        // Grab the super object
        $CI =& get_instance();

        // 檢測(cè)數(shù)據(jù)庫(kù)是否已經(jīng)連接,如果已經(jīng)連接奋岁,直接返回
        if ($return === FALSE && $query_builder === NULL && isset($CI->db) && is_object($CI->db) && ! empty($CI->db->conn_id))
        {
            return FALSE;
        }

        //真正的數(shù)據(jù)庫(kù)連接是在 DB.php 中
        require_once(BASEPATH.'database/DB.php');

        if ($return === TRUE)
        {
            return DB($params, $query_builder);
        }

        //數(shù)據(jù)庫(kù)連接成功后掛在全局對(duì)象$CI(也就是$this)上
        $CI->db = '';

        $CI->db =& DB($params, $query_builder);
        return $this;
    }

上面的代碼中我們看到思瘟,數(shù)據(jù)庫(kù)對(duì)象是從 DB.php 中生成的,該文件中我們將會(huì)看到是如何讀取數(shù)據(jù)庫(kù)配置以及適配驅(qū)動(dòng)的闻伶。

function &DB($params = '', $query_builder_override = NULL)
    {
        // 判斷是否通過(guò)參數(shù)傳進(jìn)了 dsn 配置滨攻,如果沒(méi)有就要讀配置文件
        if (is_string($params) && strpos($params, '://') === FALSE)
        {
            //判斷配文文件是否存在
            if ( ! file_exists($file_path = APPPATH.'config/'.ENVIRONMENT.'/database.php')
                && ! file_exists($file_path = APPPATH.'config/database.php'))
            {
                show_error('The configuration file database.php does not exist.');
            }

            //加載配置文件
            include($file_path);

            //如果指定了要加載的目錄,還得去這些目錄下加載
            if (class_exists('CI_Controller', FALSE))
            {
                foreach (get_instance()->load->get_package_paths() as $path)
                {
                    if ($path !== APPPATH)
                    {
                        if (file_exists($file_path = $path.'config/'.ENVIRONMENT.'/database.php'))
                        {
                            include($file_path);
                        }
                        elseif (file_exists($file_path = $path.'config/database.php'))
                        {
                            include($file_path);
                        }
                    }
                }
            }

            //檢車(chē)配置文件中的配置項(xiàng)是否存在
            if ( ! isset($db) OR count($db) === 0)
            {
                show_error('No database connection settings were found in the database config file.');
            }

            //param參數(shù)有可能是空蓝翰,也有可能是某個(gè)配置組的key铡买,如果param不為空,說(shuō)明傳進(jìn)來(lái)的是某個(gè)配置組的key霎箍,
            //后面就需要根據(jù)這個(gè)key選擇特定的配置組
            if ($params !== '')
            {
                $active_group = $params;
            }

            //檢測(cè)$active_group 作為 key 的配置組是否存在
            if ( ! isset($active_group))
            {
                show_error('You have not specified a database connection group via $active_group in your config/database.php file.');
            }
            elseif ( ! isset($db[$active_group]))
            {
                show_error('You have specified an invalid database connection group ('.$active_group.') in your config/database.php file.');
            }

            $params = $db[$active_group];
        }
        //通過(guò)參數(shù)傳進(jìn)了 dsn 格式的配置
        elseif (is_string($params))
        {
            //解析 dsn奇钞,失敗的話就報(bào)錯(cuò),說(shuō)明是非法的連接字符串
            if (($dsn = @parse_url($params)) === FALSE)
            {
                show_error('Invalid DB Connection String');
            }

            //這里為什么又要講 dsn 中的參數(shù)解析到 $param 中是因?yàn)槟J(rèn)讀取的配置文件中的配置以及通過(guò)手動(dòng)方式傳入的配置都是
            //數(shù)組的格式漂坏,為了方便后續(xù)的處理景埃,將配置都同一成數(shù)組格式;這里還需注意:用 rawurldecode() 而非 urldecode()
            //的原因是因?yàn)榍罢卟粫?huì)將加號(hào) + 解析成空格
            $params = array(
                'dbdriver'  => $dsn['scheme'],
                'hostname'  => isset($dsn['host']) ? rawurldecode($dsn['host']) : '',
                'port'      => isset($dsn['port']) ? rawurldecode($dsn['port']) : '',
                'username'  => isset($dsn['user']) ? rawurldecode($dsn['user']) : '',
                'password'  => isset($dsn['pass']) ? rawurldecode($dsn['pass']) : '',
                'database'  => isset($dsn['path']) ? rawurldecode(substr($dsn['path'], 1)) : ''
            );

            // 解析 url 中的查詢(xún)字符串
            if (isset($dsn['query']))
            {
                parse_str($dsn['query'], $extra);

                foreach ($extra as $key => $val)
                {
                    if (is_string($val) && in_array(strtoupper($val), array('TRUE', 'FALSE', 'NULL')))
                    {
                        $val = var_export($val, TRUE);
                    }

                    $params[$key] = $val;
                }
            }
        }

        // 如果既沒(méi)通過(guò)參數(shù)傳入配置顶别,也沒(méi)從配置文件讀到配置谷徙,那么就報(bào)錯(cuò)
        if (empty($params['dbdriver']))
        {
            show_error('You have not selected a database type to connect to.');
        }

        // 是否啟用查詢(xún)構(gòu)造器
        if ($query_builder_override !== NULL)
        {
            $query_builder = $query_builder_override;
        }
        // Backwards compatibility work-around for keeping the
        // $active_record config variable working. Should be
        // removed in v3.1
        elseif ( ! isset($query_builder) && isset($active_record))
        {
            $query_builder = $active_record;
        }

        //載入驅(qū)動(dòng)抽象層,該類(lèi)中規(guī)范了所有驅(qū)動(dòng)對(duì)外的一致接口
        require_once(BASEPATH.'database/DB_driver.php');

        //判斷是否啟用查詢(xún)構(gòu)造器驯绎,然后將抽象驅(qū)動(dòng)層掛到 CI_DB 上完慧,后面的各種驅(qū)動(dòng)只需要繼承 CI_DB 統(tǒng)一對(duì)外行為就行了,
        //注意: CI_DB_query_builder 繼承了 CI_DB_driver剩失,所以這里的 CI_DB 可看做是它兩的別名
        if ( ! isset($query_builder) OR $query_builder === TRUE)
        {
            require_once(BASEPATH.'database/DB_query_builder.php');
            if ( ! class_exists('CI_DB', FALSE))
            {
                /**
                 * CI_DB
                 *
                 * Acts as an alias for both CI_DB_driver and CI_DB_query_builder.
                 *
                 * @see CI_DB_query_builder
                 * @see CI_DB_driver
                 */
                class CI_DB extends CI_DB_query_builder { }
            }
        }
        elseif ( ! class_exists('CI_DB', FALSE))
        {
            /**
             * @ignore
             */
            class CI_DB extends CI_DB_driver { }
        }


        //讀取配置中的驅(qū)動(dòng)屈尼,然后載入該驅(qū)動(dòng)
        $driver_file = BASEPATH.'database/drivers/'.$params['dbdriver'].'/'.$params['dbdriver'].'_driver.php';

        file_exists($driver_file) OR show_error('Invalid DB driver');
        require_once($driver_file);

        // 實(shí)例化該驅(qū)動(dòng)
        $driver = 'CI_DB_'.$params['dbdriver'].'_driver';
        $DB = new $driver($params);

        //處理驅(qū)動(dòng)是 pdo 的情況,由于各個(gè)數(shù)據(jù)庫(kù)語(yǔ)法有不同的地方(主要是一些獲取元數(shù)據(jù)的語(yǔ)法)拴孤,所以將 pdo 又做了一層抽象脾歧,在它下面又劃分了一些子驅(qū)動(dòng)
        if ( ! empty($DB->subdriver))
        {
            $driver_file = BASEPATH.'database/drivers/'.$DB->dbdriver.'/subdrivers/'.$DB->dbdriver.'_'.$DB->subdriver.'_driver.php';

            if (file_exists($driver_file))
            {
                require_once($driver_file);
                $driver = 'CI_DB_'.$DB->dbdriver.'_'.$DB->subdriver.'_driver';
                $DB = new $driver($params);
            }
        }

        //初始化,在該方法中進(jìn)行數(shù)據(jù)庫(kù)連接
        $DB->initialize();
        return $DB;

通過(guò)分析這段代碼我們看出驅(qū)動(dòng)的適配是通過(guò)讀取配置文件演熟,進(jìn)而加載相關(guān)的驅(qū)動(dòng)鞭执,驅(qū)動(dòng)實(shí)例化后通過(guò)調(diào)用了 $DB->initialize() 方法實(shí)現(xiàn)了數(shù)據(jù)連接 。


數(shù)據(jù)庫(kù)連接

我們知道數(shù)據(jù)庫(kù)連接是在 initialize()方法中實(shí)現(xiàn)的芒粹,接下來(lái)通過(guò)學(xué)習(xí)該方法的源碼你會(huì)看到如何處理數(shù)據(jù)庫(kù)連接兄纺,以及我們經(jīng)常看到的一個(gè)字眼 高可用化漆。

public function initialize()
    {
        //判斷是否已經(jīng)連接上了估脆,是的話,直接返回
        if ($this->conn_id)
        {
            return TRUE;
        }

        // ----------------------------------------------------------------

        /*
         * 連接數(shù)據(jù)庫(kù)
         *
         * 注意:
         * 接下來(lái)這行代碼體現(xiàn)出了面向?qū)ο蟮纳铄渲幓袢琲nitialize() 是在驅(qū)動(dòng)抽象層的旁蔼,
         * 而當(dāng)驅(qū)動(dòng)繼承了驅(qū)動(dòng)抽象層,這里的 db_connect() 則是調(diào)用驅(qū)動(dòng)自己的 db_connect()疙教;
         * 參數(shù)pconnect決定是否為持久連接棺聊,持久連接可以提升效率,但是持久連接不會(huì)在腳本
         * 結(jié)束后釋放連接贞谓,如果連接數(shù)太多的時(shí)候會(huì)導(dǎo)致資源占用,影響別的客戶(hù)端限佩。
         *
         * */
        $this->conn_id = $this->db_connect($this->pconnect);

        // 連接的數(shù)據(jù)庫(kù),但是發(fā)現(xiàn)此連接失敗裸弦,那就檢測(cè)是否設(shè)置了故障轉(zhuǎn)移(高可用),
        // 故障轉(zhuǎn)移是在配置文件的 failove 字段上設(shè)置的祟同,
        // 具體的故障轉(zhuǎn)移配置見(jiàn):https://codeigniter.org.cn/user_guide/database/configuration.html
        if ( ! $this->conn_id)
        {
            // Check if there is a failover set
            if ( ! empty($this->failover) && is_array($this->failover))
            {
                // Go over all the failovers
                foreach ($this->failover as $failover)
                {
                    // Replace the current settings with those of the failover
                    foreach ($failover as $key => $val)
                    {
                        $this->$key = $val;
                    }

                    // Try to connect
                    $this->conn_id = $this->db_connect($this->pconnect);

                    // If a connection is made break the foreach loop
                    if ($this->conn_id)
                    {
                        break;
                    }
                }
            }

            // 如果使用持久化連接以后依然失敗,那么報(bào)錯(cuò)
            if ( ! $this->conn_id)
            {
                log_message('error', 'Unable to connect to the database');

                if ($this->db_debug)
                {
                    $this->display_error('db_unable_to_connect');
                }

                return FALSE;
            }
        }

        // 最后設(shè)置字符編碼
        return $this->db_set_charset($this->char_set);
    }

這段代碼中我們看到了所謂的高可用方案理疙,通過(guò)在配置文件 database.php 設(shè)置 failover 實(shí)現(xiàn)的晕城,并且做了持久化連接的設(shè)置。
同時(shí)可看到真正的數(shù)據(jù)庫(kù)連接是下沉到各個(gè)驅(qū)動(dòng)中去的窖贤,因此我們才會(huì)看到了驅(qū)動(dòng)實(shí)現(xiàn)了各自的 db_connect() 方法砖顷。

驅(qū)動(dòng)中的數(shù)據(jù)庫(kù)連接就很簡(jiǎn)單了,解析連接參數(shù) 赃梧,然后調(diào)用相關(guān)的接口進(jìn)行數(shù)據(jù)庫(kù)連接罷了滤蝠;

public function db_connect($persistent = FALSE)
{
        //判斷是否為socket連接
        if ($this->hostname[0] === '/')
        {
            $hostname = NULL;
            $port = NULL;
            $socket = $this->hostname;
        }
        else
        {
            //非socket連接,根據(jù)參數(shù)配置選擇是否持久連接授嘀,注意:持久化連接僅在php5.3以上支持
            $hostname = ($persistent === TRUE && is_php('5.3'))
                ? 'p:'.$this->hostname : $this->hostname;
            $port = empty($this->port) ? NULL : $this->port;
            $socket = NULL;
        }

        //接下來(lái)就是連接前的一些參數(shù)設(shè)置:超時(shí)時(shí)間物咳,客戶(hù)端壓縮之類(lèi)的
        $client_flags = ($this->compress === TRUE) ? MYSQLI_CLIENT_COMPRESS : 0;
        $mysqli = mysqli_init();

        $mysqli->options(MYSQLI_OPT_CONNECT_TIMEOUT, 10);

        if ($this->stricton)
        {
            $mysqli->options(MYSQLI_INIT_COMMAND, 'SET SESSION sql_mode="STRICT_ALL_TABLES"');
        }

        return $mysqli->real_connect($hostname, $this->username, $this->password, $this->database, $port, $socket, $client_flags)
            ? $mysqli : FALSE;
}

整個(gè)數(shù)據(jù)庫(kù)連接的過(guò)程就分析結(jié)束了, 數(shù)據(jù)庫(kù)連上后蹄皱,接下來(lái)就是和操作數(shù)據(jù)庫(kù)了览闰,先看下原生的 sql 語(yǔ)句是怎么被處理的。


原生 sql 語(yǔ)句處理

CI框架處理原生 sql 查詢(xún)的方法是 DB_driver.php 中的query方法巷折,在該方法中你會(huì)看到對(duì) sql 處理大致如下

首先判斷 sql 的行為焕济,原生的 sql 有兩種行為,要么是 read盔几,要么是 write晴弃,所以處理這兩種行為的返回結(jié)果是不一樣的,前者返回結(jié)果集逊拍,后者返回操作狀態(tài)上鞠!

對(duì)于 read 行為并且啟用了查詢(xún)緩存,則嘗試從緩存中讀取結(jié)果芯丧;如果沒(méi)有開(kāi)啟緩存芍阎,則通過(guò) simple_query() 獲取結(jié)果集對(duì)象并掛在 result_id 上,當(dāng)結(jié)果集對(duì)象獲取以后會(huì)扔給相應(yīng)驅(qū)動(dòng)的 DB_result 對(duì)象做一層數(shù)據(jù)訪問(wèn)的封裝缨恒,我們使用的 result_array()谴咸,row_array() 等獲取查詢(xún)結(jié)果的函數(shù)就是在 DB_result 中封裝的轮听!

最后將結(jié)果集對(duì)象(DB_result) 返回。

同時(shí)也會(huì)看到一些細(xì)節(jié)化的處理岭佳,比如

sql 執(zhí)行失敗事務(wù)該怎么辦血巍?
基準(zhǔn)測(cè)試的埋點(diǎn)是怎么埋的?
為什么 last_query() 能夠獲取到最近執(zhí)行的一條 sql?

那現(xiàn)在進(jìn)入 query 方法看下具體實(shí)現(xiàn)珊随。

public function query($sql, $binds = FALSE, $return_object = NULL)
    {
        //sql檢查述寡,判斷是否為空,并且獲取sql的操作類(lèi)型 read 還是 write
        if ($sql === '')
        {
            log_message('error', 'Invalid query: '.$sql);
            return ($this->db_debug) ? $this->display_error('db_invalid_query') : FALSE;
        }
        elseif ( ! is_bool($return_object))
        {
            $return_object = ! $this->is_write_type($sql);
        }

        // Verify table prefix and replace if necessary
        if ($this->dbprefix !== '' && $this->swap_pre !== '' && $this->dbprefix !== $this->swap_pre)
        {
            $sql = preg_replace('/(\W)'.$this->swap_pre.'(\S+?)/', '\\1'.$this->dbprefix.'\\2', $sql);
        }

        // 解析參數(shù)綁定
        if ($binds !== FALSE)
        {
            $sql = $this->compile_binds($sql, $binds);
        }

        // 如果是讀類(lèi)型的sql叶洞,則先嘗試從緩存中讀取
        if ($this->cache_on === TRUE && $return_object === TRUE && $this->_cache_init())
        {
            $this->load_rdriver();
            if (FALSE !== ($cache = $this->CACHE->read($sql)))
            {
                return $cache;
            }
        }

        // 記錄執(zhí)行過(guò)的sql鲫凶,last_query()就是從該數(shù)組中獲得最近一條sql的
        if ($this->save_queries === TRUE)
        {
            $this->queries[] = $sql;
        }

        $time_start = microtime(TRUE);

        //=================================================================================
         //注意:這段邏輯有點(diǎn)復(fù)雜
         /*
         * 執(zhí)行sql,將結(jié)果集對(duì)象掛到result_id(result_id 會(huì)用在 DB_result 中封裝一層獲取數(shù)據(jù)的方法);
         * 如果開(kāi)啟了事務(wù)但是執(zhí)行失敗衩辟,則回滾事務(wù)螟炫,由于事務(wù)存在嵌套的可能,_trans_depth 字段就是嵌套的層級(jí)艺晴,通過(guò) _trans_depth
         * 一層一層的回滾事務(wù)不恭;最后打印出sql執(zhí)行出錯(cuò)的原因。
         * */
        if (FALSE === ($this->result_id = $this->simple_query($sql)))
        {
            if ($this->save_queries === TRUE)
            {
                $this->query_times[] = 0;
            }

            // This will trigger a rollback if transactions are being used
            $this->_trans_status = FALSE;

            // Grab the error now, as we might run some additional queries before displaying the error
            $error = $this->error();

            // Log errors
            log_message('error', 'Query error: '.$error['message'].' - Invalid query: '.$sql);

            if ($this->db_debug)
            {
                // We call this function in order to roll-back queries
                // if transactions are enabled. If we don't call this here
                // the error message will trigger an exit, causing the
                // transactions to remain in limbo.
                if ($this->_trans_depth !== 0)
                {
                    do
                    {
                        $this->trans_complete();
                    }
                    while ($this->_trans_depth !== 0);
                }

                // Display errors
                return $this->display_error(array('Error Number: '.$error['code'], $error['message'], $sql));
            }

            return FALSE;
        }
        //=============================================================================================

        // Stop and aggregate the query time results
        $time_end = microtime(TRUE);
        $this->benchmark += $time_end - $time_start;

        if ($this->save_queries === TRUE)
        {
            $this->query_times[] = $time_end - $time_start;
        }

        // Increment the query counter
        $this->query_count++;

        // 如果讀類(lèi)型的 sql 并不要求返回結(jié)果财饥,那么就要重置該 sql 對(duì)應(yīng)的緩存
        if ($return_object !== TRUE)
        {
            if ($this->cache_on === TRUE && $this->cache_autodel === TRUE && $this->_cache_init())
            {
                $this->CACHE->delete();
            }

            return TRUE;
        }

        // 之前我們看到結(jié)果集對(duì)象是掛在 result_id 上换吧,通過(guò)load_rdriver加載相應(yīng)驅(qū)動(dòng) DB_result后,就可以將 $this 傳給它钥星,
        //在其內(nèi)部獲取 result_id 后沾瓦,就可以封裝出一層結(jié)果訪問(wèn)函數(shù)了,如 row_array()谦炒,result_array() 等等
        $driver     = $this->load_rdriver();
        $RES        = new $driver($this);

        //將結(jié)果集對(duì)象寫(xiě)入到緩存中贯莺,這里你可能會(huì)有疑問(wèn),$RES 已經(jīng)是結(jié)果集對(duì)象了宁改,為什么還要 new 一個(gè)結(jié)果集對(duì)象出來(lái)缕探?
        //那是因?yàn)椋?dāng)腳本執(zhí)行完成后結(jié)果集要釋放了还蹲,但如果緩存是引用了某個(gè)結(jié)果集(也就是result_id)爹耗,勢(shì)必會(huì)導(dǎo)致
        //該result_id無(wú)法釋放,進(jìn)而占用內(nèi)存資源
        if ($this->cache_on === TRUE && $this->_cache_init())
        {
            $CR = new CI_DB_result($this);
            $CR->result_object  = $RES->result_object();
            $CR->result_array   = $RES->result_array();
            $CR->num_rows       = $RES->num_rows();

            // Reset these since cached objects can not utilize resource IDs.
            $CR->conn_id        = NULL;
            $CR->result_id      = NULL;

            $this->CACHE->write($sql, $CR);
        }
        
        //最后返回該結(jié)果集對(duì)象
        return $RES;
    }

load_rdriver()如下

 public function load_rdriver()
    {
        $driver = 'CI_DB_'.$this->dbdriver.'_result';
        
        //判斷相應(yīng)的 driver是否已經(jīng)被加載谜喊,如果沒(méi)有就需要加載進(jìn)來(lái)
        if ( ! class_exists($driver, FALSE))
        {
            require_once(BASEPATH.'database/DB_result.php');
            require_once(BASEPATH.'database/drivers/'.$this->dbdriver.'/'.$this->dbdriver.'_result.php');
        }

        return $driver;
    }

我們說(shuō)了獲取結(jié)果集對(duì)象(result_id)后潭兽,會(huì)將該對(duì)象扔給 DB_result 對(duì)象封裝出一層數(shù)據(jù)訪問(wèn)的方法,例如 row_array()斗遏,result_array() 等等山卦,那么現(xiàn)在進(jìn)入 DB_result 中看看是不是這樣。

public function __construct(&$driver_object)
    {
        $this->conn_id = $driver_object->conn_id;
        $this->result_id = $driver_object->result_id;
    }

我們看到在構(gòu)造方法中真的獲取了結(jié)果集對(duì)象(result_id)诵次,看到 $driver_object 就是 query() 中 new driver($this) 時(shí)的數(shù)據(jù)庫(kù)對(duì)象账蓉,可以看出整個(gè)數(shù)據(jù)庫(kù)驅(qū)動(dòng)的架構(gòu)中責(zé)任劃分的還是很清楚的枚碗。

接著往下翻,我們看到了 result_object()

public function result_object()
    {
        //如果result_object還在內(nèi)存中铸本,就直接返回肮雨,意味著做了相同的查詢(xún)
        if (count($this->result_object) > 0)
        {
            return $this->result_object;
        }

        if ( ! $this->result_id OR $this->num_rows === 0)
        {
            return array();
        }
        
        // 如果 result_array 已經(jīng)在內(nèi)存中了,由于數(shù)組和對(duì)象的區(qū)別不是很大归敬,直接通過(guò)array生成對(duì)象
        if (($c = count($this->result_array)) > 0)
        {
            for ($i = 0; $i < $c; $i++)
            {
                $this->result_object[$i] = (object) $this->result_array[$i];
            }

            return $this->result_object;
        }
        
        //如果內(nèi)存中中沒(méi)有我們要獲取的數(shù)據(jù),那就調(diào)用_fetch_object()獲取結(jié)果鄙早,
        //_fetch_object()是在各個(gè)驅(qū)動(dòng)器中實(shí)現(xiàn)的汪茧,因?yàn)槊總€(gè)驅(qū)動(dòng)器的結(jié)果集對(duì)象(result_id)不一樣
        is_null($this->row_data) OR $this->data_seek(0);
        while ($row = $this->_fetch_object())
        {
            $this->result_object[] = $row;
        }

        return $this->result_object;
    }

在該方法內(nèi)部我們會(huì)看到其通過(guò) _fetch_object() 獲取了 sql 查詢(xún)結(jié)果,而這個(gè)結(jié)果恰恰是通過(guò)結(jié)果集對(duì)象(result_id)獲得的限番。

而 _fetch_object 其實(shí)就是對(duì)結(jié)果集對(duì)象(result_id)的包裝

protected function _fetch_object($class_name = 'stdClass')
{
      return $this->result_id->fetch_object($class_name);
}

原生 sql 查詢(xún)的源碼分析就到此結(jié)束了舱污,同時(shí)我們?cè)诜治?query() 方法的過(guò)程中看到了緩存的處理,那么現(xiàn)在也就能解答一開(kāi)始我們關(guān)于緩存的疑問(wèn)了弥虐。


查詢(xún)緩存緩存的是sql還是結(jié)果集扩灯?

答:是結(jié)果集對(duì)象,結(jié)果集對(duì)象上持有了查詢(xún)結(jié)果霜瘪,而結(jié)果集對(duì)象被序列化后寫(xiě)入到以sql為文件名的文件中去的珠插!

我們?cè)?query() 看到結(jié)果集對(duì)象是通過(guò) DB_cache 對(duì)象的 write() 方法寫(xiě)入緩存中的,具體的處理過(guò)程如下:

public function write($sql, $object)
{
        //可以看到緩存文件名是控制器颖对,方法捻撑,以及md5后的sql組成的
        $segment_one = ($this->CI->uri->segment(1) == FALSE) ? 'default' : $this->CI->uri->segment(1);
        $segment_two = ($this->CI->uri->segment(2) == FALSE) ? 'index' : $this->CI->uri->segment(2);
        $dir_path = $this->db->cachedir.$segment_one.'+'.$segment_two.'/';
        $filename = md5($sql);

        if ( ! is_dir($dir_path) && ! @mkdir($dir_path, 0750))
        {
            return FALSE;
        }
        
        //看到?jīng)],結(jié)果集對(duì)象被序列化后寫(xiě)入到了緩存文件中去了
        if (write_file($dir_path.$filename, serialize($object)) === FALSE)
        {
            return FALSE;
        }

        chmod($dir_path.$filename, 0640);
        return TRUE;
}


我們知道 CI 框架還提供了一種讓我們操作數(shù)據(jù)庫(kù)的功能:查詢(xún)構(gòu)造器缤底,查詢(xún)構(gòu)造器對(duì)于不想寫(xiě)原生 sql 的人來(lái)說(shuō)是很方便的顾患,同時(shí)還能避免sql注入,但是高層的方便意味著底層的復(fù)雜个唧,下節(jié)我們看下查詢(xún)構(gòu)造器是怎么封裝的江解!


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市徙歼,隨后出現(xiàn)的幾起案子犁河,更是在濱河造成了極大的恐慌,老刑警劉巖魄梯,帶你破解...
    沈念sama閱讀 218,941評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件呼股,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡画恰,警方通過(guò)查閱死者的電腦和手機(jī)彭谁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)允扇,“玉大人缠局,你說(shuō)我怎么就攤上這事则奥。” “怎么了狭园?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,345評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵读处,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我唱矛,道長(zhǎng)罚舱,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,851評(píng)論 1 295
  • 正文 為了忘掉前任绎谦,我火速辦了婚禮管闷,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘窃肠。我一直安慰自己包个,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布冤留。 她就那樣靜靜地躺著碧囊,像睡著了一般。 火紅的嫁衣襯著肌膚如雪纤怒。 梳的紋絲不亂的頭發(fā)上糯而,一...
    開(kāi)封第一講書(shū)人閱讀 51,688評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音泊窘,去河邊找鬼歧蒋。 笑死,一個(gè)胖子當(dāng)著我的面吹牛州既,可吹牛的內(nèi)容都是我干的谜洽。 我是一名探鬼主播,決...
    沈念sama閱讀 40,414評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼吴叶,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼阐虚!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起蚌卤,我...
    開(kāi)封第一講書(shū)人閱讀 39,319評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤实束,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后逊彭,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體咸灿,經(jīng)...
    沈念sama閱讀 45,775評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年侮叮,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了避矢。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,096評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖审胸,靈堂內(nèi)的尸體忽然破棺而出亥宿,到底是詐尸還是另有隱情,我是刑警寧澤砂沛,帶...
    沈念sama閱讀 35,789評(píng)論 5 346
  • 正文 年R本政府宣布烫扼,位于F島的核電站,受9級(jí)特大地震影響碍庵,放射性物質(zhì)發(fā)生泄漏映企。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評(píng)論 3 331
  • 文/蒙蒙 一静浴、第九天 我趴在偏房一處隱蔽的房頂上張望堰氓。 院中可真熱鬧,春花似錦马绝、人聲如沸豆赏。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,993評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至白胀,卻和暖如春椭赋,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背或杠。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,107評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工哪怔, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人向抢。 一個(gè)月前我還...
    沈念sama閱讀 48,308評(píng)論 3 372
  • 正文 我出身青樓认境,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親挟鸠。 傳聞我的和親對(duì)象是個(gè)殘疾皇子叉信,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容