基礎規範
- 表存儲引擎必須使用
InnoDB
-
表字符集默認使用
utf8
,必要時候使用utf8mb4
說明: 1)通用,無亂碼風險,漢字3字節,英文1字節 2)`utf8mb4` 是 `utf8` 的超集,有存儲 4 字節例如表情符號時,使用它
-
禁止使用存儲過程,視圖,觸發器,Event
說明: 1)對數據庫性能影響較大,互聯網業務,能讓站點層和服務層乾的事情,不要交到數據庫層 2)調試,排錯,遷移都比較困難,擴展性、移植性較差
- 禁止在數據庫中存儲大文件,例如照片,可以將大文件存儲在對象存儲系統,數據庫中存儲路徑
- 禁止在線上環境做數據庫壓力測試
- 測試,開發,線上數據庫環境必須隔離
命名規範
- 庫名與應用名稱儘量一致。
-
表名、字段名必須使用小寫字母或數字,禁止出現數字開頭,禁止兩個下劃線中間只
出現數字。說明:MySQL 在 Windows 下不區分大小寫,但在 Linux 下默認是區分大小寫。因此,數據庫名、表名、字段名,都不允許出現任何大寫字母,避免節外生枝。 正例:`aliyun_admin`,`rdc_config`,`level3_name` 反例:`AliyunAdmin`,`rdcConfig`,`level_3_name`
- 庫名,表名,列名必須見名知義,長度不要超過 32 字符
說明:tmp
,wushan
誰知道這些庫是幹嘛的 - 禁用保留字,如
desc
、range
、match
、delayed
等,請參考 MySQL 官方保留字。 - 庫備份必須以
bak
爲前綴,以日期爲後綴 - 從庫必須以
-s
爲後綴 - 備庫必須以
-ss
爲後綴
表設計規範
- 單實例表個數必須控制在 2000 個以內
- 單表分表個數必須控制在 1024 個以內
- 表必須有主鍵,推薦使用
UNSIGNED
整數爲主鍵
潛在坑:刪除無主鍵的表,如果是 row 模式的主從架構,從庫會掛住 - 禁止使用外鍵,如果要保證完整性,應由應用程式實現
說明:外鍵使得表之間相互耦合,影響update
/delete
等 SQL 性能,有可能造成死鎖,高併發情況下容易成爲數據庫瓶頸 - 建議將大字段,訪問頻度低的字段拆分到單獨的表中存儲,分離冷熱數據
-
單錶行數超過 500 萬行或者單表容量超過 2GB,才推薦進行分庫分表。
說明:如果預計三年後的數據量根本達不到這個級別,請不要在創建表時就分庫分表。
- 表名不使用複數名詞。
說明:表名應該僅僅表示表裏面的實體內容,不應該表示實體數量,對應類名也是單數形式,符合表達習慣。 - 表的命名最好是加上“業務名稱_表的作用”。
正例:alipay_task
/force_project
/trade_config
- 表必備三字段:
id
,gmt_create
,gmt_modified
。
說明:其中id
必爲主鍵,類型爲unsigned bigint
、單表時自增、步長爲 1。gmt_create
,gmt_modified
的類型均爲date_time
類型,前者現在時表示主動創建,後者過去分詞表示被動更新。
列設計規範
- 小數類型爲
decimal
,禁止使用float
和double
。
說明:float
和double
在存儲的時候,存在精度損失的問題,很可能在值的比較時,得到不正確的結果。 - 如果存儲的數據範圍超過
decimal
的範圍,建議將數據拆成整數和小數分開存儲。 - 根據業務區分使用
tinyint
/int
/bigint
,分別會佔用 1/4/8 字節 -
根據業務區分使用
char
/varchar
說明: 1)字段長度固定,或者長度近似的業務場景,適合使用 `char`,能夠減少碎片,查詢性能高 2)字段長度相差較大,或者更新較少的業務場景,適合使用 `varchar`,能夠減少空間
varchar
是可變長字符串,不預先分配存儲空間,長度不要超過 5000,如果存儲長度大於此值,定義字段類型爲text
,獨立出來一張表,用主鍵來對應,避免影響其它字段索引效率。- 根據業務區分使用
datetime
/timestamp
說明:前者佔用 5 個字節,後者佔用 4 個字節,存儲年使用YEAR
,存儲日期使用DATE
,存儲時間使用datetime
-
必須把字段定義爲
NOT NULL
並設默認值說明: 1)`NULL` 的列使用索引,索引統計,值都更加複雜,MySQL 更難優化 2)`NULL` 需要更多的存儲空間 3)`NULL` 只能採用 `IS NULL` 或者 `IS NOT NULL`,而在 `=`/`!=`/`in`/`not in` 時有大坑
- 使用
INT UNSIGNED
存儲 IPv4,不要用char(15)
-
使用
varchar(20)
存儲手機號,不要使用整數說明: 1)牽扯到國家代號,可能出現 `+/-/()` 等字符,例如 `+86` 2)手機號不會用來做數學運算 3)`varchar` 可以模糊查詢,例如 `like ‘138%’`
- 使用
TINYINT
來代替ENUM
說明:ENUM
增加新值要進行 DDL 操作 -
表達是與否概念的字段,必須使用
is_xxx
的方式命名,數據類型是unsigned tinyint
( 1 表示是,0 表示否)。說明:任何字段如果爲非負數,必須是 `unsigned`。 正例:表達邏輯刪除的字段名 `is_deleted`,1 表示刪除,0 表示未刪除。
- 如果修改字段含義或對字段表示的狀態追加時,需要及時更新字段註釋。
-
字段允許適當冗餘,以提高查詢性能,但必須考慮數據一致。冗餘字段應遵循:
1)不是頻繁修改的字段。 2)不是 `varchar` 超長字段,更不能是 `text` 字段。 正例:商品類目名稱使用頻率高,字段長度短,名稱基本一成不變,可在相關聯的表中冗餘存儲類目名稱,避免關聯查詢。
-
合適的字符存儲長度,不但節約數據庫表空間、節約索引存儲,更重要的是提升檢索速度。
正例:如下表,其中無符號值可以避免誤存負數,且擴大了表示範圍。
對象 | 年齡區間 | 類型 | 字節 | 表示範圍 |
---|---|---|---|---|
人 | 150 歲之內 | unsigned tinyint | 1 | 0 到 255 |
龜 | 數百歲 | unsigned smallint | 2 | 無符號值:0 到 65535 |
恐龍化石 | 數千萬年 | unsigned int | 4 | 無符號值:0 到約 42.9 億 |
太陽 | 約 50 億年 | unsigned bigint | 8 | 無符號值:0 到約 10 的 19 次方 |
索引規範
-
主鍵索引名爲
pk_[字段名]
;唯一索引名爲uk_[字段名]
;普通索引名則爲idx_[字段名]
;單張表索引數量建議控制在5個以內。
說明:1)`pk_` 即 primary key;`uk_` 即 unique key;`idx_` 即 index 的簡稱。 2)互聯網高併發業務,太多索引會影響寫性能 3)生成執行計劃時,如果索引太多,會降低性能,並可能導致MySQL選擇不到最優索引 4)異常複雜的查詢需求,可以選擇ES等更爲適合的方式存儲
- 組合索引字段數不建議超過 5 個
說明:如果 5 個字段還不能極大縮小 row 範圍,八成是設計有問題 - 超過三個表禁止
join
。需要join
的字段,數據類型必須絕對一致;多表關聯查詢時,保證被關聯的字段需要有索引。
說明:即使雙表join
也要注意表索引、SQL 性能。踩過因爲JOIN
字段類型不一致,而導致全表掃描的坑麼? - 理解組合索引最左前綴原則,避免重複建設索引,如果建立了
(a,b,c)
,相當於建立了(a)
,(a,b)
,(a,b,c)
- 在
varchar
字段上建立索引時,必須指定索引長度,沒必要對全字段建立索引,根據實際文本區分度決定索引長度即可。
說明:索引的長度與區分度是一對矛盾體,一般對字符串類型數據,長度爲 20 的索引,區分度會高達 90% 以上,可以使用count(distinct left(列名, 索引長度))/count(*)
的區分度來確定。 - 頁面搜索嚴禁左模糊或者全模糊,如果需要請走搜索引擎來解決。
說明:索引文件具有 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` 無法排序。
-
利用覆蓋索引來進行查詢操作,避免回表。
說明:如果一本書需要知道第 11 章是什麼標題,會翻開第 11 章對應的那一頁嗎?目錄瀏覽一下就好,這個目錄就是起到覆蓋索引的作用。 正例:能夠建立索引的種類:主鍵索引、唯一索引、普通索引,而覆蓋索引是一種查詢的一種效果,用 `explain` 的結果,`extra` 列會出現:`using index`。
-
利用延遲關聯或者子查詢優化超多分頁場景。
說明:MySQL 並不是跳過 `offset` 行,而是取 `offset+N` 行,然後返回放棄前 `offset` 行,返回 N 行,那當 offset 特別大的時候,效率就非常的低下,要麼控制返回的總頁數,要麼對超過特定閾值的頁數進行 SQL 改寫。 正例:先快速定位需要獲取的 `id` 段,然後再關聯:
SELECT a.* FROM 表 1 a, (select id from 表 1 where 條件 LIMIT 100000,20 ) b where a.id=b.id
-
SQL 性能優化的目標:至少要達到 range 級別,要求是 ref 級別,如果可以是 consts 最好。
說明:1)consts 單表中最多隻有一個匹配行(主鍵或者唯一索引),在優化階段即可讀取到數據。 2)ref 指的是使用普通的索引(normal index)。 3)range 對索引進行範圍檢索。 反例:explain 表的結果,type=index,索引物理文件全掃描,速度非常慢,這個 index 級別比較 range 還低,與全表掃描是小巫見大巫。
-
建組合索引的時候,區分度最高的在最左邊。
正例:如果 `where a=? and b=?` ,a 列的幾乎接近於唯一值,那麼只需要單建 idx_a 索引即可。 說明:存在非等號和等號混合判斷條件時,在建索引時,請把等號條件的列前置。如:`where a>? and b=?` 那麼即使 a 的區分度更高,也必須把 b 放在索引的最前列。
- 防止因字段類型不同造成的隱式轉換,導致索引失效。
-
創建索引時避免有如下極端誤解:
1)寧濫勿缺。認爲一個查詢就需要建一個索引。 2)寧缺勿濫。認爲索引會消耗空間、嚴重拖慢更新和新增速度。 3)抵制惟一索引。認爲業務的惟一性一律需要在應用層通過“先查後插”方式解決。
SQL 語句
-
禁止使用
select *
,只獲取必要字段
說明:1)`select` 會增加 cpu/io/內存/帶寬的消耗 2)指定字段能有效利用索引覆蓋 3)指定字段查詢,在表結構變更時,能保證對應用程序無影響
-
insert
必須指定字段,禁止使用insert into T values()
說明:指定字段插入,在表結構變更時,能保證對應用程序無影響
-
不要使用
count(列名)
或count(常量)
來替代count(*)
,count(*)
是 SQL92 定義的標準統計行數的語法,跟數據庫無關,跟 NULL 和非 NULL 無關。說明:`count(*)` 會統計值爲 `NULL` 的行,而 `count(列名)` 不會統計此列爲 `NULL` 值的行。
count(distinct col)
計算該列除NULL
之外的不重複行數,注意count(distinct col1, col2)
如果其中一列全爲NULL
,那麼即使另一列有不同的值,也返回爲 0。-
當某一列的值全是
NULL
時,count(col)
的返回結果爲 0,但sum(col)
的返回結果爲NULL
,因此使用sum()
時需注意 NPE 問題。正例:可以使用如下方式來避免 `sum` 的 NPE 問題:`SELECT IF(ISNULL(SUM(g)),0,SUM(g)) FROM table;` 註釋:*NPE* 全稱 NullPointerException 是指編程語言中的空指針異常。
-
使用
ISNULL()
來判斷是否爲NULL
值。說明:`NULL` 與任何值的直接比較都爲 `NULL`。 1) `NULL<>NULL` 的返回結果是 `NULL`,而不是 `false`。 2) `NULL=NULL` 的返回結果是 `NULL`,而不是 `true`。 3) `NULL<>1` 的返回結果是 `NULL`,而不是 `true`。
- 在代碼中寫分頁查詢邏輯時,若
count
爲 0 應直接返回,避免執行後面的分頁語句。 -
不得使用外鍵與級聯,一切外鍵概念必須在應用層解決。
說明:以學生和成績的關係爲例,學生表中的 `student_id` 是主鍵,那麼成績表中的 `student_id` 則爲外鍵。如果更新學生表中的 `student_id`,同時觸發成績表中的 `student_id` 更新,即爲級聯更新。外鍵與級聯更新適用於單機低併發,不適合分佈式、高併發集羣;級聯更新是強阻塞,存在數據庫更新風暴的風險;外鍵影響數據庫的插入速度。
- 禁止使用存儲過程,存儲過程難以調試和擴展,更沒有移植性。
- 數據訂正時,刪除和修改記錄時,要先
select
,避免出現誤刪除,確認無誤才能執行更新語句。 in
操作能避免則避免,若實在避免不了,需要仔細評估in
後邊的集合元素數量,控
制在 1000 個之內。-
如果有全球化需要,所有的字符存儲與表示,均以
utf-8
編碼,注意字符統計函數的區別。說明: `SELECT LENGTH("輕鬆工作");` 返回爲 12; `SELECT CHARACTER_LENGTH("輕鬆工作");` 返回爲 4; 如果需要存儲表情,那麼選擇 `utfmb4` 來進行存儲,注意它與 `utf-8` 編碼的區別。
-
TRUNCATE TABLE
比DELETE
速度快,且使用的系統和事務日誌資源少,但TRUNCATE
無事務且不觸發trigger
,有可能造成事故,故不建議在開發代碼中使用此語句。說明:`TRUNCATE TABLE` 在功能上與不帶 `WHERE` 子句的 `DELETE` 語句相同。
以上內容,整理自阿里巴巴 Java開發手冊和58到家MySQL軍規升級版。