Thinkphp 第九章:性能和安全

本章的內(nèi)容主要講解了如何給數(shù)據(jù)庫的CURD查詢添加回調(diào)事件仆百,以及如何在最底層的SQL層面進行監(jiān)聽和做出性能分析及對查詢性能做出優(yōu)化建議心俗,最后給出了一些安全方面的建議吠勘,學習內(nèi)容主要從性能分析和優(yōu)化逸寓,以及安全三個方面進行講解:

性能分析

除了一些糟糕的業(yè)務(wù)邏輯若治,框架的性能瓶頸一般都是在數(shù)據(jù)庫(其它方面的性能沒什么好糾結(jié)的)慨蓝。業(yè)務(wù)邏輯的優(yōu)化暫時不在本書的討論范疇感混,我們首先來學習如何進行數(shù)據(jù)庫的性能分析。

數(shù)據(jù)庫調(diào)試模式

和應(yīng)用的調(diào)試模式不同礼烈,數(shù)據(jù)庫有自己獨立的調(diào)試模式開關(guān)弧满,在第一章我們已經(jīng)提過,數(shù)據(jù)庫配置參數(shù)中的debug參數(shù)就是數(shù)據(jù)庫調(diào)試模式的開關(guān)此熬。

// 數(shù)據(jù)庫調(diào)試模式
'debug'           => true,

數(shù)據(jù)庫調(diào)試模式開啟后庭呜,可以支持下列行為:

  • 記錄SQL日志;
  • 分析SQL性能犀忱;
  • 支持SQL監(jiān)聽募谎;

由于上述行為不可避免會產(chǎn)生額外的開銷,因此對性能存在一定的影響峡碉,但并不大近哟,因為所有的日志是最終統(tǒng)一一次性寫入,而且可以設(shè)置為某個用戶才寫入日志鲫寄。

在生產(chǎn)模式下面吉执,必須關(guān)閉應(yīng)用調(diào)試模式(app_debug),否則會暴露你的服務(wù)器敏感信息地来。和應(yīng)用調(diào)試模式不同戳玫,開啟數(shù)據(jù)庫調(diào)試模式并不會對外暴露任何安全信息,因此是否開啟數(shù)據(jù)庫調(diào)試模式未斑,看自己的需求咕宿。

獲取查詢次數(shù)

使用Db::getQueryTimes()方法可以獲取當前的數(shù)據(jù)庫查詢次數(shù),如果使用true作為參數(shù)的話可以獲取包括寫操作在內(nèi)的查詢次數(shù)蜡秽。

// 獲取讀操作次數(shù)
$read = Db::getQueryTimes();
// 獲取所有的查詢次數(shù)
$count = Db::getQueryTimes(true);

如果開啟了頁面Trace顯示的話府阀,可以直觀的看到當前請求的查詢信息。

image

調(diào)用存儲過程會被認為是執(zhí)行一次查詢操作而非寫操作芽突,盡管存儲過程內(nèi)部可能會有寫入操作试浙。

獲取SQL

可以用getLastsql方法獲取最后一次執(zhí)行的SQL語句,無論是使用Db類還是模型類寞蚌,所以下面的方式都是有效的:

Db::name('user')->where('id', '>', 0)->select();
echo Db::getLastSql();
$user = User::get(1);
echo $user->getLastsql();

getLastSql方法即使關(guān)閉數(shù)據(jù)庫調(diào)試模式一樣有效

如果使用了文件類型記錄日志田巴,并且開啟了數(shù)據(jù)庫調(diào)試模式的話,在日志文件中可以看到所有的SQL歷史記錄挟秤。

開啟性能分析

框架不但能記錄SQL日志壹哺,而且可以對查詢的SQL語句作出性能分析,幫助你快速找出數(shù)據(jù)庫性能瓶頸艘刚。

確保在數(shù)據(jù)庫配置文件中開啟下面兩個參數(shù):

        // 開啟數(shù)據(jù)庫調(diào)試模式
        'debug'           => true,
        // 開啟SQL性能分析
        'sql_explain'     => true,

開啟sql_explain參數(shù)后管宵,會對查詢的SQLEXPLAIN解析(由每個連接器類的getExplain方法完成查詢SQL分析),并把解析結(jié)果合并記錄到SQL日志中(注意:目前僅對Mysql數(shù)據(jù)庫有效)。

