最近寫作生涯遭遇了滑鐵盧,寫了兩篇文章閱讀量都不怎么樣,但還是要繼續(xù)分享所學(xué),服務(wù)端的菜雞不能輸,要重新稱霸中原,233333當(dāng)然這是很難的,后端優(yōu)秀作者實(shí)在太多了,還得繼續(xù)加油。回歸主題,最近又在重新學(xué)習(xí)MySQL,想起了阿里開發(fā)手冊(cè)禁用select * 查詢語(yǔ)句,這是為什么呢
引言
阿里巴巴開發(fā)手冊(cè)中指出:
【強(qiáng)制】在表查詢中,一律不要使用 * 作為查詢的字段列表,需要哪些字段必須明確寫明
說明:
- 增加查詢分析器解析成本
- 增減字段容易與 resultMap 配置不一致
- 無用字段增加網(wǎng)絡(luò) 消耗,尤其是 text 類型的字段
文章將從這幾個(gè)方面展開說明
增加查詢分析器解析成本
首先介紹一下MySQL基本架構(gòu),基本結(jié)構(gòu)如下圖:

MySQL 基本架構(gòu)可以分為 Server 層和存儲(chǔ)引擎層兩部分。Server 層包括連接器、查詢緩存、分析器、優(yōu)化器、執(zhí)行器等。存儲(chǔ)引擎層負(fù)責(zé)數(shù)據(jù)的存儲(chǔ)和提取,其架構(gòu)模式是插件式的,支持 InnoDB、MyISAM、Memory 等多個(gè)存儲(chǔ)引擎
當(dāng)我們執(zhí)行一條查詢語(yǔ)句:select * from t where id = 1,在MySQL中執(zhí)行過程如下:
- 在真正執(zhí)行select * from t where id = 1時(shí)首先需要輸入命令mysql -uroot -p,通過連接器將客戶端和服務(wù)層建立起連接
- 建立起連接以后輸入select * from t where id = 1這條SQL
- 首先查找查詢緩存中是否已經(jīng)執(zhí)行過該條語(yǔ)句
- 若未命中查詢緩存,則執(zhí)行select * from t where id = 1這條SQL,分析器會(huì)對(duì)這條SQL進(jìn)行詞法分析。
- MySQL 從輸入的select這個(gè)關(guān)鍵字識(shí)別出來這是一個(gè)查詢語(yǔ)句。
- 把字符串t識(shí)別成表名t,把字符串id識(shí)別成列id
- 判斷輸入的這個(gè) SQL 語(yǔ)句是否滿足 MySQL 語(yǔ)法
如果使用select * 來查詢語(yǔ)句,分析器需要對(duì)進(jìn)行額外的解析*,如果直接指定成列名,則不需要進(jìn)行額外的解析,直接識(shí)別成列名 - 經(jīng)過了分析器,MySQL 就知道要做什么了。在開始執(zhí)行之前,還要先經(jīng)過優(yōu)化器的處理。優(yōu)化器是在表里面有多個(gè)索引的時(shí)候,決定使用哪個(gè)索引,或者在一個(gè)語(yǔ)句有多表關(guān)聯(lián)(join)的時(shí)候,決定各個(gè)表的連接順序
- MySQL通過分析器知道了要做什么,通過優(yōu)化器知道了要怎么做,就進(jìn)入了執(zhí)行器階段開始執(zhí)行語(yǔ)句。開始執(zhí)行的時(shí)候首先要判斷一下是否有對(duì)表t的查詢權(quán)限,如果沒有就會(huì)返回沒有權(quán)限的錯(cuò)誤
失去MySQL優(yōu)化器“覆蓋索引?”策略優(yōu)化的可能性
假設(shè)有一條sql語(yǔ)句為select * from t where name = "何甜甜",其中id為主鍵,name為索引,而實(shí)際上這么寫的目的只是想查詢指定name的id
在innodb存儲(chǔ)引擎中,索引可以分為非主鍵索引和主鍵索引,主鍵索引和主鍵的區(qū)別在于葉子節(jié)點(diǎn)存放數(shù)據(jù)的不同。主鍵索引中葉子節(jié)點(diǎn)存放的是整行數(shù)據(jù),而非主鍵索引中葉子節(jié)點(diǎn)存放的是主鍵的值。現(xiàn)在我們來看select * from t where name = "何甜甜"這條語(yǔ)句是如何執(zhí)行的
因?yàn)閚ame是索引且name是查詢條件,查詢優(yōu)化器會(huì)選擇使用name索引。首先根據(jù)name查詢到該name對(duì)應(yīng)的主鍵id為1,因?yàn)樾枰樵兊氖侵付╪ame的所有數(shù)據(jù),因此還需要根據(jù)主鍵id進(jìn)行一次回表操作。所謂回表操作是指非主鍵索引中查詢到主鍵id,在根據(jù)主鍵id到主鍵索引中查詢到所有數(shù)據(jù),具體過程可看下圖

前面已經(jīng)提到查詢到的中只是用到了id字段,如果將原來的sql語(yǔ)句修改為select id from t where name = "何甜甜",就可以避免一次回表操作。在非主鍵索引中已經(jīng)覆蓋了查詢需求【即所需查詢的ID已在非主鍵索引上了】,也被稱為覆蓋索引*。通過覆蓋索引可以減少回表次數(shù),從而顯著提升查詢性能,因此在實(shí)際寫sql過程中應(yīng)該盡量避免寫select * 這樣的查詢語(yǔ)句,寫之前先反問是否真的需要用到這么多字段
增加IO操作
BLOB和TEXT是為了存儲(chǔ)很大的數(shù)據(jù)而設(shè)計(jì)的字符串?dāng)?shù)據(jù)類型,當(dāng)BLOB和TEXT值太大時(shí),InnoDB會(huì)使用專門的外部存儲(chǔ)區(qū)域來進(jìn)行存儲(chǔ),每個(gè)值在行內(nèi)需要1~4字節(jié)存儲(chǔ)一個(gè)指針,然后在外部存儲(chǔ)區(qū)域存儲(chǔ)實(shí)際的值,如果查詢的*中有BLOB或TEXT類型的字段,則查詢的BLOB或TEXT列需要在進(jìn)行額外一次IO操作去外部存儲(chǔ)區(qū)域?qū)?shù)據(jù)查詢到,所以盡量避免使用select *
增加數(shù)據(jù)傳輸時(shí)間和網(wǎng)絡(luò)開銷
傳輸數(shù)據(jù)過多會(huì)增加網(wǎng)絡(luò)開銷。同時(shí),查詢語(yǔ)句執(zhí)行時(shí)會(huì)先將查詢到的數(shù)據(jù)放到查詢緩存區(qū)中,再?gòu)牟樵兙彺嬷袑⒔Y(jié)果返回給客戶端,如果查詢到的數(shù)據(jù)量非常大則需要花很多時(shí)間來存儲(chǔ)結(jié)果,所以在說一次,避免使用select *
小結(jié)
在實(shí)際開發(fā)中應(yīng)盡量避免寫select *這樣的SQL語(yǔ)句,雖然通常情況下即使真的寫了這樣的select *這樣的SQL語(yǔ)句,對(duì)項(xiàng)目的影響可能也沒這么大,但好習(xí)慣還是要養(yǎng)成的
我還是覺得之前寫的文章也很不錯(cuò),用戶管理模塊:如何保證用戶數(shù)據(jù)安全,還是要再繼續(xù)宣傳一波,點(diǎn)個(gè)贊在走吧
