最近寫作生涯遭遇了滑鐵盧,寫了兩篇文章閱讀量都不怎么樣,但還是要繼續(xù)分享所學(xué),服務(wù)端的菜雞不能輸,要重新稱霸中原,233333當(dāng)然這是很難的,后端優(yōu)秀作者實(shí)在太多了,還得繼續(xù)加油。回歸主題,最近又在重新學(xué)習(xí)MySQL,想起了阿里開發(fā)手冊禁用select * 查詢語句,這是為什么呢
引言
阿里巴巴開發(fā)手冊中指出:
【強(qiáng)制】在表查詢中,一律不要使用 * 作為查詢的字段列表,需要哪些字段必須明確寫明
說明:
- 增加查詢分析器解析成本
- 增減字段容易與 resultMap 配置不一致
- 無用字段增加網(wǎng)絡(luò) 消耗,尤其是 text 類型的字段
文章將從這幾個方面展開說明
增加查詢分析器解析成本
首先介紹一下MySQL基本架構(gòu),基本結(jié)構(gòu)如下圖:
MySQL 基本架構(gòu)可以分為 Server 層和存儲引擎層兩部分。Server 層包括連接器、查詢緩存、分析器、優(yōu)化器、執(zhí)行器等。存儲引擎層負(fù)責(zé)數(shù)據(jù)的存儲和提取,其架構(gòu)模式是插件式的,支持 InnoDB、MyISAM、Memory 等多個存儲引擎
當(dāng)我們執(zhí)行一條查詢語句:select * from t where id = 1,在MySQL中執(zhí)行過程如下:
- 在真正執(zhí)行select * from t where id = 1時首先需要輸入命令mysql -uroot -p,通過連接器將客戶端和服務(wù)層建立起連接
- 建立起連接以后輸入select * from t where id = 1這條SQL
- 首先查找查詢緩存中是否已經(jīng)執(zhí)行過該條語句
- 若未命中查詢緩存,則執(zhí)行select * from t where id = 1這條SQL,分析器會對這條SQL進(jìn)行詞法分析。
- MySQL 從輸入的select這個關(guān)鍵字識別出來這是一個查詢語句。
- 把字符串t識別成表名t,把字符串id識別成列id
- 判斷輸入的這個 SQL 語句是否滿足 MySQL 語法
如果使用select * 來查詢語句,分析器需要對進(jìn)行額外的解析*,如果直接指定成列名,則不需要進(jìn)行額外的解析,直接識別成列名 - 經(jīng)過了分析器,MySQL 就知道要做什么了。在開始執(zhí)行之前,還要先經(jīng)過優(yōu)化器的處理。優(yōu)化器是在表里面有多個索引的時候,決定使用哪個索引,或者在一個語句有多表關(guān)聯(lián)(join)的時候,決定各個表的連接順序
- MySQL通過分析器知道了要做什么,通過優(yōu)化器知道了要怎么做,就進(jìn)入了執(zhí)行器階段開始執(zhí)行語句。開始執(zhí)行的時候首先要判斷一下是否有對表t的查詢權(quán)限,如果沒有就會返回沒有權(quán)限的錯誤
失去MySQL優(yōu)化器“覆蓋索引?”策略優(yōu)化的可能性
假設(shè)有一條sql語句為select * from t where name = "何甜甜",其中id為主鍵,name為索引,而實(shí)際上這么寫的目的只是想查詢指定name的id
在innodb存儲引擎中,索引可以分為非主鍵索引和主鍵索引,主鍵索引和主鍵的區(qū)別在于葉子節(jié)點(diǎn)存放數(shù)據(jù)的不同。主鍵索引中葉子節(jié)點(diǎn)存放的是整行數(shù)據(jù),而非主鍵索引中葉子節(jié)點(diǎn)存放的是主鍵的值。現(xiàn)在我們來看select * from t where name = "何甜甜"這條語句是如何執(zhí)行的
因?yàn)閚ame是索引且name是查詢條件,查詢優(yōu)化器會選擇使用name索引。首先根據(jù)name查詢到該name對應(yīng)的主鍵id為1,因?yàn)樾枰樵兊氖侵付╪ame的所有數(shù)據(jù),因此還需要根據(jù)主鍵id進(jìn)行一次回表操作。所謂回表操作是指非主鍵索引中查詢到主鍵id,在根據(jù)主鍵id到主鍵索引中查詢到所有數(shù)據(jù),具體過程可看下圖
前面已經(jīng)提到查詢到的中只是用到了id字段,如果將原來的sql語句修改為select id from t where name = "何甜甜",就可以避免一次回表操作。在非主鍵索引中已經(jīng)覆蓋了查詢需求【即所需查詢的ID已在非主鍵索引上了】,也被稱為覆蓋索引*。通過覆蓋索引可以減少回表次數(shù),從而顯著提升查詢性能,因此在實(shí)際寫sql過程中應(yīng)該盡量避免寫select * 這樣的查詢語句,寫之前先反問是否真的需要用到這么多字段
增加IO操作
BLOB和TEXT是為了存儲很大的數(shù)據(jù)而設(shè)計(jì)的字符串?dāng)?shù)據(jù)類型,當(dāng)BLOB和TEXT值太大時,InnoDB會使用專門的外部存儲區(qū)域來進(jìn)行存儲,每個值在行內(nèi)需要1~4字節(jié)存儲一個指針,然后在外部存儲區(qū)域存儲實(shí)際的值,如果查詢的*中有BLOB或TEXT類型的字段,則查詢的BLOB或TEXT列需要在進(jìn)行額外一次IO操作去外部存儲區(qū)域?qū)?shù)據(jù)查詢到,所以盡量避免使用select *
增加數(shù)據(jù)傳輸時間和網(wǎng)絡(luò)開銷
傳輸數(shù)據(jù)過多會增加網(wǎng)絡(luò)開銷。同時,查詢語句執(zhí)行時會先將查詢到的數(shù)據(jù)放到查詢緩存區(qū)中,再從查詢緩存中將結(jié)果返回給客戶端,如果查詢到的數(shù)據(jù)量非常大則需要花很多時間來存儲結(jié)果,所以在說一次,避免使用select *
小結(jié)
在實(shí)際開發(fā)中應(yīng)盡量避免寫select *這樣的SQL語句,雖然通常情況下即使真的寫了這樣的select *這樣的SQL語句,對項(xiàng)目的影響可能也沒這么大,但好習(xí)慣還是要養(yǎng)成的
我還是覺得之前寫的文章也很不錯,用戶管理模塊:如何保證用戶數(shù)據(jù)安全,還是要再繼續(xù)宣傳一波,點(diǎn)個贊在走吧