下面是一個查詢的分析日志例子:

[ SQL ] SELECT * FROM `user` WHERE `id` IN (2) [ RunTime:0.000703s ]
[ EXPLAIN : array ( 'id' => 1, 'select_type' => 'SIMPLE', 'table' => 'think_user', 'partitions' => NULL, 'type' => 'system', 'possible_keys' => 'PRIMARY', 'key' => NULL, 'key_len' => NULL, 'ref' => NULL, 'rows' => 1, 'filtered' => 100.0, 'extra' => NULL, ) ]

SQL日志中會記錄每個SQL的執(zhí)行時間以及EXPLAIN分析結(jié)果箩朴,框架只是記錄分析結(jié)果笛臣,至于如何查出問題和解決則需要你具備一定的SQL性能分析和優(yōu)化知識。

EXPLAIN分析結(jié)果中的extra中使用了filesort或者temporary的話隧饼,系統(tǒng)會額外記錄一個警告錯誤告訴我們某條SQL存在性能問題需要處理。

SQL監(jiān)聽

如果覺得內(nèi)置的性能分析不夠全面静陈,完全可以對執(zhí)行的SQL進行監(jiān)聽并且對接第三方的SQL分析類庫燕雁。使用listen方法注冊SQL監(jiān)聽,例如可以在應(yīng)用公共文件或者某個行為擴展中添加如下代碼:

Db::listen(function ($sql, $time, $explain) {
    // 記錄SQL
    Log::record($sql . ' [' . $time . 's]', 'sql');
    // 查看性能分析結(jié)果
    dump($explain);
});

如果關(guān)閉了sql_explain參數(shù)鲸拥,explain參數(shù)就是一個空數(shù)組拐格,你可以在監(jiān)聽方法中自行分析SQL性能問題。

監(jiān)聽的閉包方法支持傳入三個參數(shù)刑赶,分別是:SQL語句捏浊、執(zhí)行時間(秒)和性能分析結(jié)果(數(shù)組),并注意如下事項:

  • 如果注冊了多個SQL監(jiān)聽方法撞叨,則會依次調(diào)用金踪;
  • 一旦注冊了SQL監(jiān)聽,則SQL日志和分析日志自動無效牵敷,由監(jiān)聽方法接管胡岔;

性能優(yōu)化

現(xiàn)在我們已經(jīng)基本掌握了性能分析的手段,那么如何進行性能優(yōu)化(本書中的優(yōu)化范疇主要是數(shù)據(jù)庫操作層面的)就是擺在開發(fā)人員面前的一件棘手大事枷餐,如果是一般的應(yīng)用可能主要做好數(shù)據(jù)表的索引就基本上沒什么大的性能問題靶瘸,對于大流量及高并發(fā)的應(yīng)用,優(yōu)化的手段和空間就比較多毛肋,因為這個情況下任何一個細小的優(yōu)化都能帶來可觀的性能改進怨咪。

SQL優(yōu)化

這里說的SQL優(yōu)化主要針對數(shù)據(jù)庫層面的優(yōu)化,對于Mysql數(shù)據(jù)庫來說润匙,下面是一些比較常規(guī)的建議:

  • 盡量少用SQL函數(shù)(會減少數(shù)據(jù)庫自身查詢緩存的命中率)而是用PHP變量傳入诗眨;
  • 給常用的查詢字段建立索引或者聯(lián)合索引;
  • 對JOIN的條件字段建立索引趁桃,并且采用相同的數(shù)據(jù)類型(包括字符集)辽话;
  • 避免使用ORDER BY RAND()
  • 盡量調(diào)用field方法顯式列出查詢的字段卫病,即使用field(true) 油啤;
  • 養(yǎng)成給數(shù)據(jù)表設(shè)置自增主鍵的習慣;
  • 合理設(shè)計你的數(shù)據(jù)表字段類型蟀苛;
  • 對于大數(shù)據(jù)表使用垂直分表把數(shù)據(jù)表分為固定長度和不定長的兩個表益咬;

