生活随笔
收集整理的這篇文章主要介紹了
MySQL数据库设计规范
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
1. 規范背景與目的
自公司組建DBA團隊以來,公司的關系型數據庫服務維護已經全部由DBA團隊接管,隨著公司互聯網業務的快速發展,對數據庫服務的需求也越來越多,服務質量的要求也越來越高,但由于之前RD/OP對數據庫設計和使用的不合理、不規范,造成數據庫性能不高,安全性差,維護難度高,從而導致DBA運維成本明顯提高,運維質量問題也日益突出。所以DBA組決定制定一部有關MySQL數據庫 使用/運維準入的文檔,從而使MySQL數據庫的使用規范化,標準化,簡單化,安全化,規避不合理的使用數據庫造成業務性能下降和安全漏洞。同時使RD和OP在程序開發和上線過程中有據可依,這樣DBA的日常運維工作也能更輕松,簡單。
2. 設計規范
2.1 數據庫設計
以下所有規范會按照【高危】、【強制】、【建議】三個級別進行標注,遵守優先級從高到低。
對于不滿足【高危】和【強制】兩個級別的設計,DBA會強制打回要求修改。
2.1.1 庫名
【強制】庫的名稱必須控制在32個字符以內,相關模塊的表名與表名之間盡量提現join的關系,如user表和user_login表。【強制】庫的名稱格式:業務系統名稱_子系統名,同一模塊使用的表名盡量使用統一前綴。【強制】一般分庫名稱命名格式是庫通配名_編號,編號從0開始遞增,比如user_001以時間進行分庫的名稱格式是“庫通配名_時間”【強制】創建數據庫時必須顯式指定字符集,并且字符集只能是utf8mb4。創建數據庫SQL舉例:create database db1 default character set utf8mb4;。
2.1.2 表結構
【強制】表和列的名稱必須控制在32個字符以內,表名只能使用字母、數字和下劃線,一律小寫。【強制】表名要求模塊名強相關,如廣告系統采用”cpd”作為前綴,渠道系統采用”qd”作為前綴等。【強制】創建表時必須顯式指定字符集為utf8mb4。【強制】創建表時必須顯式指定表存儲引擎類型,如無特殊需求,一律為InnoDB。當需要使用除InnoDB/MyISAM/Memory以外的存儲引擎時,必須通過DBA審核才能在生產環境中使用。因為Innodb表支持事務、行鎖、宕機恢復、MVCC等關系型數據庫重要特性,為業界使用最多的MySQL存儲引擎。而這是其他大多數存儲引擎不具備的,因此首推InnoDB。【強制】建表必須有comment【建議】建表時關于主鍵:(1)強制要求主鍵為id,類型為int或bigint,且為auto_increment(2)標識表里每一行主體的字段不要設為主鍵,建議設為其他字段如user_id,order_id等,并建立unique key索引(可參考cdb.teacher表設計)。因為如果設為主鍵且主鍵值為隨機插入,則會導致innodb內部page分裂和大量隨機I/O,性能下降。【建議 表必須有行數據的創建時間字段create_time和最后更新時間字段update_time,便于查問題。【建議】表中所有字段必須都是NOT NULL屬性,業務可以根據需要定義DEFAULT值。因為使用NULL值會存在每一行都會占用額外存儲空間、數據遷移容易出錯、聚合函數計算結果偏差等問題。【建議】建議對表里的blob、text等大字段,垂直拆分到其他表里,僅在需要讀這些對象的時候才去select。【建議】反范式設計:把經常需要join查詢的字段,在其他表里冗余一份。如user_name屬性在user_account,user_login_log等表里冗余一份,減少join查詢。【強制】中間表用于保留中間結果集,名稱必須以tmp_開頭。備份表用于備份或抓取源表快照,名稱必須以bak_開頭。中間表和備份表定期清理。【強制】對于超過100W行的大表進行alter table,必須經過DBA審核,并在業務低峰期執行。因為alter table會產生表鎖,期間阻塞對于該表的所有寫入,對于業務可能會產生極大影響。
2.1.3 列數據類型優化
【強制】表中的自增列(auto_increment屬性),推薦使用bigint類型或者unsigned int?。因為無符號int存儲范圍為-2147483648~2147483647(大約21億左右),溢出后會導致報錯。【建議】業務中選擇性很少的狀態status、類型type等字段推薦使用tinytint或者smallint類型節省存儲空間。【建議】業務中IP地址字段推薦使用int類型,不推薦用char(15)。因為int只占4字節,可以用如下函數相互轉換,而char(15)占用至少15字節。一旦表數據行數到了1億,那么要多用1.1G存儲空間。 SQL:select inet_aton('192.168.2.12'); select inet_ntoa(3232236044);?PHP:?ip2long(‘192.168.2.12’); long2ip(3530427185);【建議】不推薦使用enum,set。 因為它們浪費空間,且枚舉值寫死了,變更不方便。推薦使用tinyint或smallint。【建議】不推薦使用blob,text等類型。它們都比較浪費硬盤和內存空間。在加載表數據時,會讀取大字段到內存里從而浪費內存空間,影響系統性能。建議和PM、RD溝通,是否真的需要這么大字段。Innodb中當一行記錄超過8098字節時,會將該記錄中選取最長的一個字段將其768字節放在原始page里,該字段余下內容放在overflow-page里。不幸的是在compact行格式下,原始page和overflow-page都會加載。【建議】文本數據盡量用varchar存儲。因為varchar是變長存儲,比char更省空間。MySQL server層規定一行所有文本最多存65535字節,因此在utf8字符集下最多存21844個字符,超過會自動轉換為mediumtext字段。而text在utf8字符集下最多存21844個字符,mediumtext最多存2^24/3個字符,longtext最多存2^32個字符。一般建議用varchar類型,字符數不要超過2700。【建議】時間類型盡量選取timestamp。因為datetime占用8字節,timestamp僅占用4字節,但是范圍為1970-01-01 00:00:01到2038-01-01 00:00:00。更為高階的方法,選用int來存儲時間,使用SQL函數unix_timestamp()和from_unixtime()來進行轉換。
2.1.4 索引設計
【強制】InnoDB表必須主鍵為id int/bigint auto_increment,且主鍵值禁止被更新。【建議】唯一鍵以“uniq_”開頭,普通索引以“idx_”開頭,一律使用小寫格式,以表名/字段的名稱或縮寫作為后綴。【強制】單個索引中每個索引記錄的長度不能超過64KB。【建議】單個表上的索引個數不能超過5個。【建議】在建立索引時,多考慮建立聯合索引,并把區分度最高的字段放在最前面。如列userid的區分度可由select count(distinct userid)計算出來。【建議】在多表join的SQL里,保證被驅動表的連接列上有索引,這樣join執行效率最高。【建議】建表或加索引時,保證表里互相不存在冗余索引。對于MySQL來說,如果表里已經存在key(a,b),則key(a)為冗余索引,需要刪除。
2.1.5 字符集
【強制】數據庫本身庫、表、列所有字符集必須保持一致,為utf8mb4`。【強制】前端程序字符集或者環境變量中的字符集,與數據庫、表的字符集必須一致,統一為utf8mb4。
2.1.7 程序層DAO設計建議
【建議】新的代碼不要用model,推薦使用手動拼SQL+綁定變量傳入參數的方式。因為model雖然可以使用面向對象的方式操作db,但是其使用不當很容易造成生成的SQL非常復雜,且model層自己做的強制類型轉換性能較差,最終導致數據庫性能下降。【建議】前端程序連接MySQL或者redis,必須要有連接超時和失敗重連機制,且失敗重試必須有間隔時間。【建議】前端程序報錯里盡量能夠提示MySQL或redis原生態的報錯信息,便于排查錯誤。【建議】對于有連接池的前端程序,必須根據業務需要配置初始、最小、最大連接數,超時時間以及連接回收機制,否則會耗盡數據庫連接資源,造成線上事故。【建議】對于log或history類型的表,隨時間增長容易越來越大,因此上線前RD或者DBA必須建立表數據清理或歸檔方案。【建議】在應用程序設計階段,RD必須考慮并規避數據庫中主從延遲對于業務的影響。盡量避免從庫短時延遲(20秒以內)對業務造成影響,建議強制一致性的讀開啟事務走主庫,或更新后過一段時間再去讀從庫。【建議】多個并發業務邏輯訪問同一塊數據(innodb表)時,會在數據庫端產生行鎖甚至表鎖導致并發下降,因此建議更新類SQL盡量基于主鍵去更新。【建議】業務邏輯之間加鎖順序盡量保持一致,否則會導致死鎖。【建議】對于單表讀寫比大于10:1的數據行或單個列,可以將熱點數據放在緩存里(如mecache或redis),加快訪問速度,降低MySQL壓力。
2.1.8 一個規范的建表語句示例
一個較為規范的建表語句為:
use api1; CREATE TABLE user ( id bigint(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主鍵', user_id bigint(11) NOT NULL COMMENT '用戶id', username varchar(45) NOT NULL DEFAULT "abc" COMMENT '真實姓名', email varchar(30) NOT NULL COMMENT '用戶郵箱', nickname varchar(45) NOT NULL COMMENT '昵稱', avatar int(11) NOT NULL COMMENT '頭像', birthday date NOT NULL COMMENT '生日', sex tinyint(4) DEFAULT '0' COMMENT '性別', short_introduce varchar(150) DEFAULT NULL COMMENT '一句話介紹自己,最多50個漢字', user_resume varchar(300) NOT NULL COMMENT '用戶提交的簡歷存放地址', user_register_ip int NOT NULL COMMENT '用戶注冊時的源ip', create_time timestamp NOT NULL DEFAULT now() COMMENT '用戶記錄創建的時間', update_time timestamp NOT NULL DEFAULT now() COMMENT '用戶資料修改的時間', user_review_status tinyint NOT NULL COMMENT '用戶資料審核狀態,1為通過,2為審核中,3為未通過,4為還未提交審核', PRIMARY KEY (id), UNIQUE KEY uniq_user_id (user_id), KEY idx_username(username), KEY idx_create_time(create_time,user_review_status) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='網站用戶基本信息';
2.2 SQL編寫
2.2.1 DML語句
【強制】SELECT語句必須指定具體字段名稱,禁止寫成*。因為select *會將不該讀的數據也從MySQL里讀出來,造成網卡壓力。且表字段一旦更新,但model層沒有來得及更新的話,系統會報錯。【強制】insert語句指定具體字段名稱,不要寫成insert into t1 values(…),道理同上。【建議】insert into…values(XX),(XX),(XX)…。這里XX的值不要超過5000個。值過多雖然上線很很快,但會引起主從同步延遲。【建議】SELECT語句不要使用UNION,推薦使用UNION ALL,并且UNION子句個數限制在5個以內。因為union all不需要去重,節省數據庫資源,提高性能。【建議】in值列表限制在500以內。例如select… where userid in(….500個以內…),這么做是為了減少底層掃描,減輕數據庫壓力從而加速查詢。【建議】事務里批量更新數據需要控制數量,進行必要的sleep,做到少量多次。【強制】事務涉及的表必須全部是innodb表。否則一旦失敗不會全部回滾,且易造成主從庫同步終端。【強制】寫入和事務發往主庫,只讀SQL發往從庫。【強制】除靜態表或小表(100行以內),DML語句必須有where條件,且使用索引查找。【強制】生產環境禁止使用hint,如sql_no_cache,force index,ignore key,straight join等。因為hint是用來強制SQL按照某個執行計劃來執行,但隨著數據量變化我們無法保證自己當初的預判是正確的,因此我們要相信MySQL優化器!【強制】where條件里等號左右字段類型必須一致,否則無法利用索引。【建議】SELECT|UPDATE|DELETE|REPLACE要有WHERE子句,且WHERE子句的條件必需使用索引查找。【強制】生產數據庫中強烈不推薦大表上發生全表掃描,但對于100行以下的靜態表可以全表掃描。查詢數據量不要超過表行數的25%,否則不會利用索引。【強制】WHERE 子句中禁止只使用全模糊的LIKE條件進行查找,必須有其他等值或范圍查詢條件,否則無法利用索引。【建議】索引列不要使用函數或表達式,否則無法利用索引。如where length(name)='Admin'或where user_id+2=10023。【建議】減少使用or語句,可將or語句優化為union,然后在各個where條件上建立索引。如where a=1 or b=2優化為where a=1… union …where b=2, key(a),key(b)。【建議】分頁查詢,當limit起點較高時,可先用過濾條件進行過濾。如select a,b,c from t1 limit 10000,20;優化為:?select a,b,c from t1 where id>10000 limit 20;。
2.2.2 多表連接
【強制】禁止跨db的join語句。因為這樣可以減少模塊間耦合,為數據庫拆分奠定堅實基礎。【強制】禁止在業務的更新類SQL語句中使用join,比如update t1 join t2…。【建議】不建議使用子查詢,建議將子查詢SQL拆開結合程序多次查詢,或使用join來代替子查詢。【建議】線上環境,多表join不要超過3個表。【建議】多表連接查詢推薦使用別名,且SELECT列表中要用別名引用字段,數據庫.表格式,如select a from db1.table1 alias1 where …。【建議】在多表join中,盡量選取結果集較小的表作為驅動表,來join其他表。
2.2.3 事務
【建議】事務中INSERT|UPDATE|DELETE|REPLACE語句操作的行數控制在2000以內,以及WHERE子句中IN列表的傳參個數控制在500以內。【建議】批量操作數據時,需要控制事務處理間隔時間,進行必要的sleep,一般建議值5-10秒。【建議】對于有auto_increment屬性字段的表的插入操作,并發需要控制在200以內。【強制】程序設計必須考慮“數據庫事務隔離級別”帶來的影響,包括臟讀、不可重復讀和幻讀。線上建議事務隔離級別為repeatable-read。【建議】事務里包含SQL不超過5個(支付業務除外)。因為過長的事務會導致鎖數據較久,MySQL內部緩存、連接消耗過多等雪崩問題。【建議】事務里更新語句盡量基于主鍵或unique key,如update … where id=XX; 否則會產生間隙鎖,內部擴大鎖定范圍,導致系統性能下降,產生死鎖。【建議】盡量把一些典型外部調用移出事務,如調用webservice,訪問文件存儲等,從而避免事務過長。【建議】對于MySQL主從延遲嚴格敏感的select語句,請開啟事務強制訪問主庫。
2.2.4 排序和分組
【建議】減少使用order by,和業務溝通能不排序就不排序,或將排序放到程序端去做。order by、group by、distinct這些語句較為耗費CPU,數據庫的CPU資源是極其寶貴的。【建議】order by、group by、distinct這些SQL盡量利用索引直接檢索出排序好的數據。如where a=1 order by可以利用key(a,b)。【建議】包含了order by、group by、distinct這些查詢的語句,where條件過濾出來的結果集請保持在1000行以內,否則SQL會很慢。
2.2.5 線上禁止使用的SQL語句
【高危】禁用update|delete t1 … where a=XX limit XX;?這種帶limit的更新語句。因為會導致主從不一致,導致數據錯亂。建議加上order by PK。【高危】禁止使用關聯子查詢,如update t1 set … where name in(select name from user where…);效率極其低下。【強制】禁用procedure、function、trigger、views、event、外鍵約束。因為他們消耗數據庫資源,降低數據庫實例可擴展性。推薦都在程序端實現。【強制】禁用insert into …on duplicate key update…在高并發環境下,會造成主從不一致。【強制】禁止聯表更新語句,如update t1,t2 where t1.id=t2.id…。
總結
以上是生活随笔為你收集整理的MySQL数据库设计规范的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。