MySQL數據庫設計規範
目錄
1. 規範背景與目的
2. 設計規範
2.1 數據庫設計
2.1.1 庫名
2.1.2 表結構
2.1.3 列數據類型優化
2.1.4 索引設計
2.1.5 分庫分表、分區表
2.1.6 字符集
2.1.7 程序DAO層設計建議
2.1.8 一個規範的建表語句示例
2.2 SQL編寫
2.2.1 DML語句
2.2.2 多表鏈接
2.2.3 事務
2.2.4 排序和分組
2.2.5 線上禁止使用的SQL語句前端
1. 規範背景與目的
MySQL數據庫與 Oracle、 SQL Server 等數據庫相比,有其內核上的優點與劣勢。咱們在使用MySQL數據庫的時候須要遵循必定規範,揚長避短。本規範旨在幫助或指導RD、QA、OP等技術人員作出適合線上業務的數據庫設計。在數據庫變動和處理流程、數據庫表設計、SQL編寫等方面予以規範,從而爲公司業務系統穩定、健康地運行提供保障。mysql
2. 設計規範
2.1 數據庫設計
如下全部規範會按照【高危】、【強制】、【建議】三個級別進行標註,遵照優先級從高到低。web
對於不知足【高危】和【強制】兩個級別的設計,DBA會強制打回要求修改。redis
2.1.1 庫名
- 【強制】庫的名稱必須控制在32個字符之內,相關模塊的表名與表名之間儘可能提現join的關係,如user表和user_login表。
- 【強制】庫的名稱格式:業務系統名稱_子系統名,同一模塊使用的表名儘可能使用統一前綴。
- 【強制】通常分庫名稱命名格式是庫通配名_編號,編號從0開始遞增,好比wenda_001以時間進行分庫的名稱格式是「庫通配名_時間」
- 【強制】建立數據庫時必須顯式指定字符集,而且字符集只能是utf8或者utf8mb4。建立數據庫SQL舉例:create database
db1 default character set utf8;。
2.1.2 表結構
- 【強制】表和列的名稱必須控制在32個字符之內,表名只能使用字母、數字和下劃線,一概小寫。
- 【強制】表名要求模塊名強相關,如師資系統採用」sz」做爲前綴,渠道系統採用」qd」做爲前綴等。
- 【強制】建立表時必須顯式指定字符集爲utf8或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類型。由於無符號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都會加載。
- 【建議】存儲金錢的字段,建議用int,程序端乘以100和除以100進行存取。由於int佔用4字節,而double佔用8字節,空間浪費。
- 【建議】文本數據儘可能用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,且主鍵值禁止被更新。
- 【建議】主鍵的名稱以「pk_」開頭,惟一鍵以「uk_」或「uq_」開頭,普通索引以「idx_」開頭,一概使用小寫格式,以表名/字段的名稱或縮寫做爲後綴。
- 【強制】InnoDB和MyISAM存儲引擎表,索引類型必須爲BTREE;MEMORY表能夠根據須要選擇HASH或者BTREE類型索引。
- 【強制】單個索引中每一個索引記錄的長度不能超過64KB。
- 【建議】單個表上的索引個數不能超過7個。
- 【建議】在創建索引時,多考慮創建聯合索引,並把區分度最高的字段放在最前面。如列userid的區分度可由select count(distinct userid)計算出來。
- 【建議】在多表join的SQL裏,保證被驅動表的鏈接列上有索引,這樣join執行效率最高。
- 【建議】建表或加索引時,保證表裏互相不存在冗餘索引。對於MySQL來講,若是表裏已經存在key(a,b),則key(a)爲冗餘索引,須要刪除。
2.1.5 分庫分表、分區表
- 【強制】分區表的分區字段(partition-key)必須有索引,或者是組合索引的首列。
- 【強制】單個分區表中的分區(包括子分區)個數不能超過1024。
- 【強制】上線前RD或者DBA必須指定分區表的建立、清理策略。
- 【強制】訪問分區表的SQL必須包含分區鍵。
- 【建議】單個分區文件不超過2G,總大小不超過50G。建議總分區數不超過20個。
- 【強制】對於分區表執行alter table操做,必須在業務低峯期執行。
- 【強制】採用分庫策略的,庫的數量不能超過1024
- 【強制】採用分表策略的,表的數量不能超過4096
- 【建議】單個分表不超過500W行,ibd文件大小不超過2G,這樣才能讓數據分佈式變得性能更佳。
- 【建議】水平分表儘可能用取模方式,日誌、報表類數據建議採用日期進行分表。
2.1.6 字符集
- 【強制】數據庫自己庫、表、列全部字符集必須保持一致,爲utf8或utf8mb4。
- 【強制】前端程序字符集或者環境變量中的字符集,與數據庫、表的字符集必須一致,統一爲utf8。
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 一個規範的建表語句示例
一個較爲規範的建表語句爲:sql
CREATE TABLE user (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`user_id` bigint(11) NOT NULL COMMENT ‘用戶id’
`username` varchar(45) NOT NULL 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 COMMENT ‘用戶記錄建立的時間’,
`update_time` timestamp NOT NULL COMMENT ‘用戶資料修改的時間’,
`user_review_status` tinyint NOT NULL COMMENT ‘用戶資料審覈狀態,1爲經過,2爲審覈中,3爲未經過,4爲還未提交審覈’,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_user_id` (`user_id`),
KEY `idx_username`(`username`),
KEY `idx_create_time`(`create_time`,`user_review_status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 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…。