到底為什么不要用SELECT *


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+樹查詢,速度必然會慢很多。
    索引B+樹
    • 由於輔助索引的數據比聚集索引少很多,很多情況下,通過輔助索引進行覆蓋索引(通過索引就能獲取用戶需要的所有列),都不讀磁盤,直接從內存取,而聚集索引很可能數據在磁盤(外存)中(取決於buffer pool的大小和命中率),這種情況下,一個是內存讀,一個是磁盤讀,速度差異就很顯著了,幾乎是數量級的差異。

2.連接查詢時,使用*無法進入緩沖池

  1. mysql中連接查詢的原理是先對驅動表進行查詢操作,然后再用從驅動表得到的數據作為條件,逐條的到被驅動表進行查詢。
  2. 每次驅動表加載一條數據到內存中,然后被驅動表所有的數據都需要往內存中加載一遍進行比較。效率很低,所以mysql中可以指定一個緩沖池的大小,緩沖池大的話可以同時加載多條驅動表的數據進行比較,放的數據條數越多性能io操作就越少,性能也就越好。所以,如果此時使用select * 放一些無用的列,只會白白的占用緩沖空間。浪費本可以提高性能的機會。

索引知識延伸

1.聯合索引

1.1聯合索引認識

mysql索引比較簡單的是單列索引(b+tree)。遇到多條件查詢時,不可避免會使用到多列索引。聯合索引又叫復合索引

b+tree結構如下圖
每一個磁盤塊在mysql中是一個頁,頁大小是固定的,mysql innodb的默認的頁大小是16k,每個索引會分配在頁上的數量是由字段的大小決定。當字段值的長度越長,每一頁上的數量就會越少,因此在一定數據量的情況下,索引的深度會越深,影響索引的查找效率。
b+tree結構圖
對於復合索引(多列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聯合索引的優勢

  1. 減少開銷
  • 建一個聯合索引 (a,b,c) ,實際相當於建了 (a)、(a,b)、(a,b,c) 三個索引。每多一個索引,都會增加寫操作的開銷和磁盤空間的開銷。對於大量數據的表,使用聯合索引會大大的減少開銷
  1. 覆蓋索引
    對聯合索引 (a,b,c),如果有如下 sql 的,
SELECT a,b,c from table where a='xx' and b = 'xx';
  • 那么 MySQL 可以直接通過遍歷索引取得數據,而無需回表,這減少了很多的隨機 io 操作。減少 io 操作,特別是隨機 io 其實是 DBA 主要的優化策略。所以,在真正的實際應用中,覆蓋索引是主要的提升性能的優化手段之一。
  1. 效率高
    索引列多,通過聯合索引篩選出的數據越少。比如有 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 * 會解析更多的對象,字段,權限,屬性等內容,增加數據庫負擔


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM