避免使用SELECT* 的九個(gè)理由及兩個(gè)例外場景
我們經(jīng)常會(huì)看到一些文章警告使用SELECT * 是一種錯(cuò)誤的習(xí)慣,應(yīng)該明確地指定查詢的列名,譬如在最新《阿里java開發(fā)手冊(cè)(泰山版)》中對(duì)此做了強(qiáng)制性的規(guī)定。而大家對(duì)其原因只是一知半解。本文將從兩個(gè)角度解釋避免使用SELECT *的9個(gè)理由,同時(shí)討論2個(gè)合理使用SELECT *的例外場景。
性能問題
從性能的角度,在應(yīng)用中使用SELECT *可能會(huì)引起查詢的性能問題,主要表現(xiàn)在以下六個(gè)方面。
- 增加解析成本:用SELECT *數(shù)據(jù)庫需要解析更多的對(duì)象、權(quán)限、屬性等相關(guān)內(nèi)容,這個(gè)影響可能較小,但是它確實(shí)會(huì)對(duì)數(shù)據(jù)庫的元數(shù)據(jù)查詢?cè)斐梢欢ǖ膲毫Α?/li>
- 增加I/O操作,對(duì)于無用的大字段,如 VARCHAR、LOB、TEXT類型的字段,會(huì)增加 IO操作;數(shù)據(jù)庫一般會(huì)把超過一定長度的大字段,存放在單獨(dú)的表空間中,因此對(duì)這些字段的訪問會(huì)額外地增加一次I/O操作。
- 增加網(wǎng)絡(luò)消耗,帶上如LOB/TEXT之類的無用的大文本字段,傳輸數(shù)據(jù)量會(huì)成多倍地增漲,特別是如果數(shù)據(jù)庫和應(yīng)用程序不在同一臺(tái)機(jī)器,這種開銷非常明顯。
- 增加內(nèi)存消耗,不管您是否使用這些列,您的應(yīng)用程序都需要把它們接收到內(nèi)存,這可能會(huì)無謂的消耗大量的內(nèi)存,影響程序的性能及健壯性,甚至造成內(nèi)存溢出,應(yīng)用崩潰。
- 影響索引選擇,對(duì)于使用SELECT * 的查詢語句,優(yōu)化器會(huì)放棄覆蓋索引策略優(yōu)化的可能性,導(dǎo)致需要回表或是全表掃描。
- 影響索引推薦,PawSQL的索引推薦引擎能夠分析SQL的結(jié)構(gòu),對(duì)符合條件的表上創(chuàng)建索引(快速定位、避免排序、避免回表)以提升查詢性能,具體請(qǐng)參考《創(chuàng)建高效索引的準(zhǔn)則》。對(duì)于使用SELECT * 的查詢語句,PawSQL將不考慮進(jìn)行覆蓋索引的推薦。
維護(hù)代價(jià)
從代碼維護(hù)的角度,在應(yīng)用中使用SELECT *可能會(huì)導(dǎo)致維護(hù)變得困難,主要表現(xiàn)在以下三個(gè)方面,
代碼可讀性:使用SELECT * 會(huì)降低代碼的可讀性,這是因?yàn)槭褂肧ELECT * 會(huì)使查詢語句不容易理解,開發(fā)人員需要查看表定義來確定到底查詢的是什么數(shù)據(jù),同時(shí)也難以進(jìn)行調(diào)試。
列名對(duì)齊:
- 在通過ORM框架開發(fā)應(yīng)用時(shí),增加或是刪減字段,容易與 resultMap 配置不一致;
- 當(dāng)使用SELECT * 定義視圖時(shí),增加或是刪減字段,都可能導(dǎo)致視圖失效,
- 您可能會(huì)經(jīng)常使用SELECT * into INSERT . . .之類的語句,以實(shí)現(xiàn)將某些數(shù)據(jù)從一張表復(fù)制到另一張表。如果在兩張表中,各個(gè)列的排列順序略有不同,那么就可能會(huì)出現(xiàn)將不正確的數(shù)據(jù)復(fù)制到錯(cuò)誤列中的情況。
列名沖突:如果您在連接查詢中使用了SELECT * ,一旦在多個(gè)表中出現(xiàn)了具有相同名稱的列,就會(huì)導(dǎo)致列名沖突;從而導(dǎo)致數(shù)據(jù)的消費(fèi)方使用錯(cuò)誤。
兩個(gè)例外情形
SELECT * 并不是任何情況下都不適合使用,我們?nèi)粘i_發(fā)中經(jīng)常使用的合理場景有兩類:
即席查詢(Ad-hoc Query)
當(dāng)我們進(jìn)行數(shù)據(jù)探索或是問題定位時(shí),我們需要即時(shí)的手工寫一些SQL來查看某些數(shù)據(jù)表,我們不知道表有哪些列,這個(gè)時(shí)候我們可以使用SELECT *來完成我們的查詢。我們不會(huì),也不需要預(yù)先研究底層列名是什么,我們甚至是通過SELECT *來確定列名和樣例數(shù)據(jù)。特別是當(dāng)表有大量的列時(shí),SELECT *可以讓我們更方便快捷完成數(shù)據(jù)探索或是問題定位的目標(biāo)。
當(dāng) * 表示一行,而不是代表所有列時(shí)
當(dāng) * 表示一行,而不是代表所有列時(shí),* 的使用是合理的。
譬如在以下用例中,* 表示符合某個(gè)條件的行。如果您使用列名而不是 *,它將計(jì)算該列值不為NULL的行數(shù)。
類似的,在下面的這個(gè)查詢中,* 表示符合關(guān)聯(lián)條件條件的行。
有些人在 SELECT 列表中使用 表orders的主鍵o.c_custkey,或者使用數(shù)字1,但是這些約定基本上是沒有意義的。你查詢的是符合某個(gè)條件的所有行,這就是 * 的含義。對(duì)于數(shù)據(jù)庫優(yōu)化器來說,這兩個(gè)查詢語句實(shí)際上是相同的。
PawSQL中與SELECT*相關(guān)的優(yōu)化
PawSQL中與SELECT*相關(guān)的審查或優(yōu)化有三個(gè)。
SELECT*審查預(yù)警
PawSQL分析用戶輸入的SQL語句,并對(duì)其中出現(xiàn)的非上述例外情況的SELECT*進(jìn)行提示預(yù)警。
- 對(duì)下面的SQL進(jìn)行預(yù)警
- 對(duì)下面的SQL不預(yù)警,屬于例外情況
投影下推(Projection Pushdown)
PawSQL中的投影下推重寫優(yōu)化可以把子查詢中的SELECT進(jìn)行重寫,刪除不必要的SELECT
- 優(yōu)化前SQL,子查詢中存在SELECT*
- 應(yīng)用投影下推后
如果您希望在PawSQL中驗(yàn)證投影下推對(duì)于SELECT *的處理邏輯,您需要先禁用查詢折疊(Query Folding)重寫優(yōu)化,因?yàn)椴樵冋郫B會(huì)將子查詢重寫合并。
覆蓋索引推薦
如在性能問題章節(jié)里所述,對(duì)于使用SELECT * 的查詢語句,PawSQL索引推薦引擎將不考慮進(jìn)行覆蓋索引的推薦。
關(guān)于PawSQL
PawSQL專注數(shù)據(jù)庫性能優(yōu)化的自動(dòng)化和智能化,支持MySQL,PostgreSQL,Opengauss等,提供的SQL優(yōu)化產(chǎn)品包括。
- PawSQL Cloud,在線自動(dòng)化SQL優(yōu)化工具,支持SQL審查,智能查詢重寫、基于代價(jià)的索引推薦,適用于數(shù)據(jù)庫管理員及數(shù)據(jù)應(yīng)用開發(fā)人員。
- PawSQL Advisor,IntelliJ 插件, 適用于數(shù)據(jù)應(yīng)用開發(fā)人員,可以IDEA/DataGrip應(yīng)用市場通過名稱搜索“PawSQL Advisor”安裝。