去哪兒MySQL開發規範
1.命名規範
(1)庫名、表名、欄位名必須使用小寫字母,並採用下劃線分割。
(2)庫名、表名、欄位名禁止超過32個字元。
(3)庫名、表名、欄位名必須見名知意。命名與業務、產品線等相關聯。
(4)庫名、表名、欄位名禁止使用MySQL保留字。(保留字列表見官方網站)
(5)臨時庫、表名必須以tmp為字首,並以日期為字尾。例如 tmp_test01_20130704。
(6)備份庫、表必須以bak為字首,並以日期為字尾。例如 bak_test01_20130704。
2.基礎規範
(1)使用INNODB儲存引擎。
(2)表字符集使用使用UTF8MB4字符集。
(3)所有表都需要添加註釋;除主鍵外的其他欄位都需要增加註釋。推薦採用英文標點,避免出現亂碼。
(4)禁止在資料庫中儲存圖片、檔案等大資料。
(5)每張表資料量建議控制在5000W以內。
(6)禁止在線上做資料庫壓力測試。
(7)禁止從測試、開發環境直連資料庫。
3.庫表設計
(1)禁止使用分割槽表。
(2)將大欄位、訪問頻率低的欄位拆分到單獨的表中儲存,分離冷熱資料。
(3)推薦使用HASH進行散表,表名字尾使用十進位制數,數字必須從0開始。
(4)按日期時間分表需符合YYYY[MM][DD][HH]格式,例如2013071601。年份必須用4位數字表示。例如按日散表user_20110209、 按月散表user_201102。
(5)採用合適的分庫分表策略。例如千庫十表、十庫百表等。
4.欄位設計
(1)建議使用UNSIGNED儲存非負數值。
(2)建議使用INT UNSIGNED儲存IPV4。
(3)用DECIMAL代替FLOAT和DOUBLE儲存精確浮點數。例如與貨幣、金融相關的資料。
(4)INT型別固定佔用4位元組儲存,例如INT(4)僅代表顯示字元寬度為4位,不代表儲存長度。
(5)區分使用TINYINT、SMALLINT、MEDIUMINT、INT、BIGINT資料型別。例如取值範圍為0-80時,使用TINYINT UNSIGNED。
(6)強烈建議使用TINYINT來代替ENUM型別。
(7)儘可能不使用TEXT、BLOB型別。
(8)禁止在資料庫中儲存明文密碼。
(9)使用VARBINARY儲存大小寫敏感的變長字串或二進位制內容。
(10)使用盡可能小的VARCHAR欄位。VARCHAR(N)中的N表示字元數而非位元組數。
(11)區分使用DATETIME和TIMESTAMP。儲存年使用YEAR型別。儲存日期使用DATE型別。 儲存時間(精確到秒)建議使用TIMESTAMP型別。
(12)所有欄位均定義為NOT NULL。
5.索引規範
(1)單張表中索引數量不超過5個。
(2)單個索引中的欄位數不超過5個。
(3)索引名必須全部使用小寫。
(4)非唯一索引按照“idx_欄位名稱[_欄位名稱]”進用行命名。例如idx_age_name。
(5)唯一索引按照“uniq_欄位名稱[_欄位名稱]”進用行命名。例如uniq_age_name。
(6)組合索引建議包含所有欄位名,過長的欄位名可以採用縮寫形式。例如idx_age_name_add。
(7)表必須有主鍵,推薦使用UNSIGNED自增列作為主鍵。
(8)唯一鍵由3個以下欄位組成,並且欄位都是整形時,可使用唯一鍵作為主鍵。其他情況下,建議使用自增列或發號器作主鍵。
(9)禁止冗餘索引。
(10)禁止重複索引。
(11)禁止使用外來鍵。
(12)聯表查詢時,JOIN列的資料型別必須相同,並且要建立索引。
(13)不在低基數列上建立索引,例如“性別”。
(14)選擇區分度大的列建立索引。組合索引中,區分度大的欄位放在最前。
(15)對字串使用字首索引,字首索引長度不超過8個字元。
(16)不對過長的VARCHAR欄位建立索引。建議優先考慮字首索引,或新增CRC32或MD5偽列並建立索引。
(17)合理建立聯合索引,(a,b,c) 相當於 (a) 、(a,b) 、(a,b,c)。
(18)合理使用覆蓋索引減少IO,避免排序。
6.SQL設計
(1)使用prepared statement,可以提升效能並避免SQL注入。
(2)使用IN代替OR。SQL語句中IN包含的值不應過多,應少於1000個。
(3)禁止隱式轉換。數值型別禁止加引號;字串型別必須加引號。
(4)避免使用JOIN和子查詢。必要時推薦用JOIN代替子查詢。
(5)禁止在MySQL中進行數學運算和函式運算。
(6)減少與資料庫互動次數,儘量採用批量SQL語句。
(7)拆分複雜SQL為多個小SQL,避免大事務。
(8)獲取大量資料時,建議分批次獲取資料,每次獲取資料少於2000條,結果集應小於1M。
(9)使用UNION ALL代替UNION。
(10)統計行數使用COUNT(*)。
(11)SELECT只獲取必要的欄位,禁止使用SELECT *。
(12)SQL中避免出現now()、rand()、sysdate()、current_user()等不確定結果的函式。
13)INSERT語句必須指定欄位列表,禁止使用 INSERT INTO TABLE()。
(14)禁止單條SQL語句同時更新多個表。
(15)禁止使用儲存過程、觸發器、檢視、自定義函式等。
(16)建議使用合理的分頁方式以提高分頁效率。
(17)禁止在從庫上執行後臺管理和統計類功能的QUERY,必要時申請統計類從庫。
(18)程式應有捕獲SQL異常的處理機制,必要時通過rollback顯式回滾。
(19)重要SQL必須被索引:update、delete的where條件列、order by、group by、distinct欄位、多表join欄位。
(20)禁止使用%前導查詢,例如:like “%abc”,無法利用到索引。
(21)禁止使用負向查詢,例如 not in、!=、not like。
(22)使用EXPLAIN判斷SQL語句是否合理使用索引,儘量避免extra列出現:Using File Sort、Using Temporary。
(23)禁止使用order by rand()。
7.行為規範
(1)表結構變更必須通知DBA進行稽核。
(2)禁止有super許可權的應用程式賬號存在。
(3)禁止有DDL、DCL許可權的應用程式賬號存在。
(4)重要專案的資料庫方案選型和設計必須提前通知DBA參與。
(5)批量匯入、匯出資料必須通過DBA稽核,並在執行過程中觀察服務。
(6)批量更新資料,如UPDATE、DELETE操作,必須DBA進行稽核,並在執行過程中觀察服務。
(7)產品出現非資料庫導致的故障時,如被攻擊,必須及時通DBA,便於維護服務穩定。
(8)業務部門程式出現BUG等影響資料庫服務的問題,必須及時通知DBA,便於維護服務穩定。
(9)業務部門推廣活動或上線新功能,必須提前通知DBA進行服務和訪問量評估,並留出必要時間以便DBA完成擴容。
(10)出現業務部門人為誤操作導致資料丟失,需要恢復資料的,必須第一時間通知DBA,並提供準確時間點、 誤操作語句等重要線索。
(11)提交線上建表改表需求,必須詳細註明涉及到的所有SQL語句(包括INSERT、DELETE、UPDATE),便於DBA進⾏行稽核和優化。
(12)對同一個表的多次alter操作必須合併為一次操作。
(13)不要在MySQL資料庫中存放業務邏輯。
8.FAQ
1.庫名、表名、欄位名必須使用小寫字母,並採用下劃線分割。
a)MySQL有配置引數lower_case_table_names,不可動態更改,linux系統預設為 0,即庫表名以實際情況儲存,大小寫敏感。如果是1,以小寫儲存,大小寫不敏感。如果是2,以實際情況儲存,但以小寫比較。
b)如果大小寫混合使用,可能存在abc,Abc,ABC等多個表共存,容易導致混亂。
c)欄位名顯式區分大小寫,但實際使用不區分,即不可以建立兩個名字一樣但大小寫不一樣的欄位。
d)為了統一規範, 庫名、表名、欄位名使用小寫字母。
2.庫名、表名、欄位名禁止超過32個字元。
庫名、表名、欄位名支援最多64個字元,但為了統一規範、易於辨識以及減少傳輸量,禁止超過32個字元。
3.使用INNODB儲存引擎。
INNODB引擎是MySQL5.5版本以後的預設引擘,支援事務、行級鎖,有更好的資料恢復能力、更好的併發效能,同時對多核、大記憶體、SSD等硬體支援更好,支援資料熱備份等,因此INNODB相比MyISAM有明顯優勢。
4.庫名、表名、欄位名禁止使用MySQL保留字。
當庫名、表名、欄位名等屬性含有保留字時,SQL語句必須用反引號引用屬性名稱,這將使得SQL語句書寫、SHELL指令碼中變數的轉義等變得非常複雜。
5.禁止使用分割槽表。
分割槽表對分割槽鍵有嚴格要求;分割槽表在表變大後,執行DDL、SHARDING、單表恢復等都變得更加困難。因此禁止使用分割槽表,並建議業務端手動SHARDING。
6.建議使用UNSIGNED儲存非負數值。
同樣的位元組數,非負儲存的數值範圍更大。如TINYINT有符號為 -128-127,無符號為0-255。
7.建議使用INT UNSIGNED儲存IPV4。
UNSINGED INT儲存IP地址佔用4位元組,CHAR(15)則佔用15位元組。另外,計算機處理整數型別比字串型別快。使用INT UNSIGNED而不是CHAR(15)來儲存IPV4地址,通過MySQL函式inet_ntoa和inet_aton來進行轉化。IPv6地址目前沒有轉化函式,需要使用DECIMAL或兩個BIGINT來儲存。
例如:
SELECT INET_ATON('209.207.224.40'); 3520061480
SELECT INET_NTOA(3520061480); 209.207.224.40
8.強烈建議使用TINYINT來代替ENUM型別。
ENUM型別在需要修改或增加列舉值時,需要線上DDL,成本較大;ENUM列值如果含有數字型別,可能會引起預設值混淆。
9.使用VARBINARY儲存大小寫敏感的變長字串或二進位制內容。
VARBINARY預設區分大小寫,沒有字符集概念,速度快。
10.INT型別固定佔用4位元組儲存,例如INT(4)僅代表顯示字元寬度為4位,不代表儲存長度。
數值型別括號後面的數字只是表示寬度而跟儲存範圍沒有關係,比如INT(3)預設顯示3位,空格補齊,超出時正常顯示,python、java客戶端等不具備這個功能。
11.區分使用DATETIME和TIMESTAMP。儲存年使用YEAR型別。儲存日期使用DATE型別。 儲存時間(精確到秒)建議使用TIMESTAMP型別。
DATETIME和TIMESTAMP都是精確到秒,優先選擇TIMESTAMP,因為TIMESTAMP只有4個位元組,而DATETIME8個位元組。同時TIMESTAMP具有自動賦值以及自動更新的特性。注意:在5.5和之前的版本中,如果一個表中有多個timestamp列,那麼最多隻能有一列能具有自動更新功能。
如何使用TIMESTAMP的自動賦值屬性?
a)自動初始化,並自動更新: column1 TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
b)只是自動初始化: column1 TIMESTAMP DEFAULT CURRENT_TIMESTAMP
c)自動更新,初始化的值為0: column1 TIMESTAMP DEFAULT 0 ON UPDATE CURRENT_TIMESTAMP
d)初始化的值為0: column1 TIMESTAMP DEFAULT 0
12.所有欄位均定義為NOT NULL。
a)對錶的每一行,每個為NULL的列都需要額外的空間來標識。
b)B樹索引時不會儲存NULL值,所以如果索引欄位可以為NULL,索引效率會下降。
c)建議用0、特殊值或空串代替NULL值。
13.將大欄位、訪問頻率低的欄位拆分到單獨的表中儲存,分離冷熱資料。
有利於有效利用快取,防止讀入無用的冷資料,較少磁碟IO,同時保證熱資料常駐記憶體提高快取命中率。
14.禁止在資料庫中儲存明文密碼。
採用加密字串儲存密碼,並保證密碼不可解密,同時採用隨機字串加鹽保證密碼安全。防止資料庫資料被公司內部人員或黑客獲取後,採用字典攻擊等方式暴力破解使用者密碼。
15.表必須有主鍵,推薦使用UNSIGNED自增列作為主鍵。
表沒有主鍵,INNODB會預設設定隱藏的主鍵列;沒有主鍵的表在定位資料行的時候非常困難,也會降低基於行復制的效率。
16.禁止冗餘索引。
索引是雙刃劍,會增加維護負擔,增大IO壓力。(a,b,c)、(a,b),後者為冗餘索引。可以利用字首索引來達到加速目的,減輕維護負擔。
17.禁止重複索引。
primary key a;uniq index a;重複索引增加維護負擔、佔用磁碟空間,同時沒有任何益處。
18.不在低基數列上建立索引,例如“性別”。
大部分場景下,低基數列上建立索引的精確查詢,相對於不建立索引的全表掃描沒有任何優勢,而且增大了IO負擔。
19.合理使用覆蓋索引減少IO,避免排序。
覆蓋索引能從索引中獲取需要的所有欄位,從而避免回表進行二次查詢,節省IO。INNODB儲存引擎中, secondary index(非主鍵索引,又稱為輔助索引、二級索引)沒有直接儲存行地址,而是儲存主鍵值。如果使用者需要查詢secondary index中所不包含的資料列,則需要先通過secondary index查詢到主鍵值,然後再通過主鍵查詢到其他資料列,因此需要查詢兩次。覆蓋索引則可以在一個索引中獲取所有需要的資料,因此效率較高。主鍵查詢是天然的覆蓋索引。例如SELECT email,uid FROM user_email WHERE uid=xx,如果uid 不是主鍵,適當時候可以將索引新增為index(uid,email),以獲得性能提升。
20.用IN代替OR。SQL語句中IN包含的值不應過多,應少於1000個。
IN是範圍查詢,MySQL內部會對IN的列表值進行排序後查詢,比OR效率更高。
21.表字符集使用UTF8,必要時可申請使用UTF8MB4字符集。
a)UTF8字符集儲存漢字佔用3個位元組,儲存英文字元佔用一個位元組。
b)UTF8統一而且通用,不會出現轉碼出現亂碼風險。
c)如果遇到EMOJ等表情符號的儲存需求,可申請使用UTF8MB4字符集。
22.用UNION ALL代替UNION。
UNION ALL不需要對結果集再進行排序。
23.禁止使用order by rand()。
order by rand()會為表增加一個偽列,然後用rand()函式為每一行資料計算出rand()值,然後基於該行排序, 這通常都會生成磁碟上的臨時表,因此效率非常低。建議先使用rand()函式獲得隨機的主鍵值,然後通過主鍵獲取資料。
24.建議使用合理的分頁方式以提高分頁效率。
第一種分頁寫法:
select *
from t
where thread_id = 771025
and deleted = 0
order by gmt_create asc limit 0, 15;
select * from t
where thread_id = 771025
and deleted = 0
order by gmt_create asc limit 0, 15;
原理:一次性根據過濾條件取出所有欄位進行排序返回。
資料訪問開銷=索引IO+索引全部記錄結果對應的表資料IO
缺點:該種寫法越翻到後面執行效率越差,時間越長,尤其表資料量很大的時候。
適用場景:當中間結果集很小(10000行以下)或者查詢條件複雜(指涉及多個不同查詢欄位或者多表連線)時適用。
第二種分頁寫法:
select t.* from (
select id from t
where thread_id = 771025 and deleted = 0 order by gmt_create asc limit 0, 15) a, t
where a.id = t.id;
前提:假設t表主鍵是id列,且有覆蓋索引secondary key:(thread_id, deleted, gmt_create)
原理:先根據過濾條件利用覆蓋索引取出主鍵id進行排序,再進行join操作取出其他欄位。
資料訪問開銷=索引IO+索引分頁後結果(例子中是15行)對應的表資料IO。
優點:每次翻頁消耗的資源和時間都基本相同,就像翻第一頁一樣。
適用場景:當查詢和排序欄位(即where子句和order by子句涉及的欄位)有對應覆蓋索引時,且中間結果集很大的情況時適用。
25.SELECT只獲取必要的欄位,禁止使用SELECT *。
減少網路頻寬消耗;
能有效利用覆蓋索引;
表結構變更對程式基本無影響。
26.SQL中避免出現now()、rand()、sysdate()、current_user()等不確定結果的函式。
語句級複製場景下,引起主從資料不一致;不確定值的函式,產生的SQL語句無法利用QUERY CACHE。
27.採用合適的分庫分表策略。例如千庫十表、十庫百表等。
採用合適的分庫分表策略,有利於業務發展後期快速對資料庫進行水平拆分,同時分庫可以有效利用MySQL的多執行緒複製特性。
28.減少與資料庫互動次數,儘量採用批量SQL語句。
使用下面的語句來減少和db的互動次數:
a)INSERT ... ON DUPLICATE KEY UPDATE
b)REPLACE INTO
c)INSERT IGNORE
d)INSERT INTO VALUES()
29.拆分複雜SQL為多個小SQL,避免大事務。
簡單的SQL容易使用到MySQL的QUERY CACHE;減少鎖表時間特別是MyISAM;可以使用多核CPU。
30.對同一個表的多次alter操作必須合併為一次操作。
mysql對錶的修改絕大部分操作都需要鎖表並重建表,而鎖表則會對線上業務造成影響。為減少這種影響,必須把對錶的多次alter操作合併為一次操作。例如,要給表t增加一個欄位b,同時給已有的欄位aa建立索引, 通常的做法分為兩步:
alter table t add column b varchar(10);
然後增加索引:
alter table t add index idx_aa(aa);
正確的做法是:
alter table t add column b varchar(10),add index idx_aa(aa);
31.避免使用儲存過程、觸發器、檢視、自定義函式等。
這些高階特性有效能問題,以及未知BUG較多。業務邏輯放到資料庫會造成資料庫的DDL、SCALE OUT、 SHARDING等變得更加困難。
32.禁止有super許可權的應用程式賬號存在。
安全第一。super許可權會導致read only失效,導致較多詭異問題而且很難追蹤。
33.提交線上建表改表需求,必須詳細註明涉及到的所有SQL語句(包括INSERT、DELETE、UPDATE),便於DBA進行稽核和優化。
並不只是SELECT語句需要用到索引。UPDATE、DELETE都需要先定位到資料才能執行變更。因此需要業務提供所有的SQL語句便於DBA稽核。
34.不要在MySQL資料庫中存放業務邏輯。
資料庫是有狀態的服務,變更復雜而且速度慢,如果把業務邏輯放到資料庫中,將會限制業務的快速發展。建議把業務邏輯提前,放到前端或中間邏輯層,而把資料庫作為儲存層,實現邏輯與儲存的分離。
35.建表語句示例
36.產品線對應名稱說明(供參考)