作者:IT王小二
上一篇給小伙伴們講了關於SQL查詢性能優化的相關技巧,一個好的查詢SQL離不開合理的索引設計。這篇小二就來嘮一嘮怎么合理的設計一個索引來優化我們的查詢速度,要是有不合理的地方...嗯..
當然啦,開個玩笑,歡迎小伙伴們指正!
一、索引設計基石
索引設計基石是什么呢?
- 小二認為索引設計的基石就是數據表字段類型的合理設計,即選擇合適字段類型和設置合適的長度。
- 選擇正確的數據類型,那么在字段上建立索引時,一個數據頁可以存儲更多的索引,一次讀取加載到內存的索引個數更多,同時降低B+tree的高度,減少磁盤IO,對提升MySQL的性能提升有着極大的意義。
通常情況下,字段類型的選擇是需要根據業務來判斷的,通常需要遵循以下幾點。
- 確定合適的大類型:數字、字符串、日期和時間、二進制等。
- 確定具體的類型:有無符號、取值范圍、變長定長等。
- 盡量選擇更小的數據類型,因為它們通常有更好的性能,占用更少的硬件資源。
- 盡量把字段定義為
NOT NULL
,避免使用NULL
。
下列各種類型表格內容來自菜鳥教程,權當備忘。
1. 數值類型
類型 | 大小(bytes) | 范圍(有符號) | 范圍(無符號) | 用途 |
---|---|---|---|---|
TINYINT | 1 | (-128, 127) | (0, 255) | 小整數值 |
SMALLINT | 2 | (-32768, 32767) | (0, 65535) | 大整數值 |
MEDIUMINT | 3 | (-8388608, 8388 607) | (0, 16777215) | 大整數值 |
INT或INTEGER | 4 | (-2147483648, 2147483647) | (0, 4294967295) | 大整數值 |
BIGINT | 8 | (-9233372036854775808, 9223372036854775807) | (0, 18446744073 709551615) | 極大整數值 |
FLOAT | 4 | (-3.402823466E+38, 1.175494351E-38),0,(1.175494351E-38,3.402823466351E+38) | 0, (1.175494351E-38, 3.402823466E+38) | 單精度浮點數值 |
DOUBLE | 8 | (1.7976931348623157E+308, 2.2250738585072014E-308), 0, (2.2250738585072014E-308, 1.7976931348623157E+308) | 0, (2.2250738585072014E-308, 1.7976931348623157E+308) | 雙精度浮點數值 |
DECIMAL | 對DECIMAL(M,D) ,如果M>D,為M+2否則為D+2 | 依賴於M和D的值 | 依賴於M和D的值 | 小數值 |
優化建議:
- 如果整型數據沒有負數,如id號,建議指定為UNSIGNED無符號類型,容量可以擴大一倍。
- 整數通常是最佳的數據類型,因為它速度快,並且能使用AUTO_INCREMENT。
- 建議使用TINYINT代替ENUM、BITENUM、SET。
- 建議使用整型類型來運算和存儲實數,一種方法是實數乘以相應的倍數后再操作;另外一種方法是使用兩個字段來分別存儲整數位和小數位。
- DECIMAL最適合保存准確度要求高並且用於計算的數據,比如價格、金額等,但是在使用DECIMAL類型的時候注意長度設置。
- 避免使用整數的顯示寬度,也就是說不要用INT(5)類似的方法指定字段顯示寬度,直接用INT。
注意: INT(2)設置的為顯示寬度,而不是整數的長度,需要配合ZEROFILL
使用 。
CREATE TABLE user(
id TINYINT(2) UNSIGNED
);
例如id
設置為 TINYINT(2) UNSIGNED
,表示無符號,可以存儲的最大數值為255,其中TINYINT(2)
沒有配合ZEROFILL
實際沒有任何意義,例如插入數字200,長度雖然超過了兩位,但是這個時候是可以插入成功的,查詢結果同樣為200;插入數字5時,同樣查詢結果為5。
CREATE TABLE user(
id TINYINT(2) UNSIGNED ZEROFILL
);
而TINYINT(2)
配合ZEROFILL
后,當插入數字5時,實際存儲的還是5,不過在查詢是MySQL會在前面補上一個0,即查詢出來的實際為05
。
2. 字符串類型
類型 | 大小(bytes) | 用途 |
---|---|---|
CHAR | 0-255 | 定長字符串,char(n)當插入的字符數不足n時(n代表字符數),插入空格進行補充保存。在進行檢索時,尾部的空格會被去掉。 |
VARCHAR | 0-65535 | 變長字符串,varchar(n)中的n代表最大字符數,插入的字符數不足n時不會補充空格 |
TINYBLOB | 0-255 | 不超過 255 個字符的二進制字符串 |
TINYTEXT | 0-255 | 短文本字符串 |
BLOB | 0-65535 | 二進制形式的長文本數據 |
TEXT | 0-65535 | 長文本數據 |
MEDIUMBLOB | 0-16777215 | 二進制形式的中等長度文本數據 |
MEDIUMTEXT | 0-16777215 | 中等長度文本數據 |
LONGBLOB | 0-4294967295 | 二進制形式的極大文本數據 |
LONGTEXT | 0-4294967295 | 極大文本數據 |
優化建議:
- 當字符串短,並且所有值都固定一個長度或者接近一個長度時使用CHAR,當然要是如果沒有完全可以使用整型來存儲;字符串長度相差較大時使用VARCHAR。
- CHAR和VARCHAR適合長度不超過255個字符唱的的任意字母和數字組合,例如人名、電話號碼、編碼等。用來計算的數字不要用VARCHAR類型保存,因為可能會導致一些與計算相關的問題,同時可能影響到計算的准確性和完整性。
- VARCHAR(255)在建立索引時會占用比較多的存儲空間,在不要求保證數據完全精確的境況下可以使用前綴索引。例如
idx_name_age_position(name(20), age, position)
,取前20個字符作為索引,但是這種情況下因為是不完全字段,所以order by name asc
或者group by name
排序過程無法使用索引排序。當然需要保證數據的精確性和查找速度,最優的方案就是使用全文搜索引擎ES了。 - 盡量不用BLOB和TEXT,如果實在要用可以考慮將BLOB和TEXT字段單獨存一張表,使用主鍵id來關聯。
- BLOB和TEXT都不能有默認值。BLOB系列存儲二進制字符串,與字符集無關;TEXT系列存儲非二進制字符串,與字符集相關。
3. 時間類型
類型 | 大小(bytes) | 范圍 | 格式 | 用途 |
---|---|---|---|---|
DATE | 3 | 1000-01-01 到 9999-12-31 | YYYY-MM-DD | 日期值 |
TIME | 3 | '-838:59:59' 到 '838:59:59' | HH:MM:SS | 時間值或持續時間 |
YEAR | 1 | 1901 到 2155 | YYYY | 年份值 |
DATETIME | 8 | 1000-01-01 00:00:00 到 9999-12-31 23:59:59 | YYYY-MM-DD HH:MM:SS | 混合日期和時間值 |
TIMESTAMP | 4 | 1970-01-01 00:00:00 到 2038-01-19 03:14:07 (格林尼時間) | YYYYMMDD HHMMSS | 混合日期和時間值,時間戳 |
優化建議:
- MySQL能存儲的最小時間粒度為秒。當然要是需要精確到毫秒級的話,當然也是有辦法的,新加一列在另外一列保留毫秒值即可。
- 建議使用MySQL的內建類型DATE、TIME、DATETIME來存儲時間,而不是使用字符串或者保存時間戳,這樣的話可以通過MySQL的日期函數處理相關邏輯。
- 當不需要具體時間時,建議用DATE數據類型來保存日期,MySQL中默認的日期格式是
YYYY-MM-DD
。 - 當數據格式為TIMESTAMP和DATETIME時,可以用
CURRENT_TIMESTAMP
作為(MySQL5.6以后),MySQL會自動返回記錄插入的當前確切時間。不過需要注意的是校准MySQL運行環境的時間和時區,比如Linux時間或者docker容器的時間和時區。 - TIMESTAMP是UTC時間戳,與時區相關;DATETIME的存儲格式是一個YYYYMMDD HH:MM:SS的整數,與時區無關,存的什么讀出來就是什么。
- 一般的短期項目或者小公司項目小二建議使用TIMESTAMP,因為這種項目生命往往活不到2038年,DATETIME還更節約空間。但是如果是騰訊、阿里、京東一般會用DATETIME,因為不用考慮TIMESTAMP將來的時間上限問題。
二、索引設計原則
1. 索引未建,代碼先行
通常來說,考慮好表中每個字段應該使用什么類型和長度,建完表需要做的事情不是馬上建立索引,而是先把相關主體業務開發完畢,然后把涉及該表的SQL都拿出來分析之后再建立索引。
2. 聯合索引盡量覆蓋條件
盡量少建立單值索引(唯一索引除外),應當設計一個或者兩三個聯合索引,讓每一個聯合索引都盡量去包含SQL語句中的where、order by、group by
的字段,同時確保聯合索引的字段順序盡量滿足SQL查詢的最左前綴原則。
3. 不要在小基數字段上建立索引
索引基數是指這個字段在表里總共有多少個不同的值,比如一張表總共100萬行記錄,其中有個性別字段,性別一共有三個值:男、女、保密,那么該字段的基數就是3。
如果對這種小基數字段建立索引的話,因為索引樹中只有男、女、保密三個值,根本沒法進行快速的二分查找,同時還需要回表查詢,還不如全表掃描嘞。
一般建立索引,盡量使用那些基數比較大的字段,那么才能發揮出B+樹快速二分查找的優勢來。
4. where與order by沖突時優先where
在where
和order by
出現索引設計沖突時,是優先針對where去設計索引?還是優先針對order by設計索引?
通常情況下都是優先針對where
來設計索引,因為通常情況下都是先where
條件使用索引快速篩選出來符合條件的數據,然后對進行篩選出來的數據進行排序和分組,而where
條件快速篩選出來的的數據往往不會很多。
5. 對慢查詢SQL進行優化
對生產實際運行過程中,或者測試環境大數據量測試過程中發現的慢查詢SQL進行特定的索引優化、代碼優化等策略。
三、索引設計實戰
終於輪到實戰了,小二最喜歡實戰了。
寫到這里不得不吐槽一下,這個金三銀四的跳槽季節,年前提離職了,結果離職還沒辦完就封村整整兩個禮拜了,嗚嗚嗚...
上節小二就提到會有個很有意思的小案例,那么在疫情當下,門都出不去的日子,感覺這個例子更有意思了,咱們來討論一下各種社交平台怎么做的用戶信息搜索呢。
社交平台有一個小伙伴們都喜歡的功能,搜索好友信息,比如小二熟練的點開省份...城市..性別..年齡..身高...
咳咳咳...小二怎么可能干這種事情,小二的心里只有代碼,嗯...沒錯,就是這樣。
這個就可以說是對於用戶信息的查詢篩選了,通常這種表都是非常大數據量的,在不考慮分庫分表的情況下,怎么通過索引配合SQL來優化呢?
通常我們在編寫SQL是會寫出類似如下的SQL來執行,有where、order by、limit
等條件來查詢。
select xx from user where xx=xx and xx=xx order by xx asc limit xx,xx;
那么接下來小二一個一個慢慢增加字段來分析分析,怎么根據業務場景來設計索引。
例如通常小伙伴們都會優先篩選出自己所屬城市和性別的人,那么該怎么設計索引呢?
where province = xx and city = xx and sex = xx
針對這種情況,很簡單,設計一個聯合索引(provice, city, sex)
就完事了。
那么這個時候小伙伴肯定又要瞅瞅年齡段了,嘿嘿😊
where province = xx and city = xx and sex = xx and age >= 18 and age <= 28
那么這時候有小伙伴就會說了,很簡單啊,范圍字段放最后咱還是知道的,聯合索引改成(provice, city, sex, age)
不就可以了。
嗯,是的,這么干沒毛病,但是小伙伴們有沒有想過有些人萬一既喜歡帥哥又喜歡美女,別想歪了哈...,挺多小姐姐就既喜歡帥哥又喜歡美女的。
那么這個時候小姐姐就不搜索性別了,那么這個時候聯合索引只能用到前兩個字段了,那么不符合咱們的專業標准啊,咋辦呢?這時候還是有辦法的,咱們只需要動動小腦袋改改SQL就行了,在沒有選擇性別時判斷一下,改成下面這樣就可以了。
province=xx and city=xx and sex in ('male','female') and age >= 18 and age <= 28
那么有愛好之類的其他等值字段。
province=xx and city=xx and sex in ('male','female') and hobby = 1 and xx = xx and age >= 18 and age <= 28
咋辦嘞,同樣往聯合索引里面塞,例如(provice, city, sex, hobby, xx, age)
。
那么如果還有范圍查詢,比如身高、體重范圍和最后登錄時間等等。
針對這種多個范圍查詢的話,為了比較好的利用索引,在業務允許的情況下可以使用固定范圍,然后數據庫字段存儲范圍標識就可以了,這樣就轉化為了等值匹配,就可以很好地利用索引了。
例如最后登錄時間字段不記錄最后登錄時間,而是記錄設置字段 is_login_within_seven_days
在7天內有登錄則為1,否則為0,最后索引設計成(provice, city, sex, hobby, xx, is_login_within_seven_days, age)
。
那么根據場景最后設計出來的這個索引可能已經可以覆蓋大部分的查詢流量了,那么如果還有其他一部分熱度比較高的查詢怎么辦呢,辦法也很簡單啊,再加一兩個索引即可。
例如通常會查詢這個城市比較受歡迎(評分:score)的小姐姐,這時候添加一個聯合索引(provice, city, sex, score)
那么就可以了。
可以看出,索引時必須結合場景來設計的,思路就是盡量用不超過3個復雜的聯合索引來抗住大部分的80%以上的常用查詢流量,然后再用一兩個二級索引來抗下一些非常用查詢流量。
以上就是小二要給大家分享的索引設計,如果能動動你發財的小手給小二點個免費的贊就更好啦~
下篇小二就來講講MySQL事務和鎖機制。