更深層次的優(yōu)化可以對Mysql的配置參數(shù)進行優(yōu)化配置(沒有一勞永逸的配置優(yōu)化,一定是針對應(yīng)用場景的)帜平,相信大部分應(yīng)用暫時還不需要到優(yōu)化配置的地步幽告,首先考慮的還是架構(gòu)設(shè)計的優(yōu)化梅鹦,數(shù)據(jù)庫配置的優(yōu)化策略對應(yīng)用的部署遷移會造成額外的成本以及不可預(yù)知的問題,如果你不是一個DBA角色不建議頻繁調(diào)整配置參數(shù)冗锁。

字段緩存

說完了數(shù)據(jù)庫層面的優(yōu)化齐唆,我們后面著重來說下框架和應(yīng)用層面的優(yōu)化。

為了更安全的進行數(shù)據(jù)庫操作冻河,框架底層在查詢數(shù)據(jù)表數(shù)據(jù)的時候箍邮,會首先獲取該數(shù)據(jù)表的字段信息,包括字段名稱叨叙、字段類型以及主鍵名锭弊,對于不在字段列表中的字段則會進行忽略處理甚至拋出異常,字段類型則用于進行寫入和查詢的自動參數(shù)綁定擂错,雖然說每個數(shù)據(jù)表只會獲取一次字段信息味滞,但每次請求都要重新獲取一次不免覺得有點性能浪費。不過在開發(fā)階段钮呀,如果經(jīng)常會涉及到字段信息的變化剑鞍,還是無所謂,但如果已經(jīng)部署上線了的話行楞,還是建議使用字段緩存攒暇,也可以有效提高查詢性能,我們會在頁面Trace的SQL欄中看到類似的信息

[ SQL ] SHOW COLUMNS FROM `user` [ RunTime:0.001582s ]

其實就是查詢數(shù)據(jù)表user的字段信息的SQL語句(不同的數(shù)據(jù)庫查詢字段信息的SQL語句是不同的子房,由連接器類的getFields方法完成查詢)形用。

部署上線后,可以在命令行下執(zhí)行以下指令生成字段緩存证杭,在命令行切換到應(yīng)用的根目錄(think文件所在目錄)田度,輸入:

php think optimize:schema

會自動生成當前數(shù)據(jù)庫配置文件中定義的數(shù)據(jù)表字段緩存,執(zhí)行后會自動在runtime/schema目錄下面按照數(shù)據(jù)表生成字段緩存文件解愤,緩存文件的命名格式為:

數(shù)據(jù)庫名.數(shù)據(jù)表名.php

如果你的應(yīng)用有多個數(shù)據(jù)庫的操作镇饺,也可以指定數(shù)據(jù)庫生成字段緩存(必須有用戶權(quán)限),例如送讲,下面用--db參數(shù)指定生成demo數(shù)據(jù)庫下面的所有數(shù)據(jù)表的字段緩存信息奸笤。

php think optimize:schema --db demo

如果你的應(yīng)用不同的模塊使用了不同的數(shù)據(jù)庫連接,還可以根據(jù)模塊來生成哼鬓,用--module參數(shù)指定模塊如下:

php think optimize:schema --module index

會讀取index模塊的模型來生成數(shù)據(jù)表字段緩存监右,沒有繼承think\Model類的模型和抽象類不會生成。

每次執(zhí)行指令都會重新生成數(shù)據(jù)表字段緩存文件异希,如果只是更改了數(shù)據(jù)表的某個字段或者增加了新的字段健盒,重新部署上線的時候,支持單獨更新某個數(shù)據(jù)表的緩存。

使用 --table參數(shù)指定需要更新的數(shù)據(jù)表:

php think optimize:schema --table user

支持指定數(shù)據(jù)庫名稱

php think optimize:schema --table demo.think_user

生成字段緩存后扣癣,你會發(fā)現(xiàn)數(shù)據(jù)庫的查詢性能提升明顯惰帽,尤其是在請求中操作大量數(shù)據(jù)表的情況下。

數(shù)據(jù)緩存

