MySQL數據庫開發規範

一. 命名規範mysql

1.庫名、表名、字段名必須使用小寫字母,並採用下劃線分割sql

(1)MySQL有配置參數lower_case_table_names=1,即庫表名以小寫存儲,大小寫不敏感。若是是0,則庫表名以實際狀況存儲,大小寫敏感;若是是2,以實際狀況存儲,但以小寫比較。數據庫

(2)若是大小寫混合使用,可能存在abc,Abc,ABC等多個表共存,容易致使混亂。緩存

(3)字段名顯示區分大小寫,但實際使⽤時不區分,即不能夠創建兩個名字同樣但大小寫不同的字段。網絡

(4)爲了統一規範, 庫名、表名、字段名使用小寫字母。mysql優化

2.庫名以 d 開頭,表名以 t 開頭,字段名以 f_ 開頭併發

(1)好比表 t_crm_relation,中間的 crm 表明業務模塊名運維

(2)視圖以view_開頭,事件以event_開頭,觸發器以trig_開頭,存儲過程以proc_開頭,函數以func_開頭函數

(3)普通索引以idx_col1_col2命名,惟一索引以uk_col1_col2命名(可去掉f_公共部分)。如 idx_companyid_corpid_contacttime(f_company_id,f_corp_id,f_contact_time)工具

3.庫名、表名、字段名禁止超過32個字符,需見名知意

庫名、表名、字段名支持最多64個字符,但爲了統一規範、易於辨識以及減小傳輸量,禁止超過32個字符

4.臨時庫、表名須以tmp加日期爲後綴

如 t_crm_relation_tmp0425。備份表也相似,形如 _bak20160425 。

5.按日期時間分表須符合_YYYY[MM][DD]格式

這也是爲未來有可能分表作準備的,好比t_crm_ec_record_201403,但像 t_crm_contact_at201506就打破了這種規範。
不具備時間特性的,直接以 t_tbname_001 這樣的方式命名。

二. 庫表基礎規範

1.使用Innodb存儲引擎

5.5版本開始mysql默認存儲引擎就是InnoDB,5.7版本開始,系統表都放棄MyISAM了。

2.表字符集統一使用UTF8

(1)UTF8字符集存儲漢字佔用3個字節,存儲英文字符佔用一個字節

(2)校對字符集使用默認的 utf8_general_ci

(3)鏈接的客戶端也使用utf8,創建鏈接時指定charset或SET NAMES UTF8;。(對於已經在項目中長期使用latin1的,救不了了)

(4)若是遇到EMOJ等表情符號的存儲需求,可申請使用UTF8MB4字符集

3.全部表都要添加註釋

(1)儘可能給字段也添加註釋

(2)類status型需指明主要值的含義,如」0-離線,1-在線」

4.控制單表字段數量

(1)單表字段數上限30左右,再多的話考慮垂直分表,一是冷熱數據分離,二是大字段分離,三是常在一塊兒作條件和返回列的不分離。

(2)表字段控制少而精,能夠提升IO效率,內存緩存更多有效數據,從而提升響應速度和併發能力,後續 alter table 也更快。

5.全部表都必需要顯式指定主鍵

(1)主鍵儘可能採用自增方式,InnoDB表實際是一棵索引組織表,順序存儲能夠提升存取效率,充分利用磁盤空間。還有對一些複雜查詢可能須要自鏈接來優化時須要用到。

(2)須要全局惟一主鍵時,使用外部發號器ticket server(建設中)

(3)若是沒有主鍵或惟一索引,update/delete是經過全部字段來定位操做的行,至關於每行就是一次全表掃描

(4)少數狀況可使用聯合惟一主鍵,需與DBA協商

6.不強制使用外鍵參考

即便2個表的字段有明確的外鍵參考關係,也不使用 FOREIGN KEY ,由於新紀錄會去主鍵表作校驗,影響性能。

7.適度使用存儲過程、視圖,禁止使用觸發器、事件

(1)存儲過程(procedure)雖然能夠簡化業務端代碼,在傳統企業寫複雜邏輯時可能會用到,而在互聯網企業變動是很頻繁的,在分庫分表的狀況下要升級一個存儲過程至關麻煩。又由於它是不記錄log的,因此也不方便debug性能問題。若是使用過程,必定考慮若是執行失敗的狀況。

