一、引言

在使用 Laravel 進(jìn)行開發(fā)的過程中,打印 SQL 語句是一項(xiàng)非常重要且實(shí)用的操作。它有著諸多應(yīng)用場景,比如當(dāng)我們使用 Query Builder 構(gòu)建查詢時,只有清楚地知道到底執(zhí)行了什么 SQL 語句,才能正確且高效地編寫 Query Builder。像 “熱加載” 和 “懶加載” 場景中,了解 SQL 的執(zhí)行情況,就能避免一些效率問題的產(chǎn)生。例如在熱加載場景下,像 $user = User::where('name', 'Eric')->with('articles')->first(); 這樣的語句,Query Builder 可能只使用類似 select * from users where name = 'Eric' limit 1; 以及 select * from articles where user_id in(22); 這樣兩條 SQL 語句,后續(xù)遍歷用戶文章時就無需再頻繁請求數(shù)據(jù)庫了。而與之相對的懶加載,如果不清楚底層 SQL 執(zhí)行情況,就可能出現(xiàn)明明可以少量次數(shù)完成的查詢,卻執(zhí)行了多次 SQL 查詢的 “N + 1” 問題,導(dǎo)致效率變低。另外,在處理一些較為復(fù)雜的業(yè)務(wù)場景,例如拼接多個 whereRaw 的 SQL 語句時,若程序執(zhí)行結(jié)果與預(yù)期不符,通過打印 SQL 語句,觀察其實(shí)際執(zhí)行的內(nèi)容,就能排查出是語句本身構(gòu)造問題,還是框架執(zhí)行層面出現(xiàn)了差錯。而且在調(diào)試自定義的參數(shù)綁定查詢時,比如根據(jù)經(jīng)緯度手動計算兩點(diǎn)之間的近距離這類涉及到自定義 SQL 語句和參數(shù)綁定的情況,將 SQL 語句打印出來,有助于確認(rèn)參數(shù)是否正確傳入、語句是否符合預(yù)期等,方便我們對接口執(zhí)行的查詢語句做具體分析,從而更好地完成開發(fā)與調(diào)試工作,確保項(xiàng)目順利進(jìn)行。總之,掌握 Laravel 中打印 SQL 語句的方法,能為我們開發(fā)帶來極大的便利呢。
二、Laravel 打印 SQL 語句的方法
(一)開啟執(zhí)行日志法
在 Laravel 中,一種常見的打印 SQL 語句的方法是開啟執(zhí)行日志。具體操作是使用 DB::connection()->enableQueryLog(); 來開啟日志記錄,然后執(zhí)行我們的數(shù)據(jù)庫查詢操作,比如 $result = DB::table('advert')->whereJsonContains('tag', "1")->get();,最后通過 dd(DB::getQueryLog()); 就能獲取到執(zhí)行的查詢語句以及相關(guān)的綁定參數(shù)和執(zhí)行時間等信息。這種方法的優(yōu)點(diǎn)在于它能完整地記錄下所有執(zhí)行過的 SQL 語句,對于我們排查一些復(fù)雜業(yè)務(wù)邏輯中 SQL 的執(zhí)行情況非常有幫助。例如在一個涉及多個關(guān)聯(lián)表查詢和條件篩選的功能模塊中,如果出現(xiàn)數(shù)據(jù)不準(zhǔn)確的問題,通過開啟執(zhí)行日志,我們可以清晰地看到每一步查詢的具體情況,從而快速定位是哪一個查詢出現(xiàn)了偏差。然而,它也有一定的局限性,開啟執(zhí)行日志會在一定程度上影響程序的性能,尤其是在生產(chǎn)環(huán)境中,如果大量使用這種方式來打印 SQL 語句,可能會導(dǎo)致系統(tǒng)響應(yīng)變慢。而且日志信息是存放在內(nèi)存中的,如果查詢語句較多或者數(shù)據(jù)量較大,可能會占用較多的內(nèi)存資源。所以這種方法比較適合在開發(fā)環(huán)境或者測試環(huán)境中,對一些關(guān)鍵的業(yè)務(wù)邏輯進(jìn)行 SQL 語句的排查和調(diào)試。
(二)toSql () 方法
另一種打印 SQL 語句的方法是使用 toSql() 方法。比如對于 $query = DB::table('users')->where('id', 10);,我們可以通過 $query->toSql(); 來獲取對應(yīng)的 SQL 語句。這種方法的優(yōu)勢在于它的使用非常簡便,能夠快速地獲取到我們構(gòu)建的查詢語句的大致樣子。不過它也存在一些不足,toSql() 方法獲取到的 SQL 語句中參數(shù)是用 ? 占位符表示的,并沒有顯示出實(shí)際的參數(shù)值。如果我們想要獲取完整的帶有實(shí)際參數(shù)值的 SQL 語句,還需要進(jìn)一步處理,例如 $sql = str_replace_array('?', $query->getBindings(), $query->toSql()); 這樣的操作來替換占位符,相對來說比較麻煩。而且這種方法只能獲取到當(dāng)前構(gòu)建的這一條 SQL 語句,如果在一個方法或者一個業(yè)務(wù)流程中存在多條 SQL 語句的執(zhí)行,使用 toSql() 方法就需要逐個去獲取,不如開啟執(zhí)行日志法那樣可以一次性獲取到所有執(zhí)行過的 SQL 語句。但在一些簡單的場景下,比如我們只是想快速查看一下某一條特定查詢語句的基本結(jié)構(gòu),toSql() 方法還是比較方便快捷的。
(三)推薦方法:在 AppServiceProvider 中處理
這里推薦一種更為實(shí)用和穩(wěn)定的方法,即在 AppServiceProvider 的 boot 方法中進(jìn)行處理。首先,我們打開 app/Providers/AppServiceProvider.php 文件,在 boot 方法中添加以下代碼:通過這種方式,我們可以將所有執(zhí)行的 SQL 語句以及完整的參數(shù)信息記錄到指定的日志文件中,比如 storage/logs/2024-12-25_query.log(日期會根據(jù)實(shí)際執(zhí)行日期生成)。這樣做的好處是,在開發(fā)和調(diào)試過程中,我們可以隨時查看這些日志文件,了解程序在運(yùn)行過程中到底執(zhí)行了哪些 SQL 語句,并且能夠清晰地看到完整的 SQL 語句內(nèi)容,包括參數(shù)值。而且這種方法對程序性能的影響相對較小,因?yàn)樗窃谝粋€統(tǒng)一的地方進(jìn)行日志記錄的處理,不會像開啟執(zhí)行日志法那樣在每個查詢的地方都進(jìn)行額外的日志記錄操作,從而避免了過多的性能開銷。同時,將日志記錄到文件中也便于我們長期保存和分析,對于后續(xù)排查一些線上環(huán)境中偶爾出現(xiàn)的數(shù)據(jù)庫相關(guān)問題也提供了有力的支持。所以,在實(shí)際的 Laravel 項(xiàng)目開發(fā)中,這種在 AppServiceProvider 中處理 SQL 語句打印的方法是一個比較好的選擇。
三、不同方法的對比與選擇
(一)使用便捷性對比
開啟執(zhí)行日志法:使用時需要先通過 DB::connection()->enableQueryLog(); 開啟日志記錄,執(zhí)行查詢操作后,再用 dd(DB::getQueryLog()); 來獲取信息,整體步驟不算復(fù)雜,但相對而言,在每個需要查看 SQL 語句的地方都要進(jìn)行這樣的操作,略顯繁瑣。并且在生產(chǎn)環(huán)境大量使用還可能影響性能,所以更適合開發(fā)和測試環(huán)境,使用場景上有一定限制,便捷性打個中等分吧。toSql () 方法:使用起來非常簡便,比如對于構(gòu)建的查詢語句 $query = DB::table('users')->where('id', 10);,只需通過 $query->toSql(); 就能快速獲取到 SQL 語句的大致樣子,在只想簡單查看某一條特定查詢語句結(jié)構(gòu)時很方便快捷,便捷性方面表現(xiàn)較好,可以打高分。在 AppServiceProvider 中處理:需要打開 app/Providers/AppServiceProvider.php 文件,在 boot 方法中添加相應(yīng)代碼來處理 SQL 語句的記錄和打印,初次配置時步驟稍多一些。不過一旦配置好,后續(xù)在開發(fā)和調(diào)試過程中,無需過多額外操作就能自動記錄 SQL 語句到日志文件,從長期使用和整體項(xiàng)目角度來看,還是比較方便的,便捷性也能給到中等偏上的分?jǐn)?shù)。
(二)獲取信息完整性對比
開啟執(zhí)行日志法:它的優(yōu)勢十分明顯,能夠完整地記錄下所有執(zhí)行過的 SQL 語句,還能包含相關(guān)的綁定參數(shù)和執(zhí)行時間等詳細(xì)信息,這對于排查復(fù)雜業(yè)務(wù)邏輯中 SQL 的執(zhí)行情況幫助極大,信息完整性方面可以打高分。toSql () 方法:其獲取到的 SQL 語句中參數(shù)是用 ? 占位符表示的,并不會直接顯示出實(shí)際的參數(shù)值,如果要獲取完整的帶有實(shí)際參數(shù)值的 SQL 語句,還需要進(jìn)行如 $sql = str_replace_array('?', $query->getBindings(), $query->toSql()); 這樣額外的處理操作,所以在信息完整性上有所欠缺,只能打低分。在 AppServiceProvider 中處理:通過這種方式可以將所有執(zhí)行的 SQL 語句以及完整的參數(shù)信息記錄到指定的日志文件中,我們能隨時查看日志文件了解完整的 SQL 語句內(nèi)容,包括參數(shù)值,在信息完整性方面表現(xiàn)優(yōu)秀,同樣可以打高分。
(三)性能影響對比
開啟執(zhí)行日志法:開啟執(zhí)行日志會在一定程度上影響程序的性能,尤其是在生產(chǎn)環(huán)境中,如果大量使用這種方式來打印 SQL 語句,可能會導(dǎo)致系統(tǒng)響應(yīng)變慢。而且日志信息是存放在內(nèi)存中的,當(dāng)查詢語句較多或者數(shù)據(jù)量較大時,還可能占用較多的內(nèi)存資源,性能影響方面表現(xiàn)較差,只能打低分。toSql () 方法:它只是獲取當(dāng)前構(gòu)建的這一條 SQL 語句,相對來說對性能的影響較小,不過如果在一個業(yè)務(wù)流程中有多條 SQL 語句需要查看,逐個獲取的過程可能也會有一定的性能損耗,但總體在這三種方法里對性能影響算是比較小的,可打中等分。在 AppServiceProvider 中處理:因?yàn)槭窃谝粋€統(tǒng)一的地方進(jìn)行日志記錄的處理,不會像開啟執(zhí)行日志法那樣在每個查詢的地方都進(jìn)行額外的日志記錄操作,從而避免了過多的性能開銷,對程序性能的影響相對較小,在性能影響這塊能打高分。
(四)選擇建議
綜合以上對比,如果您只是想快速查看某一條查詢語句的大致結(jié)構(gòu),方便簡單驗(yàn)證,那么 toSql() 方法是不錯的選擇,它操作簡便,能迅速滿足需求。要是處于開發(fā)環(huán)境或者測試環(huán)境,需要排查復(fù)雜業(yè)務(wù)邏輯中 SQL 的具體執(zhí)行情況,對信息完整性要求很高,并且不太在意一定程度的性能影響以及內(nèi)存占用問題時,開啟執(zhí)行日志法可以很好地幫助您定位問題,清晰呈現(xiàn)每一步查詢情況。而對于實(shí)際的 Laravel 項(xiàng)目開發(fā),特別是需要長期記錄和查看 SQL 語句,兼顧信息完整性、性能以及方便后續(xù)排查線上環(huán)境中偶爾出現(xiàn)的數(shù)據(jù)庫相關(guān)問題,建議采用在 AppServiceProvider 中處理的方法,雖然配置時稍麻煩一點(diǎn),但從整個項(xiàng)目周期來看,是最為實(shí)用和穩(wěn)定的選擇哦。
四、實(shí)際應(yīng)用案例展示
以下我們通過一個具體的復(fù)雜查詢案例,來分別使用上述三種方法進(jìn)行 SQL 語句打印,展示在實(shí)際開發(fā)中如何運(yùn)用這些方法進(jìn)行調(diào)試和優(yōu)化。假設(shè)我們正在開發(fā)一個電商項(xiàng)目,現(xiàn)在有一個需求是查詢出滿足特定條件的商品列表以及對應(yīng)的商家信息,同時還要根據(jù)商品的銷量進(jìn)行降序排序,并且只獲取前 10 條記錄。
(一)使用開啟執(zhí)行日志法
首先,我們使用開啟執(zhí)行日志法來查看執(zhí)行的 SQL 語句。在相關(guān)的控制器方法或者業(yè)務(wù)邏輯代碼處,添加以下代碼:通過這樣的操作,我們可以獲取到類似如下的執(zhí)行日志信息:從這個結(jié)果中,我們可以清晰地看到實(shí)際執(zhí)行的 SQL 語句結(jié)構(gòu)以及參數(shù)綁定情況。比如這里能明確看到 where 條件中商品狀態(tài)的參數(shù)值是 1,同時知道是按照 products.sales 字段進(jìn)行降序排序并且限制了獲取 10 條記錄。在排查數(shù)據(jù)是否正確顯示、關(guān)聯(lián)查詢是否符合預(yù)期等問題時,這樣完整的日志信息就很有用了。例如,如果發(fā)現(xiàn)獲取到的商品列表中包含了狀態(tài)不為 1 的商品,那就可以順著這條 SQL 語句去檢查是條件判斷邏輯有誤,還是數(shù)據(jù)在存入時狀態(tài)值就出現(xiàn)了差錯等情況。不過要注意,在生產(chǎn)環(huán)境中如果大量使用這種方式,尤其是面對頻繁的查詢操作時,可能會因?yàn)閮?nèi)存占用以及對性能的影響,導(dǎo)致系統(tǒng)響應(yīng)變慢等問題哦,所以它更適合在開發(fā)環(huán)境或者測試環(huán)境中幫助我們排查問題呢。
(二)使用 toSql () 方法
接下來,我們用 toSql() 方法來嘗試獲取 SQL 語句。代碼可以這樣寫:運(yùn)行后,得到的結(jié)果大概是這樣:可以看到,使用 toSql() 方法能快速獲取到查詢語句的基本結(jié)構(gòu),非常簡便直觀。但正如前面所說,它存在參數(shù)顯示不完整的問題,這里的參數(shù)都是用 ? 占位符表示的。如果我們想要確切知道參數(shù)值,還需要進(jìn)一步處理,像下面這樣:經(jīng)過替換操作后,才能完整顯示出帶有實(shí)際參數(shù)值的 SQL 語句。在這個案例中,如果只是想快速查看一下構(gòu)建的這個查詢語句的大致樣子,不想去開啟執(zhí)行日志等相對麻煩一點(diǎn)的操作時,toSql() 方法就可以滿足需求了,能讓我們迅速知曉查詢的基本邏輯和結(jié)構(gòu)是否正確。
(三)使用在 AppServiceProvider 中處理的方法
最后,我們看看在 AppServiceProvider 中處理的方法如何運(yùn)用在這個案例中。先打開 app/Providers/AppServiceProvider.php 文件,在 boot 方法中添加之前介紹的代碼:然后正常執(zhí)行查詢操作,也就是和前面類似的代碼:這時,我們可以到對應(yīng)的日志文件(例如 storage/logs/2024-12-25_query.log,日期根據(jù)實(shí)際執(zhí)行時間而定)中去查看記錄的 SQL 語句信息,可能會看到類似這樣的內(nèi)容:這種方式不僅能完整地記錄下帶有實(shí)際參數(shù)值的 SQL 語句,而且對程序性能的影響相對較小,還便于我們長期保存和后續(xù)分析。比如在后續(xù)項(xiàng)目上線后,如果偶爾出現(xiàn)和這個查詢相關(guān)的數(shù)據(jù)異常問題,我們就可以通過查看這些歷史的日志文件,快速定位是不是 SQL 語句執(zhí)行方面出現(xiàn)了狀況,幫助我們排查線上環(huán)境中的問題哦。通過這個實(shí)際的復(fù)雜查詢案例,大家可以更直觀地體會到這三種打印 SQL 語句方法在實(shí)際開發(fā)中的應(yīng)用場景和各自的特點(diǎn),從而根據(jù)具體的開發(fā)需求和環(huán)境,選擇最適合的方法來幫助我們進(jìn)行調(diào)試和優(yōu)化工作啦。
五、總結(jié)與注意事項(xiàng)
通過以上內(nèi)容,我們詳細(xì)介紹了 Laravel 中打印 SQL 語句的多種方法,包括開啟執(zhí)行日志法、toSql() 方法以及在 AppServiceProvider 中處理的方法,并對它們的使用便捷性、獲取信息完整性和性能影響等方面進(jìn)行了對比分析,同時展示了在實(shí)際復(fù)雜查詢案例中的應(yīng)用。在實(shí)際使用過程中,大家需要注意以下幾點(diǎn):首先,在生產(chǎn)環(huán)境中,應(yīng)謹(jǐn)慎使用開啟執(zhí)行日志法來打印 SQL 語句,因?yàn)樗赡軙π阅墚a(chǎn)生較大的影響,導(dǎo)致系統(tǒng)響應(yīng)變慢等問題。而在開發(fā)和測試環(huán)境中,這種方法對于排查復(fù)雜業(yè)務(wù)邏輯中的 SQL 執(zhí)行情況非常有幫助,可以根據(jù)實(shí)際情況合理運(yùn)用。其次,使用 toSql() 方法時,要清楚它獲取的 SQL 語句中參數(shù)是用 ? 占位符表示的,如果需要完整的帶有實(shí)際參數(shù)值的 SQL 語句,還需要進(jìn)行額外的處理操作,避免在不知情的情況下因?yàn)閰?shù)問題導(dǎo)致對 SQL 語句的理解和調(diào)試出現(xiàn)偏差。對于在 AppServiceProvider 中處理的方法,雖然它是一種較為穩(wěn)定和實(shí)用的方法,但在配置代碼時要確保準(zhǔn)確無誤,以免出現(xiàn)記錄的 SQL 語句信息不準(zhǔn)確或者無法記錄的情況。另外,無論使用哪種方法打印 SQL 語句,都要注意代碼的安全性和性能優(yōu)化。避免在不必要的地方頻繁打印 SQL 語句,同時要確保打印的 SQL 語句不會泄露敏感信息,防止?jié)撛诘陌踩L(fēng)險??傊莆?Laravel 打印 SQL 語句的方法,能夠幫助我們更好地進(jìn)行數(shù)據(jù)庫查詢的調(diào)試和優(yōu)化,提高項(xiàng)目的開發(fā)效率和質(zhì)量,確保項(xiàng)目的穩(wěn)定運(yùn)行。希望大家在實(shí)際開發(fā)中,根據(jù)項(xiàng)目的具體需求和環(huán)境,靈活選擇合適的方法來打印 SQL 語句,為項(xiàng)目的順利推進(jìn)提供有力的支持。