數(shù)據(jù)庫的優(yōu)化手段有時候比不過架構(gòu)和緩存的設(shè)計優(yōu)化父虑,而架構(gòu)的優(yōu)化是一個綜合的范疇该酗,需要針對具體的邏輯和場景,并且優(yōu)化的手段通常多元化士嚎,模型關(guān)聯(lián)的設(shè)計也是底層提供的架構(gòu)設(shè)計的優(yōu)化手段之一(使用預(yù)載入查詢可以有效減少數(shù)據(jù)庫查詢次數(shù))垂涯,現(xiàn)在我們要講的是如何利用數(shù)據(jù)緩存策略來減少數(shù)據(jù)庫的查詢開銷,這是一個不依賴數(shù)據(jù)庫的普適優(yōu)化策略航邢。

數(shù)據(jù)庫的數(shù)據(jù)緩存并不是你理解的直接使用Cache類進行操作,那樣太麻煩了骄蝇,每次都要手動設(shè)置及額外讀取膳殷,也許像下面這樣:

$user = Cache::get('user_cache');
if (!$user) {
    $user = Db::table('user')
        ->where('id', 10)
        ->find();
    Cache::set('user_cache', $user);
}

查詢類封裝了一個數(shù)據(jù)緩存的鏈式方法cache,可以很方便的進行查詢數(shù)據(jù)的自動緩存和讀取九火,以及緩存數(shù)據(jù)的自動更新赚窃。數(shù)據(jù)庫的緩存策略主要就是掌握cache鏈式方法的使用,下面我們仔細給你講解下用法岔激。

先給出一個最簡單的用法:

Db::table('user')
    ->cache(600)
    ->where('id', 10)
    ->find();

Db::table('user')
    ->where('status', 1)
    ->cache(600)
    ->count();

可以對find勒极、selectvaluecolumn方法及其衍生方法使用數(shù)據(jù)緩存功能虑鼎,不支持原生查詢query方法辱匿。

cache方法如果傳入數(shù)字,表示查詢數(shù)據(jù)的緩存時間(秒)炫彩,所以上面的查詢在10分鐘以內(nèi)多次調(diào)用的話不會重復(fù)查詢數(shù)據(jù)庫匾七,而是直接讀取緩存數(shù)據(jù)(使用當前配置的緩存類型和緩存參數(shù))。

如果需要在外部調(diào)用緩存數(shù)據(jù)(盡管并不常見江兢,但在跨模塊的時候可能會需要)昨忆,可以指定緩存標識,例如:

Db::table('user')
    ->cache('user_cache_key', 600)
    ->where('id', 10)
    ->find();

cache方法的第一個參數(shù)使用字符串表示緩存標識杉允,這個時候第二個參數(shù)就表示緩存有效期邑贴,然后可以在外部調(diào)用緩存的用戶數(shù)據(jù):

// 緩存數(shù)據(jù)有效期為10分鐘
$userData = Cache::get('user_cache_key');

內(nèi)置的數(shù)據(jù)緩存策略對原生查詢不起作用(只能單獨使用緩存方法來進行緩存),相比緩存的優(yōu)勢用原生查詢的那點性能優(yōu)越感這個時候已經(jīng)蕩然無存了叔磷,查詢構(gòu)造器的優(yōu)勢就很明顯了拢驾。

數(shù)據(jù)緩存策略的關(guān)鍵是如何及時更新緩存數(shù)據(jù),我們來看下如何做到自動更新緩存世澜,下面的內(nèi)容才是數(shù)據(jù)緩存要講的關(guān)鍵独旷。

只需要在調(diào)用更新或者刪除方法之前調(diào)用cache方法(見證奇跡的時刻到了):

Db::table('user')
    ->cache('user_data')
    ->select([1, 3, 5]);

Db::table('user')
    ->cache('user_data')
    ->update(['id' => 1, 'name' => 'thinkphp']);

Db::table('user')
    ->cache('user_data')
    ->select([1, 3, 5]);

在更新數(shù)據(jù)的時候調(diào)用cache手動清除緩存,所以最后查詢的數(shù)據(jù)不會受第一條查詢緩存的影響,查詢出來的數(shù)據(jù)依然是同步更新后的數(shù)據(jù)嵌洼。

同樣案疲,如果進行了刪除操作,也會自動清除緩存數(shù)據(jù)麻养。

Db::table('user')
    ->cache('user_data')
    ->select([1, 3, 5]);

Db::table('user')
    ->cache('user_data')
    ->delete(1);

Db::table('user')
    ->cache('user_data')
    ->select([1, 3, 5]);

