「MySQL」數據庫規範


1 建表規約

【強制】表達是與否概念的字段,必須使用 is_xxx 的方式命名,數據類型是 unsigned tinyint (1 表示是,0 表示否)。 說明:任何字段如果為非負數,必須是 unsigned。 注意:數據庫表示是與否的值,使用 tinyint 類型,堅持 is_xxx 的 命名方式是為了明確其取值含義與取值範圍。 正例:表達邏輯刪除的字段名 is_deleted,1 表示刪除,0 表示未刪除。

解讀:從優化角度來講,應該按字段的用途來定義合適的類型。表達是與否,用長度為1個字節的tinyint足以。

【強制】表名、字段名必須使用小寫字母或數字,禁止出現數字開頭,禁止兩個下劃線中間只 出現數字。數據庫字段名的修改代價很大,因為無法進行預發佈,所以字段名稱需要慎重考慮。 說明:MySQL 在 Windows 下不區分大小寫,但在 Linux 下默認是區分大小寫。因此,數據庫名、 表名、字段名,都不允許出現任何大寫字母,避免節外生枝。 正例:aliyun_admin,rdc_config,level3_name 反例:AliyunAdmin,rdcConfig,level_3_name

解讀:Win環境下開發,代碼中用的表名是小寫,本地數據庫用的是大寫,那麼在win環境下沒有問題,但發佈到linux環境會有問題。Linux下MySQL安裝完後默認:區分表名的大小寫,不區分列名的大小寫,總而言之都用小寫就不會出現問題。

MySQL在Linux下數據庫名、表名、列名、別名大小寫規則:

(1)數據庫名與表名是嚴格區分大小寫

(2)表的別名是嚴格區分大小寫

(3)列名與列的別名在所有的情況下均是忽略大小寫的

(4)變量名也是嚴格區分大小寫的

【強制】表名不使用複數名詞。 說明:表名應該僅僅表示表裡面的實體內容,不應該表示實體數量,對應於 DO 類名也是單數形式,符合表達習慣。

【強制】禁用保留字,如 desc、range、match、delayed 等,請參考 MySQL 官方保留字。

【強制】主鍵索引名為 pk_字段名;唯一索引名為 uk_字段名;普通索引名則為 idx_字段名。 說明:pk_ 即 primary key;uk_ 即 unique key;idx_ 即 index 的簡稱

【強制】小數類型為 decimal,禁止使用 float 和 double。 說明:float 和 double 在存儲的時候,存在精度損失的問題,很可能在值的比較時,得到不 正確的結果。如果存儲的數據範圍超過 decimal 的範圍,建議將數據拆成整數和小數分開存儲。

解讀:float和double都是浮點型,而decimal是定點型。MySQL 浮點型和定點型可以用類型名稱後加(M,D)來表示,M表示該值的總共長度,D表示小數點後面的長度。

FLOAT和DOUBLE在不指定精度時,默認會按照實際的精度來顯示,而DECIMAL在不指定精度時,默認整數為10,小數為0。所以建議在定義表時,定義(M,D)。

float和double在設置超過定義長度的數值時,會自動四捨五入,decimal會截斷,並給出一條警告。

精度損失問題:float和double類型的列,在做sum計算時,會丟失精度,而decimal會精確計算。

【強制】如果存儲的字符串長度幾乎相等,使用 char 定長字符串類型。

解讀:從優化角度來說,如果一個表的所有字段都是定長的,那麼每一條數據也就是定長的,數據庫就可以直接計算出下一條數據的偏移量,查詢速度會更快。

【強制】varchar 是可變長字符串,不預先分配存儲空間,長度不要超過 5000,如果存儲長 度大於此值,定義字段類型為 text,獨立出來一張表,用主鍵來對應,避免影響其它字段索 引效率。

解讀:MySQL5.0以上版本,varchar最大可以存儲65535字節數據(內容開頭用1-2個字節存儲長度信息,超過255時用兩個字節,所以最大65535)。 我們通常編碼設置為U8,每個字符最多佔3個字節,那麼最大長度不能超過21845。 若定義的時候超過上述限制,則varchar字段會被強行轉為text類型,併產生warning。 此外,受MYSQL行長度限制影響,MySQL要求一個行的定義長度不能超過65535。若定義的表長度超過這個值,則提示ERROR 1118 (42000): Row size too large。

