mysql數據庫開發規范

命名規范


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

    • MySQL有配置參數lower_case_table_names=1,即庫表名以小寫存儲,大小寫不敏感。如果是0,則庫表名以實際情況存儲,大小寫敏感;如果是2,以實際情況存儲,但以小寫比較。
    • 如果大小寫混合使用,可能存在abc,Abc,ABC等多個表共存,容易導致混亂。
    • 字段名顯示區分大小寫,但實際使?時不區分,即不可以建立兩個名字一樣但大小寫不一樣的字段。
    • 為了統一規范, 庫名、表名、字段名使用小寫字母。
  2. 庫名以 d 開頭,表名以 t 開頭,字段名以 f_ 開頭

    • 比如表 t_crm_relation,中間的 crm 代表業務模塊名
    • 視圖以view_開頭,事件以event_開頭,觸發器以trig_開頭,存儲過程以proc_開頭,函數以func_開頭
    • 普通索引以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

    • UTF8字符集存儲漢字占用3個字節,存儲英文字符占用一個字節
    • 校對字符集使用默認的 utf8_general_ci
    • 連接的客戶端也使用utf8,建立連接時指定charset或SET NAMES UTF8;。(對于已經在項目中長期使用latin1的,救不了了)
    • 如果遇到EMOJ等表情符號的存儲需求,可申請使用UTF8MB4字符集
  3. 所有表都要添加注釋

    • 盡量給字段也添加注釋
    • 類status型需指明主要值的含義,如”0-離線,1-在線”
  4. 控制單表字段數量

    • 單表字段數上限30左右,再多的話考慮垂直分表,一是冷熱數據分離,二是大字段分離,三是常在一起做條件和返回列的不分離。
    • 表字段控制少而精,可以提高IO效率,內存緩存更多有效數據,從而提高響應速度和并發能力,后續 alter table 也更快。
  5. 所有表都必須要顯式指定主鍵

    • 主鍵盡量采用自增方式,InnoDB表實際是一棵索引組織表,順序存儲可以提高存取效率,充分利用磁盤空間。還有對一些復雜查詢可能需要自連接來優化時需要用到。
    • 需要全局唯一主鍵時,使用外部發號器ticket server(建設中)
    • 如果沒有主鍵或唯一索引,update/delete是通過所有字段來定位操作的行,相當于每行就是一次全表掃描
    • 少數情況可以使用聯合唯一主鍵,需與DBA協商
  6. 不強制使用外鍵參考

    • 即使2個表的字段有明確的外鍵參考關系,也不使用 FOREIGN KEY ,因為新紀錄會去主鍵表做校驗,影響性能。
  7. 適度使用存儲過程、視圖,禁止使用觸發器、事件

    • 存儲過程(procedure)雖然可以簡化業務端代碼,在傳統企業寫復雜邏輯時可能會用到,而在互聯網企業變更是很頻繁的,在分庫分表的情況下要升級一個存儲過程相當麻煩。又因為它是不記錄log的,所以也不方便debug性能問題。如果使用過程,一定考慮如果執行失敗的情況。
    • 使用視圖一定程度上也是為了降低代碼里SQL的復雜度,但有時候為了視圖的通用性會損失性能(比如返回不必要的字段)。
    • 觸發器(trigger)也是同樣,但也不應該通過它去約束數據的強一致性,mysql只支持“基于行的觸發”,也就是說,觸發器始終是針對一條記錄的,而不是針對整個sql語句的,如果變更的數據集非常大的話,效率會很低。掩蓋一條sql背后的工作,一旦出現問題將是災難性的,但又很難快速分析和定位。再者需要ddl時無法使用pt-osc工具。放在transaction執行。
    • 事件(event)也是一種偷懶的表現,目前已經遇到數次由于定時任務執行失敗影響業務的情況,而且mysql無法對它做失敗預警。建立專門的 job scheduler 平臺。
  8. 單表數據量控制在5000w以內

  9. 數據庫中不允許存儲明文密碼