確保查詢和更新或者刪除使用相同的緩存標識才能自動清除緩存褐啡。

比較常用的數(shù)據(jù)緩存是以主鍵為查詢條件的單個數(shù)據(jù)的緩存,所以如果使用find方法并且使用主鍵查詢的情況鳖昌,緩存更新更智能备畦。update或者delete方法可以不需要調(diào)用cache方法,也會自動清理緩存许昨,例如:

Db::table('user')
    ->cache(true)
    ->find(1);

Db::table('user')
    ->update(['id' => 1, 'name' => 'topthink']);

Db::table('user')
    ->cache(true)
    ->find(1);

根據(jù)主鍵查詢的話懂盐,緩存更新是自動的,因此上面的例子最后查詢的數(shù)據(jù)會是更新后的數(shù)據(jù)糕档。

使用where方法查詢主鍵條件的話莉恼,效果一樣:

Db::table('user')
    ->cache(true)
    ->where('id', 1)
    ->find();

Db::table('user')
    ->where('id', 1)
    ->update(['name' => 'topthink']);

Db::table('user')
    ->cache(true)
    ->where('id', 1)
    ->find();

模型緩存

除了使用Db類,模型類還提供了更方便的方法進行數(shù)據(jù)緩存速那。如果是緩存讀取單個數(shù)據(jù)俐银,可以使用:

// 查詢數(shù)據(jù)并緩存讀取
$user = User::get(1, [], true);
// 設(shè)置緩存有效期
$user = User::get(1, [], 600);

由于第二個參數(shù)是預(yù)載入查詢,所以查詢緩存屈居二線了_端仰,不過如果你的版本在5.0.6以上的話捶惜,可以直接寫成:

// 查詢數(shù)據(jù)并緩存讀取
$user = User::get(1, true);
// 設(shè)置緩存有效期
$user = User::get(1, 600);

當使用主鍵查詢、更新和刪除模型數(shù)據(jù)的時候荔烧,會自動更新模型數(shù)據(jù)緩存吱七。如果你的查詢條件不是主鍵,可以指定緩存標識鹤竭,并在刪除的時候帶上緩存標識陪捷,例如:

// 查詢name為thinkphp的用戶數(shù)據(jù)并緩存讀取
$user = User::cache('user_key_thinkphp')
    ->getByName('thinkphp');
// 刪除數(shù)據(jù)并更新緩存數(shù)據(jù)
$user->cache('user_key_thinkphp')
    ->delete();

模型數(shù)據(jù)緩存標識不能直接在外部讀取,因為緩存的數(shù)據(jù)都是數(shù)組而不是對象诺擅,所以下面才是正確的姿勢市袖。

// 查詢name為thinkphp的用戶數(shù)據(jù)并緩存讀取
$user = User::cache('user_key_thinkphp')
    ->getByName('thinkphp');
// 外部讀取模型數(shù)據(jù)緩存
$data = new User(Cache::get('user_key_thinkphp'));

同樣的用法,如果要緩存讀取多個數(shù)據(jù)烁涌,使用下面的方式:

// 查詢多個數(shù)據(jù)并緩存讀取
$users = User::all([1, 2, 3], [], true);
// 設(shè)置緩存有效期
$users = User::all([1, 2, 3], [], 3600);

5.0.6版本以上同樣可以使用

// 查詢多個數(shù)據(jù)并緩存讀取
$users = User::all([1, 2, 3], true);
// 設(shè)置緩存有效期
$users = User::all([1, 2, 3], 3600);

模型的數(shù)據(jù)緩存配合關(guān)聯(lián)預(yù)載入查詢的話效果更佳苍碟,關(guān)于如何使用關(guān)聯(lián)預(yù)載入查詢請參考上一章的內(nèi)容。

查詢事件

使用查詢事件可以在不改變原有數(shù)據(jù)查詢代碼的前提下制定獨立的緩存策略撮执,先來了解下什么是查詢事件微峰。

查詢事件是針對數(shù)據(jù)庫的CURD操作而設(shè)計的回調(diào)方法,主要包括:

事件 描述
before_select select查詢前回調(diào)
before_find find查詢前回調(diào)
after_insert insert操作成功后回調(diào)
after_update update操作成功后回調(diào)
after_delete delete操作成功后回調(diào)