數據庫中定義的「varchar(20)指的是20個字符」。 關於5000的建議:由於通常定義的U8每個字符佔3個字節,那麼5000字符需要15000個字節,考慮行最大長度限制和別的列,以及查詢性能,推薦5000。

【推薦】表的命名最好是加上“業務名稱_表的作用”。 正例: alipay_task / force_project / trade_config

【推薦】庫名與應用名稱儘量一致。

【推薦】如果修改字段含義或對字段表示的狀態追加時,需要及時更新字段註釋。

【推薦】字段允許適當冗餘,以提高查詢性能,但必須考慮數據一致。冗餘字段應遵循: 1) 不是頻繁修改的字段。 2) 不是 varchar 超長字段,更不能是 text 字段。 正例: 商品類目名稱使用頻率高,字段長度短,名稱基本一成不變,可在相關聯的表中冗餘存儲類目名稱,避免關聯查詢。

【參考】合適的字符存儲長度,不但節約數據庫表空間、節約索引存儲,更重要的是提升檢索速度。 正例: 人都在150歲之內,用tinyint unsigned存儲(取值範圍:0-255)。 其中無符號值可以避免誤存負數, 且擴大了表示範圍


「MySQL」數據庫規範

2 索引規約

【強制】業務上具有唯一特性的字段,即使是多個字段的組合,也必須建成唯一索引。 說明: 不要以為唯一索引影響了 insert 速度,這個速度損耗可以忽略,但提高查找速度是明顯的; 另外,即使在應用層做了非常完善的校驗控制,只要沒有唯一索引,根據墨菲定律,必然有髒數據產生。

解讀:墨菲定律:如果事情有變壞的可能,不管這種可能性有多小,它總會發生。

【強制】超過三個表禁止 join。需要 join 的字段,數據類型必須絕對一致; 多表關聯查詢時,保證被關聯的字段需要有索引。 說明: 即使雙表 join 也要注意表索引、 SQL 性能。

【強制】在 varchar 字段上建立索引時,必須指定索引長度,沒必要對全字段建立索引,根據實際文本區分度決定索引長度即可。 說明: 索引的長度與區分度是一對矛盾體,一般對字符串類型數據,長度為 20 的索引,區分度會高達 90%以上,可以使用 count(distinct left(列名, 索引長度))/count(*)的區分度來確定。

解讀:區分度是指不重複的索引值和數據表的記錄總數的比值,範圍(0,1],值越高則查詢效率越高。 對於blob,text,varchar的列必須使用前綴索引,MySQL不允許索引這些列的完整長度。 最好選擇足夠長的前綴保證較高的區分度,也不能太長(節省空間)。

【強制】頁面搜索嚴禁左模糊或者全模糊,如果需要請走搜索引擎來解決。 說明: 索引文件具有 B-Tree 的最左前綴匹配特性,如果左邊的值未確定,那麼無法使用此索引。

【推薦】如果有 order by 的場景,請注意利用索引的有序性。 order by 最後的字段是組合索引的一部分,並且放在索引組合順序的最後,避免出現 file_sort 的情況,影響查詢性能。 正例: where a=? and b=? order by c; 索引: a_b_c 反例: 索引中有範圍查找,那麼索引有序性無法利用,如: WHERE a>10 ORDER BY b; 索引a_b 無法排序。

解讀:file_sort即在order by時發生的文件排序,如果排序時沒有用到索引,就會產生file_sort。 只有當索引的列順序和order by子句的順序完全一致,並且所有列的排序方向都一樣時,才能用索引排序。如果查詢需要關聯多張表,只有當order by子句引用的字段全部為第一個表時,才能使用索引排序。

【推薦】利用覆蓋索引來進行查詢操作, 避免回表。 說明: 如果一本書需要知道第 11 章是什麼標題,會翻開第 11 章對應的那一頁嗎?目錄瀏覽一下就好,這個目錄就是起到覆蓋索引的作用。 正例: 能夠建立索引的種類分為主鍵索引、唯一索引、普通索引三種,而覆蓋索引只是一種查詢的一種效果,用 explain 的結果, extra 列會出現: using index。