字段規范


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

    • 對于長度基本固定的列,如果該列恰好更新又特別頻繁,適合char
    • varchar雖然存儲變長字符串,但不可太小也不可太大。UTF8最多能存21844個漢字,或65532個英文
    • varbinary(M)保存的是二進制字符串,它保存的是字節而不是字符,所以沒有字符集的概念,M長度0-255(字節)。只用于排序或比較時大小寫敏感的類型,不包括密碼存儲
    • TEXT類型與VARCHAR都類似,存儲可變長度,最大限制也是2^16,但是它20bytes以后的內容是在數據頁以外的空間存儲(row_format=dynamic),對它的使用需要多一次尋址,沒有默認值。
    • 一般用于存放容量平均都很大、操作沒有其它字段那樣頻繁的值。
    • 網上部分文章說要避免使用text和blob,要知道如果純用varchar可能會導致行溢出,效果差不多,但因為每行占用字節數過多,會導致buffer_pool能緩存的數據行、頁下降。另外text和blob上面一般不會去建索引,而是利用sphinx之類的第三方全文搜索引擎,如果確實要創建(前綴)索引,那就會影響性能。凡事看具體場景。
    • 另外盡可能把text/blob拆到另一個表中
    • BLOB可以看出varbinary的擴展版本,內容以二進制字符串存儲,無字符集,區分大小寫,有一種經常提但不用的場景:不要在數據庫里存儲圖片。
  2. int、tinyint、decimal等數字類型定義

    • 使用tinyint來代替 enum和boolean
    • ENUM類型在需要修改或增加枚舉值時,需要在線DDL,成本較高;ENUM列值如果含有數字類型,可能會引起默認值混淆
    • tinyint使用1個字節,一般用于status,type,flag的列
    • 建議使用 UNSIGNED 存儲非負數值
    • 相比不使用 unsigned,可以擴大一倍使用數值范圍
    • int使用固定4個字節存儲,int(11)與int(4)只是顯示寬度的區別
    • 使用Decimal 代替float/double存儲精確浮點數
    • 對于貨幣、金額這樣的類型,使用decimal,如 decimal(9,2)。float默認只能能精確到6位有效數字
  3. timestamp與datetime選擇

    • 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 要小。
    • timestamp可以在insert/update行時,自動更新時間字段(如 f_set_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP),但一個表只能有一個這樣的定義。
    • timestamp顯示與時區有關,內部總是以 UTC 毫秒 來存的。還受到嚴格模式的限制
    • 優先使用timestamp,datetime也沒問題
    • where條件里不要對時間列上使用時間函數
  4. 建議字段都定義為NOT NULL

    • 如果是索引字段,一定要定義為not null 。因為null值會影響cordinate統計,影響優化器對索引的選擇
    • 如果不能保證insert時一定有值過來,定義時使用default ‘’ ,或 0
  5. 同一意義的字段定義必須相同

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