使用下面的方式注冊一個查詢事件

Db::event('before_select', function ($options, $query) {
    // 事件處理
});

如果before_select或者before_find回調(diào)方法有返回數(shù)據(jù)抒钱,則表示提前返回查詢結(jié)果蜓肆,不會繼續(xù)執(zhí)行查詢操作颜凯。

Db::event('before_find', function ($options, $query) {
    // 事件處理
    if ('user' == $options['table']) {
        $result = ['id' => 1, 'name' => 'thinkphp'];
        return $result;
    }
});

$user = Db::table('user')->find();

user變量最終的結(jié)果是['id'=>1,'name'=>'thinkphp']

下面的例子我們沒有使用cache方法進行數(shù)據(jù)緩存仗扬,而是利用查詢事件來定制自己的數(shù)據(jù)緩存策略症概。

// after_insert回調(diào)方法
Db::event('after_insert', function ($options, $query) {
    $pk   = $query->getPk($options);
    $guid = $options['table'] . '_' . $options['data'][$pk];
    Cache::set($guid, $options['data'], 0);
});

// after_update回調(diào)方法
Db::event('after_update', function ($options, $query) {
    $pk   = $query->getPk($options);
    $guid = $options['table'] . '_' . $options['data'][$pk];
    $data = Cache::get($guid);
    $data = array_merge($data, $options['data']);
    Cache::set($guid, $data, 0);
});

// after_delete回調(diào)方法
Db::event('after_delete', function ($options, $query) {
    $pk   = $query->getPk($options);
    $guid = $options['table'] . '_' . $options['data'][$pk];
    Cache::set($guid, null, 0);
});

// before_find回調(diào)方法
Db::event('before_find', function ($options, $query) {
    $pk   = $query->getPk($options);
    $guid = $options['table'] . '_' . $options['data'][$pk];
    $data = Cache::get($guid);
    if ($data) {
        return $data;
    }
});

注冊完查詢回調(diào)方法后,下面的查詢除了寫操作會執(zhí)行數(shù)據(jù)庫操作早芭,其它的查詢方法都直接讀取緩存數(shù)據(jù)彼城,而且始終保持最新的數(shù)據(jù)。

$id = Db::table('user')
    ->insert(['name'=>'thinkphp']);

Db::table('user')->find($id);

Db::table('user')
    ->where('id',$id)
    ->update(['name'=>'topthink']);

Db::table('user')->find($id);

Db::table('user')
    ->delete($id);

Db::table('user')->find($id);  

數(shù)據(jù)安全

安全和優(yōu)化就如同魚和熊掌一般退个,很難兼得募壕。從某種程度上說,數(shù)據(jù)安全比性能優(yōu)化更重要语盈,因此為了更加安全和穩(wěn)健運行舱馅,犧牲一定的性能都是值得的,下面我們來學習下基本的安全策略刀荒。

底層防護

5.0版本提供了更高的底層安全策略习柠,雖然不至于因此而高枕無憂,但也完全不必杞人憂天照棋,主要體現(xiàn)在:

  • WEB訪問目錄和應(yīng)用目錄隔離;
  • 內(nèi)置使用PDO預(yù)處理和自動參數(shù)綁定機制武翎;
  • 默認用戶提交數(shù)據(jù)不支持數(shù)組烈炭;
  • 支持數(shù)據(jù)自動過濾機制;

只要善于運用系統(tǒng)提供的安全手段和做好一些配置宝恶,可確保你的應(yīng)用安全無虞符隙,聽我給你細細道來。

寫入過濾

由于系統(tǒng)的安全機制垫毙,任何非數(shù)據(jù)表的字段如果要寫入數(shù)據(jù)庫都會導致異常霹疫,如果你不希望非數(shù)據(jù)表字段寫入數(shù)據(jù)庫的時候拋出異常,而只是忽略就行综芥,那么可以使用下面兩種方式丽蝎。

如果是僅僅當前操作忽略,則可以使用strict方法膀藐,例如:

Db::table('user')
    ->strict(false)
    ->insert([
        'name'     => 'thinkphp',
        'nickname' => '流年',
        'test'     => '測試數(shù)據(jù)',
    ]);