解讀:如果一個索引包含所有需要查詢的字段的值,稱之為“覆蓋索引”。 由於覆蓋索引必須要存儲索引列的值,哈希索引、空間索引和全文索引都不存儲列的值,MySQL只有B-Tree索引可以做覆蓋索引。如:對id,name,title三個字段建立索引,在索引中會存儲這三個列的值,如果查詢:select id,name,title from table where id < 10; 通過explain會看到extra為using index。

如果查詢select * from table where id < 10;就不會使用覆蓋索引,因為索引中沒有包含所有的列值。

【推薦】利用延遲關聯或者子查詢優化超多分頁場景。 說明: MySQL 的limit查詢並不是跳過 offset 行,而是取 offset+N 行,然後返回放棄前 offset 行,返回N 行,那當 offset 特別大的時候,效率就非常的低下,要麼控制返回的總頁數,要麼對超過特定閾值的頁數進行 SQL 改寫。 解讀:案例

<code>select count(*) from user_game_info; // 共有956176條數據

select * from user_game_info a limit 900000, 20; // 此查詢耗時0.547S

select t1.* from user_game_info t1, (select id from user_game_info limit 900000, 20) t2 where t1.id = t2.id; // 優化後耗時0.178S/<code>

【推薦】 SQL 性能優化的目標:至少要達到 range 級別, 要求是 ref 級別, 如果可以是 consts最好。 說明: 1) consts 單表中最多隻有一個匹配行( 主鍵或者唯一索引) ,在優化階段即可讀取到數據。 2) ref 指的是使用普通的索引( normal index) 。 3) range 對索引進行範圍檢索。 反例: explain 表的結果, type=index,索引物理文件全掃描,速度非常慢,這個 index 級別比較 range 還低,與全表掃描是小巫見大巫。

解讀:這裡說的是explain中的type字段(連接類型)。 常見的幾種類型有:all、index、range、ref、eq_ref、const,從左到右效率依次增強。

1)All:全表掃描

2)Index:先掃描全部索引,再回表獲取數據,性能不比all強

3)Range:有範圍的索引掃描,between/and/>/

4)Ref:查找條件列使用了索引而且不為主鍵和unique

5)Eq_ref:優化器已知查詢結果只有一個,在使用了主鍵或唯一索引的情況下觸發

6)Const:將主鍵放到where後面做等值查詢,例如:select * from user_game_info where id = 100;

【推薦】建組合索引的時候,區分度最高的在最左邊。 正例: 如果 where a=? and b=? , 如果 a 列的幾乎接近於唯一值,那麼只需要單建 idx_a索引即可。 說明: 存在非等號和等號混合時,在建索引時,請把等號條件的列前置。如: where c>? and d=? 那麼即使 c 的區分度更高,也必須把 d 放在索引的最前列, 即索引 idx_d_c。

【推薦】 防止因字段類型不同造成的隱式轉換,導致索引失效。

解讀:例如,給表tb_user_account表的username(varchar)字段加索引,由於字段是varchar類型的,所以查詢條件一定要少字符串類型

隱式轉換規則: 1)兩個參數至少有一個是 NULL 時,比較的結果也是 NULL,例外是使用 <=> 對兩個 NULL 做比較時會返回 1,這兩種情況都不需要做類型轉換

2)兩個參數都是字符串,會按照字符串來比較,不做類型轉換

3)兩個參數都是整數,按照整數來比較,不做類型轉換

4)十六進制的值和非數字做比較時,會被當做二進制串

5)有一個參數是 TIMESTAMP 或 DATETIME,並且另外一個參數是常量,常量會被轉換為 timestamp

6)有一個參數是 decimal 類型,如果另外一個參數是 decimal 或者整數,會將整數轉換為 decimal 後進行比較,如果另外一個參數是浮點數,則會把 decimal 轉換為浮點數進行比較

7)所有其他情況下,兩個參數都會被轉換為浮點數再進行比較