索引規范


  1. 任何新的select,update,delete上線,都要先explain,看索引使用情況
    1. 盡量避免extra列出現:Using File Sort,Using Temporary,rows超過1000的要謹慎上線。explain解讀
    type:ALL, index, range, ref, eq_ref, const, system, NULL(從左到右,性能從差到好)
    possible_keys:指出MySQL能使用哪個索引在表中找到記錄,查詢涉及到的字段上若存在索引,則該索引將被列出,但不一定被查詢使用
    key:表示MySQL實際決定使用的鍵(索引)
    如果沒有選擇索引,鍵是NULL。要想強制MySQL使用或忽視possible_keys列中的索引,在查詢中使用FORCE INDEX、USE INDEX或者IGNORE INDEX
    ref:表示選擇 key 列上的索引,哪些列或常量被用于查找索引列上的值
    rows:根據表統計信息及索引選用情況,估算的找到所需的記錄所需要讀取的行數
    Extra
        Using temporary:表示MySQL需要使用臨時表來存儲結果集,常見于排序和分組查詢
        Using filesort:MySQL中無法利用索引完成的排序操作稱為“文件排序”
    
  1. 索引個數限制

    • 索引是雙刃劍,會增加維護負擔,增大IO壓力,索引占用空間是成倍增加的
    • 單張表的索引數量控制在5個以內,或不超過表字段個數的20%。若單張表多個字段在查詢需求上都要單獨用到索引,需要經過DBA評估。
  2. 避免冗余索引

    • InnoDB表是一棵索引組織表,主鍵是和數據放在一起的聚集索引,普通索引最終指向的是主鍵地址,所以把主鍵做最后一列是多余的。如f_crm_id作為主鍵,聯合索引(f_user_id,f_crm_id)上的f_crm_id就完全多余
    • (a,b,c)、(a,b),后者為冗余索引。可以利用前綴索引來達到加速目的,減輕維護負擔
  3. 沒有特殊要求,使用自增id作為主鍵

    • 主鍵是一種聚集索引,順序寫入。組合唯一索引作為主鍵的話,是隨機寫入,適合寫少讀多的表
    • 主鍵不允許更新
  4. 索引盡量建在選擇性高的列上

    • 不在低基數列上建立索引,例如性別、類型。但有一種情況,idx_feedbackid_type (f_feedback_id,f_type),如果經常用 f_type=1 比較,而且能過濾掉90%行,那這個組合索引就值得創建。有時候同樣的查詢語句,由于條件取值不同導致使用不同的索引,也是這個道理。
    • 索引選擇性計算方法(基數 ÷ 數據行數)
    • Selectivity = Cardinality / Total Rows = select count(distinct col1)/count(*) from tbname,越接近1說明col1上使用索引的過濾效果越好
    • 走索引掃描行數超過30%時,改全表掃描
  5. 最左前綴原則

    • 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等情況就完全用不到這個索引
    • 遇到范圍查詢(>、<、between、like)也會停止索引匹配,比如 c1=a and c2 > 2 and c3=c,只有c1,c2列上的比較能用到索引,(c1,c2,c3)排列的索引才可能會都用上
    • where條件里面字段的順序與索引順序無關,mysql優化器會自動調整順序
  6. 前綴索引

    • 對超過30個字符長度的列創建索引時,考慮使用前綴索引,如 idx_cs_guid2 (f_cs_guid(26))表示截取前26個字符做索引,既可以提高查找效率,也可以節省空間
    • 前綴索引也有它的缺點是,如果在該列上 ORDER BY 或 GROUP BY 時無法使用索引,也不能把它們用作覆蓋索引(Covering Index)
    • 如果在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. 小心隱式類型轉換

    • 轉換規則
  a. 兩個參數至少有一個是 NULL 時,比較的結果也是 NULL,例外是使用 <=> 對兩個 NULL 做比較時會返回 1,這兩種情況都不需要做類型轉換
  b. 兩個參數都是字符串,會按照字符串來比較,不做類型轉換
  c. 兩個參數都是整數,按照整數來比較,不做類型轉換
  d. 十六進制的值和非數字做比較時,會被當做二進制串
  e. 有一個參數是 TIMESTAMP 或 DATETIME,并且另外一個參數是常量,常量會被轉換為 timestamp
  f. 有一個參數是 decimal 類型,如果另外一個參數是 decimal 或者整數,會將整數轉換為 decimal 后進行比較,如果另外一個參數是浮點數,則會把 decimal 轉換為浮點數進行比較
  g. 所有其他情況下,兩個參數都會被轉換為浮點數再進行比較。

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

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

    • 會導致索引失效,如lower(email),f_qq % 4。可放到右邊的常量上計算
    • 返回小結果集不是很大的情況下,可以對返回列使用函數,簡化程序開發
  2. 使用like模糊匹配,%不要放首位

    • 會導致索引失效,有這種搜索需求是,考慮其它方案,如sphinx全文搜索
  3. 涉及到復雜sql時,務必先參考已有索引設計,先explain

    • 簡單SQL拆分,不以代碼處理復雜為由。
    • 比如 OR 條件: f_phone=’10000’ or f_mobile=’10000’,兩個字段各自有索引,但只能用到其中一個。可以拆分成2個sql,或者union all。
    • 先explain的好處是可以為了利用索引,增加更多查詢限制條件
  4. 使用join時,where條件盡量使用充分利用同一表上的索引

    • 如 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,那么可以用到完整的索引
    • 這種情況可能會在字段冗余設計(反范式)時出現
    • 正確選取inner join和left join
  5. 少用子查詢,改用join

    • 小于5.6版本時,子查詢效率很低,不像Oracle那樣先計算子查詢后外層查詢。5.6版本開始得到優化
  6. 考慮使用union all,少使用union,注意考慮去重

    • union all不去重,而少了排序操作,速度相對比union要快,如果沒有去重的需求,優先使用union all
    • 如果UNION結果中有使用limit,在2個子SQL可能有許多返回值的情況下,各自加上limit。如果還有order by,請找DBA。
  7. IN的內容盡量不超過200個

    • 超過500個值使用批量的方式,否則一次執行會影響數據庫的并發能力,因為單SQL只能且一直占用單CPU,而且可能導致主從復制延遲
  8. 拒絕大事務

    • 比如在一個事務里進行多個select,多個update,如果是高頻事務,會嚴重影響MySQL并發能力,因為事務持有的鎖等資源只在事務rollback/commit時才能釋放。但同時也要權衡數據寫入的一致性。
  9. 避免使用is null, is not null這樣的比較

  10. order by .. limit

    • 這種查詢更多的是通過索引去優化,但order by的字段有講究,比如主鍵id與f_time都是順序遞增,那就可以考慮order by id而非 f_time 。
  11. c1 < a order by c2

    • 與上面不同的是,order by之前有個范圍查詢,由前面的內容可知,用不到類似(c1,c2)的索引,但是可以利用(c2,c1)索引。另外還可以改寫成join的方式實現。
  12. 分頁優化

    • 建議使用合理的分頁方式以提高分頁效率,大頁情況下不使用跳躍式分頁 假如有類似下面分頁語句:
    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
    
  1. count計數

    • 首先count()、count(1)、count(col1)是有區別的,count()表示整個結果集有多少條記錄,count(1)表示結果集里以primary key統計數量,絕大多數情況下count()與count(1)效果一樣的,但count(col1)表示的是結果集里 col1 列 NOT null 的記錄數。優先采用count()
    • 大數據量count是消耗資源的操作,甚至會拖慢整個庫,查詢性能問題無法解決的,應從產品設計上進行重構。例如當頻繁需要count的查詢,考慮使用匯總表
    • 遇到distinct的情況,group by方式可能效率更高。
  2. delete,update語句改成select再explain

    • select最多導致數據庫慢,寫操作才是鎖表的罪魁禍首
  3. 減少與數據庫交互的次數,盡量采用批量SQL語句

    • INSERT ... ON DUPLICATE KEY UPDATE ...,插入行后會導致在一個UNIQUE索引或PRIMARY KEY中出現重復值,則執行舊行UPDATE,如果不重復則直接插入,影響1行。
    • REPLACE INTO類似,但它是沖突時刪除舊行。INSERT IGNORE相反,保留舊行,丟棄要插入的新行。
    • INSERT INTO VALUES(),(),(),合并插入。
  4. 杜絕危險SQL

    • 去掉where 1=1 這樣無意義或恒真的條件,如果遇到update/delete或遭到sql注入就恐怖了
    • SQL中不允許出現DDL語句。一般也不給予create/alter這類權限,但阿里云RDS只區分讀寫用戶

行為規范


  1. 不允許在DBA不知情的情況下導現網數據
  2. 大批量更新,如修復數據,避開高峰期,并通知DBA。直接執行sql的由運維或DBA同事操作
  3. 及時處理已下線業務的SQL
  4. 復雜sql上線審核
  5. 因為目前還沒有SQL審查機制,復雜sql如多表join,count,group by,主動上報DBA評估。
  6. 重要項目的數據庫方案選型和設計必須提前通知DBA參與

參考文檔

mysql開發規范
iMySQL | 老葉茶館
互聯網MySQL開發規范
MySQL開發規范與使用技巧總結
MySQL開發規范arstercz's blog

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,362評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,013評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,346評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,421評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,146評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,534評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,585評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,767評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,318評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,074評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,258評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,828評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,486評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,916評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,156評論 1 290
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,993評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,234評論 2 375

推薦閱讀更多精彩內容