(2)使用視圖必定程度上也是爲了下降代碼裏SQL的複雜度,但有時候爲了視圖的通用性會損失性能(好比返回沒必要要的字段)。

(3)觸發器(trigger)也是一樣,但也不該該經過它去約束數據的強一致性,mysql只支持「基於行的觸發」,也就是說,觸發器始終是針對一條記錄的,而不是針對整個sql語句的,若是變動的數據集很是大的話,效率會很低。掩蓋一條sql背後的工做,一旦出現問題將是災難性的,但又很難快速分析和定位。再者須要ddl時沒法使用pt-osc工具。放在transaction執行。

(4)事件(event)也是一種偷懶的表現,目前已經遇到數次因爲定時任務執行失敗影響業務的狀況,並且mysql沒法對它作失敗預警。創建專門的 job scheduler 平臺。

a.單表數據量控制在5000w之內

b.數據庫中不容許存儲明文密碼

三. 字段規範

1.char、varchar、text等字符串類型定義

(1)對於長度基本固定的列,若是該列剛好更新又特別頻繁,適合char

(2)varchar雖然存儲變長字符串,但不可過小也不可太大。UTF8最多能存21844個漢字,或65532個英文

(3)varbinary(M)保存的是二進制字符串,它保存的是字節而不是字符,因此沒有字符集的概念,M長度0-255(字節)。只用於排序或比較時大小寫敏感的類型,不包括密碼存儲

(4)TEXT類型與VARCHAR都相似,存儲可變長度,最大限制也是2^16,可是它20bytes之後的內容是在數據頁之外的空間存儲(row_format=dynamic),對它的使用須要多一次尋址,沒有默認值。

通常用於存放容量平均都很大、操做沒有其它字段那樣頻繁的值。

網上部分文章說要避免使用text和blob,要知道若是純用varchar可能會致使行溢出,效果差很少,但由於每行佔用字節數過多,會致使buffer_pool能緩存的數據行、頁降低。另外text和blob上面通常不會去建索引,而是利用sphinx之類的第三方全文搜索引擎,若是確實要建立(前綴)索引,那就會影響性能。凡事看具體場景。

另外儘量把text/blob拆到另外一個表中

(5)BLOB能夠看出varbinary的擴展版本,內容以二進制字符串存儲,無字符集,區分大小寫,有一種常常提但不用的場景:不要在數據庫裏存儲圖片。

2.int、tinyint、decimal等數字類型定義

(1)使用tinyint來代替 enum和boolean
ENUM類型在須要修改或增長枚舉值時,須要在線DDL,成本較高;ENUM列值若是含有數字類型,可能會引發默認值混淆
tinyint使用1個字節,通常用於status,type,flag的列

(2)建議使用 UNSIGNED 存儲非負數值
相比不使用 unsigned,能夠擴大一倍使用數值範圍

(3)int使用固定4個字節存儲,int(11)與int(4)只是顯示寬度的區別

(4)使用Decimal 代替float/double存儲精確浮點數
對於貨幣、金額這樣的類型,使用decimal,如 decimal(9,2)。float默認只能能精確到6位有效數字

3.timestamp與datetime選擇

(1)datetime 和 timestamp類型所佔的存儲空間不一樣,前者8個字節,後者4個字節,這樣形成的後果是二者能表示的時間範圍不一樣。前者範圍爲1000-01-01 00:00:00 ~ 9999-12-31 23:59:59,後者範圍爲 1970-01-01 08:00:01 到 2038-01-19 11:14:07 。因此 TIMESTAMP 支持的範圍比 DATATIME 要小。

(2)timestamp能夠在insert/update行時,自動更新時間字段(如 f_set_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP),但一個表只能有一個這樣的定義。

(3)timestamp顯示與時區有關,內部老是以 UTC 毫秒 來存的。還受到嚴格模式的限制

(4)優先使用timestamp,datetime也沒問題

(5)where條件裏不要對時間列上使用時間函數

4.建議字段都定義爲NOT NULL

(1)若是是索引字段,必定要定義爲not null 。由於null值會影響cordinate統計,影響優化器對索引的選擇

(2)若是不能保證insert時必定有值過來,定義時使用default ‘' ,或 0

5.同一意義的字段定義必須相同

好比不一樣表中都有 f_user_id 字段,那麼它的類型、字段長度要設計成同樣

四. 索引規範

1.任何新的select,update,delete上線,都要先explain,看索引使用狀況

儘可能避免extra列出現:Using File Sort,Using Temporary,rows超過1000的要謹慎上線。
explain解讀

(1)type:ALL, index, range, ref, eq_ref, const, system, NULL(從左到右,性能從差到好)

(2)possible_keys:指出MySQL能使用哪一個索引在表中找到記錄,查詢涉及到的字段上若存在索引,則該索引將被列出,但不必定被查詢使用

(3)key:表示MySQL實際決定使用的鍵(索引)
若是沒有選擇索引,鍵是NULL。要想強制MySQL使用或忽視possible_keys列中的索引,在查詢中使用FORCE INDEX、USE INDEX或者IGNORE INDEX

(4)ref:表示選擇 key 列上的索引,哪些列或常量被用於查找索引列上的值

(5)rows:根據表統計信息及索引選用狀況,估算的找到所需的記錄所須要讀取的行數

(6)Extra

a.Using temporary:表示MySQL須要使用臨時表來存儲結果集,常見於排序和分組查詢
b.Using filesort:MySQL中沒法利用索引完成的排序操做稱爲「文件排序」

1.索引個數限制

(1)索引是雙刃劍,會增長維護負擔,增大IO壓力,索引佔用空間是成倍增長的

(2)單張表的索引數量控制在5個之內,或不超過表字段個數的20%。若單張表多個字段在查詢需求上都要單獨用到索引,須要通過DBA評估。

2.避免冗餘索引

(1.)InnoDB表是一棵索引組織表,主鍵是和數據放在一塊兒的彙集索引,普通索引最終指向的是主鍵地址,因此把主鍵作最後一列是多餘的。如f_crm_id做爲主鍵,聯合索引(f_user_id,f_crm_id)上的f_crm_id就徹底多餘

(2)(a,b,c)、(a,b),後者爲冗餘索引。能夠利用前綴索引來達到加速目的,減輕維護負擔

3.沒有特殊要求,使用自增id做爲主鍵

(1.)主鍵是一種彙集索引,順序寫入。組合惟一索引做爲主鍵的話,是隨機寫入,適合寫少讀多的表

(2)主鍵不容許更新

4.索引儘可能建在選擇性高的列上

(1)不在低基數列上創建索引,例如性別、類型。但有一種狀況,idx_feedbackid_type (f_feedback_id,f_type),若是常常用 f_type=1 比較,並且能過濾掉90%行,那這個組合索引就值得建立。有時候一樣的查詢語句,因爲條件取值不一樣致使使用不一樣的索引,也是這個道理。

(2)索引選擇性計算方法(基數 ÷ 數據行數)

Selectivity = Cardinality / Total Rows = select count(distinct col1)/count(*) from tbname,越接近1說明col1上使用索引的過濾效果越好

(3)走索引掃描行數超過30%時,改全表掃描

5.最左前綴原則

(1)mysql使用聯合索引時,從左向右匹配,遇到斷開或者範圍查詢時,沒法用到後續的索引列
好比索引idx_c1_c2_c3 (c1,c2,c3),至關於建立了(c1)、(c1,c2)、(c1,c2,c3)三個索引,where條件包含上面三種狀況的字段比較則能夠用到索引,但像 where c1=a and c3=c 只能用到c1列的索引,像 c2=b and c3=c等狀況就徹底用不到這個索引

(2)遇到範圍查詢(>、<、between、like)也會中止索引匹配,好比 c1=a and c2 > 2 and c3=c,只有c1,c2列上的比較能用到索引,(c1,c2,c3)排列的索引纔可能會都用上

(3)where條件裏面字段的順序與索引順序無關,mysql優化器會自動調整順序

6.前綴索引

(1)對超過30個字符長度的列建立索引時,考慮使用前綴索引,如 idx_cs_guid2 (f_cs_guid(26))表示截取前26個字符作索引,既能夠提升查找效率,也能夠節省空間

(2)前綴索引也有它的缺點是,若是在該列上 ORDER BY 或 GROUP BY 時沒法使用索引,也不能把它們用做覆蓋索引(Covering Index)

(3)若是在varbinary或blob這種以二進制存儲的列上創建前綴索引,要考慮字符集,括號裏表示的是字節數

7.合理使用覆蓋索引減小IO

INNODB存儲引擎中,secondary index(非主鍵索引,又稱爲輔助索引、二級索引)沒有直接存儲行地址,而是存儲主鍵值。
若是用戶須要查詢secondary index中所不包含的數據列,則須要先經過secondary index查找到主鍵值,而後再經過主鍵查詢到其餘數據列,所以須要查詢兩次。覆蓋索引則能夠在一個索引中獲取全部須要的數據列,從而避免回表進行二次查找,節省IO所以效率較高。

例如SELECT email,uid FROM user_email WHERE uid=xx,若是uid不是主鍵,適當時候能夠將索引添加爲index(uid,email),以得到性能提高。

8.儘可能不要在頻繁更新的列上建立索引

如不在定義了 ON UPDATE CURRENT_STAMP 的列上建立索引,維護成本過高(好在mysql有insert buffer,會合並索引的插入)

五. SQL設計

1.杜絕直接 SELECT * 讀取所有字段

即便須要全部字段,減小網絡帶寬消耗,能有效利用覆蓋索引,表結構變動對程序基本無影響

2.能肯定返回結果只有一條時,使用 limit 1

在保證數據不會有誤的前提下,能肯定結果集數量時,多使用limit,儘快的返回結果。

3.當心隱式類型轉換

(1)轉換規則

a. 兩個參數至少有一個是 NULL 時,比較的結果也是 NULL,例外是使用 <=> 對兩個 NULL 作比較時會返回 1,這兩種狀況都不須要作類型轉換

b. 兩個參數都是字符串,會按照字符串來比較,不作類型轉換

c. 兩個參數都是整數,按照整數來比較,不作類型轉換

d. 十六進制的值和非數字作比較時,會被當作二進制串

e. 有一個參數是 TIMESTAMP 或 DATETIME,而且另一個參數是常量,常量會被轉換爲 timestamp

f. 有一個參數是 decimal 類型,若是另一個參數是 decimal 或者整數,會將整數轉換爲 decimal 後進行比較,若是另一個參數是浮點數,則會把 decimal 轉換爲浮點數進行比較

g. 全部其餘狀況下,兩個參數都會被轉換爲浮點數再進行比較。

(2)若是一個索引創建在string類型上,若是這個字段和一個int類型的值比較,符合第 g 條。如f_phone定義的類型是varchar,但where使用f_phone in (098890),兩個參數都會被當成成浮點型。發生這個隱式轉換並非最糟的,最糟的是string轉換後的float,mysql沒法使用索引,這才致使了性能問題。若是是 f_user_id = ‘1234567' 的狀況,符合第 b 條,直接把數字當字符串比較。

4.禁止在where條件列上使用函數

(1)會致使索引失效,如lower(email),f_qq % 4。可放到右邊的常量上計算

(2)返回小結果集不是很大的狀況下,能夠對返回列使用函數,簡化程序開發

5.使用like模糊匹配,%不要放首位

會致使索引失效,有這種搜索需求是,考慮其它方案,如sphinx全文搜索

6.涉及到複雜sql時,務必先參考已有索引設計,先explain

(1)簡單SQL拆分,不以代碼處理複雜爲由。

(2)好比 OR 條件: f_phone='10000' or f_mobile='10000',兩個字段各自有索引,但只能用到其中一個。能夠拆分紅2個sql,或者union all。

(3)先explain的好處是能夠爲了利用索引,增長更多查詢限制條件

7.使用join時,where條件儘可能使用充分利用同一表上的索引

(1)如 select t1.a,t2.b * from t1,t2 and t1.a=t2.a and t1.b=123 and t2.c= 4 ,若是t1.c與t2.c字段相同,那麼t1上的索引(b,c)就只用到b了。此時若是把where條件中的t2.c=4改爲t1.c=4,那麼能夠用到完整的索引