【參考】創建索引時避免有如下極端誤解: 1) 寧濫勿缺。 認為一個查詢就需要建一個索引。 2) 寧缺勿濫。 認為索引會消耗空間、嚴重拖慢更新和新增速度。 3) 抵制唯一索引。 認為業務的唯一性一律需要在應用層通過“先查後插”方式解決。

3 SQL語句

【強制】不要使用 count(列名)或 count(常量)來替代 count(), count()是 SQL92 定義的標準統計行數的語法,跟數據庫無關,跟 NULL 和非 NULL 無關。 說明: count(*)會統計值為 NULL 的行,而 count(列名)不會統計此列為 NULL 值的行。

【強制】 count(distinct col) 計算該列除 NULL 之外的不重複行數, 注意 count(distinctcol1, col2) 如果其中一列全為 NULL,那麼即使另一列有不同的值,也返回為 0。

【強制】當某一列的值全是 NULL 時, count(col)的返回結果為 0,但 sum(col)的返回結果為NULL,因此使用 sum()時需注意 NPE 問題。 正例: 可以使用如下方式來避免 sum 的 NPE(null point Exception) 問題: SELECT IF(ISNULL(SUM(g)),0,SUM(g))FROM table;

【強制】使用 ISNULL()來判斷是否為 NULL 值。 說明: NULL 與任何值的直接比較都為 NULL。 1) NULL<>NULL 的返回結果是 NULL, 而不是 false。 2) NULL=NULL 的返回結果是 NULL, 而不是 true。 3) NULL<>1 的返回結果是 NULL,而不是 true。

【強制】 在代碼中寫分頁查詢邏輯時,若 count 為 0 應直接返回,避免執行後面的分頁語句。 解讀:先查詢COUNT,後查詢分頁數據

【強制】不得使用外鍵與級聯,一切外鍵概念必須在應用層解決。 說明:以學生和成績的關係為例,學生表中的 student_id是主鍵,那麼成績表中的 student_id則為外鍵。如果更新學生表中的 student_id,同時觸發成績表中的 student_id 更新, 即為級聯更新。外鍵與級聯更新適用於單機低併發,不適合分佈式、高併發集群; 級聯更新是強阻塞,存在數據庫更新風暴的風險; 外鍵影響數據庫的插入速度。

【強制】禁止使用存儲過程,存儲過程難以調試和擴展,更沒有移植性。

【強制】數據訂正(特別是刪除、 修改記錄操作) 時,要先 select,避免出現誤刪除,確認無誤才能執行更新語句。

解讀:手動執行SQL來修改或刪除數據時,先用where後的條件select一遍,確認數據無誤後,在執行update或delete。

【推薦】 in 操作能避免則避免,若實在避免不了,需要仔細評估 in 後邊的集合元素數量,控制在 1000 個之內。

【參考】 如果有國際化需要,所有的字符存儲與表示,均以 utf-8 編碼,注意字符統計函數的區別。 說明:SELECT LENGTH(“輕鬆工作”); 返回為 12,統計字節數SELECT CHARACTER_LENGTH(“輕鬆工作”); 返回為 4,統計字符數如果需要存儲表情,那麼選擇 utf8mb4 來進行存儲,注意它與 utf-8 編碼的區別。 解讀:utf8可以存儲3個字節的數據,utf8mb4可以存儲四個字節,專門用來兼容4個字節的unicode,utf8mb4是utf8的超集,將編碼從utf8改為utf8mb4無需額外轉換。 Emoji表情不在utf8的3個字節的表示範圍之內,可以用utf8mb4存儲。

4 ORM映射

【強制】在表查詢中,一律不要使用 * 作為查詢的字段列表,需要哪些字段必須明確寫明。 說明: 1)增加查詢分析器解析成本。 2)增減字段容易與 resultMap 配置不一致。 3)無用字段增加網絡消耗,尤其是 text 類型的字段。

解讀:可以更好的利用“覆蓋索引”。

【參考】 @Transactional 事務不要濫用。事務會影響數據庫的 QPS,另外使用事務的地方需要考慮各方面的回滾方案,包括緩存回滾、搜索引擎回滾、消息補償、統計修正等。


分享到:


相關文章: