SELECT *
無論工作還是面試,說到sql優化,比說的一個問題就是,代碼中sql不要出現 SELECT *,之前一直也沒有深入去研究研究,為什么,只是記住了,代碼中注意了,但是就在今天逛某某論壇時,又看到有同學在發布這樣的經驗分享,讀完,有感覺模模糊糊,懵懵懂懂。
遂下定決心,整理一篇,為什么不要使用**SELECT * **,直接進入正。
一、為什么不要使用_SELECT *_
首先我們參考一下《阿里java開發手冊(泰山版)》中 MySQL 部分描述:
4 - 1. 【強制】在表查詢中,一律不要使用 * 作為查詢的字段列表,需要哪些字段必須明確寫明。
說明:
- 增加查詢分析器解析成本。
- 增減字段容易與 resultMap 配置不一致。
- 無用字段增加網絡 消耗,尤其是 text 類型的字段。
在阿里的開發手冊中,大面的概括了上面幾點。
二、詳細解讀原因
1. 使用 * 號查詢,會查詢出多個我們不需要的字段,增加sql執行的時間,同時大量的多余字段,會增加網絡開銷
-
用
“SELECT * ”
數據庫需要解析更多的對象、字段、權限、屬性等相關內容,在 SQL 語句復雜,硬解析較多的情況下,會對數據庫造成沉重的負擔 -
"SELECT * "
經常會帶上無用且大文本字段(比如LOG,),當出現這情況時,對於網絡開銷來說,是一種巨大的負擔,甚至網絡開銷會幾何倍數增加,另外如果DB和應用程序不在同一台機器,這種開銷非常明顯 -
即使 mysql 服務器和客戶端是在同一台機器上,使用的協議還是 tcp,通信也是需要額外的時間。
2.對於無用的大字段,如 varchar、blob、text,會增加 io 操作
- 准確來說,長度超過 728 字節的時候,會先把超出的數據序列化到另外一個地方,因此讀取這條記錄會增加一次 io 操作。(MySQL InnoDB)
3. 失去MySQL優化器“覆蓋索引”策略優化的可能性
SELECT * 杜絕了覆蓋索引的可能性,而基於MySQL優化器的“覆蓋索引”策略又是速度極快,效率極高,業界極為推薦的查詢優化方式。
例如:有一個表為t(a,b,c,d,e,f),其中,a為主鍵,b列有索引。
- 這種情況,首先我們要搞明白,mysql會給我們創建那些索引;
- 我們知道在mysql中(Innodb引擎)聚集索引是一定存在的,如果表結構中定義了主鍵,聚集索引就根據主鍵建立,否則如果有唯一列就用唯一列,否則就會自動在每條記錄中生成一個隱藏的ID列並以此建立聚集索引,聚集索引的葉子節點就是真實的數據。
- 因此上面的示例會創建兩種索引:1、聚集索引(a,b,c,d,e,f) 2、聯合索引(a,b)[這里也包含單列索引]
- 當我們查詢的時候,走索引的大致流程分兩步,一是先根據索引列在聯合索引樹中找到ID,然后根據ID在聚集索引中找到對應的記錄(這一步也稱為回表)
- 根據上面查詢索引過程就會發現問題,也就是聯合索引中其實已經包含了a,b字段的數據。那如果我查詢語句中查詢的就是這兩個字段的數據,並且搜索條件中也是這兩列中的一列,那我還有必要分兩步操作嗎?還有必要進行回表操作嗎?需要在聯合索引樹中進行搜索即可以對於這種情況,如果用
select a,b from table where a = '1'
這種指定列的查詢只需走聯合索引中的單列索引即可,無需回表,並且索引的每條記錄不含有隱藏列,加載內存的操作會更快如果用select *
的話,因為聯合索引樹中並沒有c等其它字段,所以根本走不了聯合索引,只能對聚集索引進行全表掃描,在數據量大的情況下,性能影響還是很可觀的。 - 如果用戶使用
SELECT*
,獲取了不需要的數據,則首先通過輔助索引過濾數據,然后再通過聚集索引獲取所有的列,這就多了一次b+樹查詢,速度必然會慢很多。
- 由於輔助索引的數據比聚集索引少很多,很多情況下,通過輔助索引進行覆蓋索引(通過索引就能獲取用戶需要的所有列),都不讀磁盤,直接從內存取,而聚集索引很可能數據在磁盤(外存)中(取決於buffer pool的大小和命中率),這種情況下,一個是內存讀,一個是磁盤讀,速度差異就很顯著了,幾乎是數量級的差異。
2.連接查詢時,使用*
無法進入緩沖池
- mysql中連接查詢的原理是先對驅動表進行查詢操作,然后再用從驅動表得到的數據作為條件,逐條的到被驅動表進行查詢。
- 每次驅動表加載一條數據到內存中,然后被驅動表所有的數據都需要往內存中加載一遍進行比較。效率很低,所以mysql中可以指定一個緩沖池的大小,緩沖池大的話可以同時加載多條驅動表的數據進行比較,放的數據條數越多性能io操作就越少,性能也就越好。所以,如果此時使用
select *
放一些無用的列,只會白白的占用緩沖空間。浪費本可以提高性能的機會。
索引知識延伸
1.聯合索引
1.1聯合索引認識
mysql索引比較簡單的是單列索引(b+tree)。遇到多條件查詢時,不可避免會使用到多列索引。聯合索引又叫復合索引
b+tree結構如下圖
每一個磁盤塊在mysql中是一個頁,頁大小是固定的,mysql innodb的默認的頁大小是16k,每個索引會分配在頁上的數量是由字段的大小決定。當字段值的長度越長,每一頁上的數量就會越少,因此在一定數據量的情況下,索引的深度會越深,影響索引的查找效率。
對於復合索引(多列b+tree,使用多列值組合而成的b+tree索引)。遵循最左側原則,從左到右的使用索引中的字段,一個查詢可以只使用索引中的一部份,但只能是最左側部分。例如索引是key index (a,b,c). 可以支持a a,b a,b,c 3種組合進行查找,但不支持 b,c進行查找。當使用最左側字段時,索引就十分有效。
創建表test如下:
create table test(
a int,
b int,
c int,
KEY a(a,b,c));
- 查(a,b,c)的時候,b+數是按照從左到右的順序來建立搜索樹的,比如當(a=? and b=? and c=?)這樣的數據來檢索的時候,b+樹會優先比較a列來確定下一步的所搜方向,如果a列相同再依次比較b列和c列,最后得到檢索的數據;但當(b=? and c=?)這樣的沒有a列的數據來的時候,b+樹就不知道下一步該查哪個節點,因為建立搜索樹的時候a列就是第一個比較因子,必須要先根據a列來搜索才能知道下一步去哪里查詢。比如當(a=? and c=?)這樣的數據來檢索時,b+樹可以用a列來指定搜索方向,但下一個字段b列的缺失,所以只能把a列的數據找到,然后再匹配c列的數據了, 這個是非常重要的性質,即索引的最左匹配特性。以下通過例子分析索引的使用情況,以便於更好的理解聯合索引的查詢方式和使用范圍
1.2多列索引在and查詢中應用
select * from test where a=? and b=? and c=?;查詢效率最高,索引全覆蓋。
select * from test where a=? and b=?;索引覆蓋a和b。
select * from test where b=? and a=?;經過mysql的查詢分析器的優化,索引覆蓋a和b。
select * from test where a=?;索引覆蓋a。
select * from test where b=? and c=?;沒有a列,不走索引,索引失效。
select * from test where c=?;沒有a列,不走索引,索引失效。
1.3多列索引在范圍查詢中應用
select * from test where a=? and b between ? and ? and c=?;索引覆蓋a和b,因b列是范圍查詢,因此c列不能走索引。
select * from test where a between ? and ? and b=?;a列走索引,因a列是范圍查詢,因此b列是無法使用索引。
select * from test where a between ? and ? and b between ? and ? and c=?;a列走索引,因a列是范圍查詢,b列是范圍查詢也不能使用索引。
1.4多列索引在排序中應用
select * from test where a=? and b=? order by c;a、b、c三列全覆蓋索引,查詢效率最高。
select * from test where a=? and b between ? and ? order by c;a、b列使用索引查找,因b列是范圍查詢,因此c列不能使用索引,會出現file sort。
1.5聯合索引的優勢
- 減少開銷
- 建一個聯合索引 (a,b,c) ,實際相當於建了 (a)、(a,b)、(a,b,c) 三個索引。每多一個索引,都會增加寫操作的開銷和磁盤空間的開銷。對於大量數據的表,使用聯合索引會大大的減少開銷
- 覆蓋索引
對聯合索引 (a,b,c),如果有如下 sql 的,
SELECT a,b,c from table where a='xx' and b = 'xx';
- 那么 MySQL 可以直接通過遍歷索引取得數據,而無需回表,這減少了很多的隨機 io 操作。減少 io 操作,特別是隨機 io 其實是 DBA 主要的優化策略。所以,在真正的實際應用中,覆蓋索引是主要的提升性能的優化手段之一。
- 效率高
索引列多,通過聯合索引篩選出的數據越少。比如有 1000W 條數據的表,有如下SQL:
select col1,col2,col3 from table where col1=1 and col2=2 and col3=3;
假設:假設每個條件可以篩選出10%
的數據。
- A. 如果只有單列索引,那么通過該索引能篩選出
1000W10%=100w
條數據,然后再回表從100w
條數據中找到符合col2=2 and col3= 3
的數據,然后再排序,再分頁,以此類推(遞歸); - B. 如果是(col1,col2,col3)聯合索引,通過三列索引篩選出
1000w10% 10% *10%=1w
,效率提升可想而知!
索引是建的越多越好嗎
答案自然是否定的
* 數據量小的表不需要建立索引,建立會增加額外的索引開銷
* 不經常引用的列不要建立索引,因為不常用,即使建立了索引也沒有多大意義
* 經常頻繁更新的列不要建立索引,因為肯定會影響插入或更新的效率
* 數據重復且分布平均的字段,因此他建立索引就沒有太大的效果(例如性別字段,只有男女,不適合建立索引)
* 數據變更需要維護索引,意味着索引越多維護成本越高。
* 更多的索引也需要更多的存儲空間
1.6聯合索引小結
- 總結聯合索引的使用在寫where條件的順序無關,mysql查詢分析會進行優化而使用索引。但是減輕查詢分析器的壓力,最好和索引的從左到右的順序一致。使用等值查詢,多列同時查詢,索引會一直傳遞並生效。因此等值查詢效率最好(無需回表,)。索引查找遵循最左側原則。但是遇到范圍查詢列之后的列索引失效。排序也能使用索引,合理使用索引排序,避免出現file sort。
其他
select中要不要使用 *
除了極少數情況下,絕大多數情況下,使用 * 是一種糟糕的編程習慣!
分析如下:
1、如果采用 select * 進行查找時,查詢到的列是按照它們在表的原始位置展示的;如果客戶端同樣采用列的原始位置進行引用,如果更改表結構,會導致難以察覺的錯誤;
2、使用 * 時,數據庫會先查數據字典,明確 * 代表什么,這會在分析階段造成大量開銷;
3、select * 最大的問題是可能會多出一些不用的列,導致無法使用索引覆蓋,導致查詢成本幾何層級的增加
4、不需要的字段會增加數據傳輸的時間,如果是本地客戶端,連接的事本地的mysql服務器,tcp協議傳輸數據會增加額外時間;如果是db和客戶端不在同一台機器,比如連接到阿里雲,則開銷會更加明顯
5、如果查詢的時候獲取了不必要的列,字段較多時,mysql並非一次性保存,而是主次分布內存,當時用完后,再次分配。如此會導致多次分配,頻繁分配會增加額外消耗時間
6、如果sql語句復雜,select * 會解析更多的對象,字段,權限,屬性等內容,增加數據庫負擔