由于user表中并不存在test字段屠阻,因此test數(shù)據(jù)會被直接忽略,但由于使用了strict(false)方法额各,而不會拋出異常国觉。

如果希望全局不拋出異常,可以在數(shù)據(jù)庫配置文件中設(shè)置

        // 是否嚴格檢查字段是否存在
        'fields_strict'   => false,

但有些時候我們還需要限制寫入數(shù)據(jù)庫的字段虾啦,避免被用戶提交更新一些敏感數(shù)據(jù)麻诀,并非只有查詢的時候可以使用field方法指定字段列表痕寓,我們還可以在寫入數(shù)據(jù)的時候使用field方法限制字段寫入。

Db::table('user')
    ->field('name,nickname')
    ->where('id', 1)
    ->update([
        'name'     => 'thinkphp',
        'nickname' => '流年',
        'email'    => 'thinkphp@qq.com',
    ]);

上面的例子中蝇闭,由于我們用field方法限制了寫入的字段列表呻率,因此email數(shù)據(jù)不會被更新,而是直接忽略丁眼。

同樣筷凤,field方法也支持排除某些字段

Db::table('user')
    ->field('email,score', true)
    ->where('id', 1)
    ->update([
        'name'     => 'thinkphp',
        'nickname' => '流年',
        'email'    => 'thinkphp@qq.com',
    ]);

如果使用模型操作的話,我們還可以使用allowField方法提前對數(shù)據(jù)進行字段過濾

$user           = User::get(1);
$user->name     = 'thinkphp';
$user->nickname = '流年';
$user->email    = 'thinkphp@qq.com';
$user->allowField('name,nickname')
    ->save();

allowField過濾數(shù)據(jù)并不會導致異常苞七,和field方法不同藐守,allowField方法并不支持字段排除,如果調(diào)用allowField(true) 表示過濾數(shù)據(jù)表字段之外的數(shù)據(jù)

模型還額外提供了一個只讀字段的功能蹂风,針對某些字段只提供寫入功能而不提供更新功能卢厂,具體可以參考模型高級用法一章的內(nèi)容。

安全建議

為了讓你的應(yīng)用更安全惠啄,綜合之前提到的各種安全因素慎恒,在數(shù)據(jù)庫的層面我們給出如下安全建議:

  • 對用戶輸入的數(shù)據(jù)做盡可能的驗證;
  • 對寫入的數(shù)據(jù)做好過濾撵渡,避免異常融柬;
  • 避免直接使用用戶提交數(shù)據(jù)作為查詢條件;
  • 查詢字段名不應(yīng)該由表單或者用戶決定趋距;
  • 對于getfind方法的參數(shù)建議做好Null判斷粒氧;
  • 數(shù)據(jù)輸出的時候注意做好XSS安全過濾;
  • 對于模型數(shù)據(jù)盡量隱藏敏感數(shù)據(jù)后輸出节腐;
  • 對于業(yè)務(wù)數(shù)據(jù)的寫入操作應(yīng)當做好權(quán)限檢查外盯;
  • 寫入數(shù)據(jù)嚴格使用field方法限制寫入字段;

舉個例子翼雀,如果你開放查詢字段名給用戶提交而未作判斷直接作為查詢條件饱苟,例如下面的代碼:

$where = request()->param();
// 查詢用戶是否存在
$user = Db::table('user')
    ->where($where)
    ->find();

假設(shè)你的表單里面有一個name字段,那么狼渊,用戶就可以在瀏覽器構(gòu)造一個name|email字段完成OR查詢箱熬,查詢的結(jié)果可能完全不同了,極有可能造成邏輯漏洞狈邑。

正確的查詢方式應(yīng)該是:

// 查詢用戶是否存在
$user = Db::table('user')
    ->where('name',request()->param('name'))
    ->find();

總結(jié)

到目前為止坦弟,我們已經(jīng)完成了5.0的數(shù)據(jù)庫和模型的學習,最好的老師是實踐并把掌握的知識點融會貫通官地,在后面的附錄中我們會給大家匯總整理一些常見問題酿傍,并保持不斷更新。

感謝你堅持看完了本書的內(nèi)容驱入,您的建議是我們努力完善的動力赤炒,希望不吝賜教并隨時在本書的評論區(qū)或者github上給我們留言氯析,最后祝你在新的開發(fā)征程中所向披靡,因為我們的愿景就是讓開發(fā)變得更簡單莺褒!