(2)這種狀況可能會在字段冗餘設計(反範式)時出現

(3)正確選取inner join和left join

8.少用子查詢,改用join

小於5.6版本時,子查詢效率很低,不像Oracle那樣先計算子查詢後外層查詢。5.6版本開始獲得優化

9.考慮使用union all,少使用union,注意考慮去重

(1)union all不去重,而少了排序操做,速度相對比union要快,若是沒有去重的需求,優先使用union all

(2)若是UNION結果中有使用limit,在2個子SQL可能有許多返回值的狀況下,各自加上limit。若是還有order by,請找DBA。

10.IN的內容儘可能不超過200個

超過500個值使用批量的方式,不然一次執行會影響數據庫的併發能力,由於單SQL只能且一直佔用單CPU,並且可能致使主從複製延遲

11.拒絕大事務

好比在一個事務裏進行多個select,多個update,若是是高頻事務,會嚴重影響MySQL併發能力,由於事務持有的鎖等資源只在事務rollback/commit時才能釋放。但同時也要權衡數據寫入的一致性。

12.避免使用is null, is not null這樣的比較

13.order by .. limit

這種查詢更多的是經過索引去優化,但order by的字段有講究,好比主鍵id與f_time都是順序遞增,那就能夠考慮order by id而非 f_time 。

14.c1 < a order by c2

與上面不一樣的是,order by以前有個範圍查詢,由前面的內容可知,用不到相似(c1,c2)的索引,可是能夠利用(c2,c1)索引。另外還能夠改寫成join的方式實現。

15.分頁優化

建議使用合理的分頁方式以提升分頁效率,大頁狀況下不使用跳躍式分頁

假若有相似下面分頁語句:

SELECT FROM table1 ORDER BY ftime DESC LIMIT 10000,10;

這種分頁方式會致使大量的io,由於MySQL使用的是提早讀取策略。

推薦分頁方式:

SELECT FROM table1 WHERE ftime < last_time ORDER BY ftime DESC LIMIT 10

即傳入上一次分頁的界值

SELECT * FROM table as t1 inner JOIN (SELECT id FROM table ORDER BY time LIMIT 10000,10) as t2 ON t1.id=t2.id

16.count計數

(1)首先count()、count(1)、count(col1)是有區別的,count()表示整個結果集有多少條記錄,count(1)表示結果集裏以primary key統計數量,絕大多數狀況下count()與count(1)效果同樣的,但count(col1)表示的是結果集裏 col1 列 NOT null 的記錄數。優先採用count()

(2)大數據量count是消耗資源的操做,甚至會拖慢整個庫,查詢性能問題沒法解決的,應從產品設計上進行重構。例如當頻繁須要count的查詢,考慮使用匯總表

(3)遇到distinct的狀況,group by方式可能效率更高。

17.delete,update語句改爲select再explain

select最多致使數據庫慢,寫操做纔是鎖表的罪魁禍首

18.減小與數據庫交互的次數,儘可能採用批量SQL語句

(1)INSERT ... ON DUPLICATE KEY UPDATE ...,插入行後會致使在一個UNIQUE索引或PRIMARY KEY中出現重複值,則執行舊行UPDATE,若是不重複則直接插入,影響1行。

(2)REPLACE INTO相似,但它是衝突時刪除舊行。INSERT IGNORE相反,保留舊行,丟棄要插入的新行。

(3)INSERT INTO VALUES(),(),(),合併插入。

19.杜絕危險SQL

(1)去掉where 1=1 這樣無心義或恆真的條件,若是遇到update/delete或遭到sql注入就恐怖了

(2)SQL中不容許出現DDL語句。通常也不給予create/alter這類權限,但阿里雲RDS只區分讀寫用戶

六. 行爲規範

(1)不容許在DBA不知情的狀況下導現網數據

(2)大批量更新,如修復數據,避開高峯期,並通知DBA。直接執行sql的由運維或DBA同事操做

(3)及時處理已下線業務的SQL

(4)複雜sql上線審覈

由於目前尚未SQL審查機制,複雜sql如多表join,count,group by,主動上報DBA評估。

(5)重要項目的數據庫方案選型和設計必須提早通知DBA參與

相關文章
相關標籤/搜索