上一篇:第八章:模型關(guān)聯(lián)
下一篇:附錄A:常見問題

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末掩缓,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子遵岩,更是在濱河造成了極大的恐慌你辣,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件尘执,死亡現(xiàn)場離奇詭異舍哄,居然都是意外死亡,警方通過查閱死者的電腦和手機誊锭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,347評論 3 385
  • 文/潘曉璐 我一進店門表悬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人丧靡,你說我怎么就攤上這事蟆沫。” “怎么了温治?”我有些...
    開封第一講書人閱讀 157,435評論 0 348
  • 文/不壞的土叔 我叫張陵饭庞,是天一觀的道長。 經(jīng)常有香客問我熬荆,道長舟山,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,509評論 1 284
  • 正文 為了忘掉前任惶看,我火速辦了婚禮,結(jié)果婚禮上六孵,老公的妹妹穿的比我還像新娘纬黎。我一直安慰自己,他們只是感情好劫窒,可當我...
    茶點故事閱讀 65,611評論 6 386
  • 文/花漫 我一把揭開白布本今。 她就那樣靜靜地躺著,像睡著了一般主巍。 火紅的嫁衣襯著肌膚如雪冠息。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,837評論 1 290
  • 那天孕索,我揣著相機與錄音逛艰,去河邊找鬼。 笑死搞旭,一個胖子當著我的面吹牛散怖,可吹牛的內(nèi)容都是我干的菇绵。 我是一名探鬼主播,決...
    沈念sama閱讀 38,987評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼镇眷,長吁一口氣:“原來是場噩夢啊……” “哼咬最!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起欠动,我...
    開封第一講書人閱讀 37,730評論 0 267
  • 序言:老撾萬榮一對情侶失蹤永乌,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后具伍,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體翅雏,經(jīng)...
    沈念sama閱讀 44,194評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,525評論 2 327
  • 正文 我和宋清朗相戀三年沿猜,在試婚紗的時候發(fā)現(xiàn)自己被綠了枚荣。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,664評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡啼肩,死狀恐怖橄妆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情祈坠,我是刑警寧澤害碾,帶...
    沈念sama閱讀 34,334評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站赦拘,受9級特大地震影響慌随,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜躺同,卻給世界環(huán)境...
    茶點故事閱讀 39,944評論 3 313
  • 文/蒙蒙 一阁猜、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蹋艺,春花似錦剃袍、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,764評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至涛救,卻和暖如春畏邢,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背检吆。 一陣腳步聲響...
    開封第一講書人閱讀 31,997評論 1 266
  • 我被黑心中介騙來泰國打工舒萎, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蹭沛。 一個月前我還...
    沈念sama閱讀 46,389評論 2 360
  • 正文 我出身青樓逆甜,卻偏偏與公主長得像虱肄,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子交煞,可洞房花燭夜當晚...
    茶點故事閱讀 43,554評論 2 349

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

  • Spark SQL, DataFrames and Datasets Guide Overview SQL Dat...
    草里有只羊閱讀 18,304評論 0 85
  • ORA-00001: 違反唯一約束條件 (.) 錯誤說明:當在唯一索引所對應(yīng)的列上鍵入重復(fù)值時咏窿,會觸發(fā)此異常。 O...
    我想起個好名字閱讀 5,256評論 0 9
  • 今天看到一位朋友寫的mysql筆記總結(jié)素征,覺得寫的很詳細很用心集嵌,這里轉(zhuǎn)載一下,供大家參考下御毅,也希望大家能關(guān)注他原文地...
    信仰與初衷閱讀 4,725評論 0 30
  • 特別說明: 1根欧、本文只是面對數(shù)據(jù)庫應(yīng)用開發(fā)的程序員,不適合專業(yè)DBA端蛆,DBA在數(shù)據(jù)庫性能優(yōu)化方面需要了解更多的知識...
    安易學車閱讀 1,805評論 0 40
  • --- layout: post title: "如果有人問你關(guān)系型數(shù)據(jù)庫的原理凤粗,叫他看這篇文章(轉(zhuǎn))" date...
    藍墜星閱讀 780評論 0 3