資料庫開發規範
本文轉載自“V型知識庫”,用於以後寫sql借鑑,來源
1. 【強制】表達是與否概念的欄位,必須使用 is_xxx 的方式命名,資料型別是 unsigned tinyint
(1 表示是, 0 表示否),此規則同樣適用於 odps 建表。
說明: 任何欄位如果為非負數,必須是 unsigned。
(和java相反哎,java避免pojo定義用is)
2. 【強制】表名、欄位名必須使用小寫字母或數字;禁止出現數字開頭,禁止兩個下劃線中間只
出現數字。資料庫欄位名的修改代價很大,因為無法進行預釋出,所以欄位名稱需要慎重考慮。
正例: getter_admin, task_config, level3_name
反例: GetterAdmin, taskConfig, level_3_name
(命名一定要規範,要不後期改累死)
3. 【強制】表名不使用複數名詞。
說明: 表名應該僅僅表示表裡面的實體內容,不應該表示實體數量,對應於 DO 類名也是單數
形式,符合表達習慣。
(表名不要用複數,因為它是一張表,肯定有很多資料啊,你還複數幹啥呢)
4. 【強制】禁用保留字,如 desc、 range、 match、 delayed 等, 參考官方保留字。
(像oracle就有很多保留字,小心遇到錯誤啊)
5. 【強制】唯一索引名為 uk_欄位名;普通索引名則為 idx_欄位名。
說明: uk_ 即 unique key; idx_ 即 index 的簡稱。
(索引命名)
6. 【強制】小數型別為 decimal,禁止使用 float 和 double。
說明: float 和 double 在儲存的時候,存在精度損失的問題,很可能在值的比較時,得到不
正確的結果。如果儲存的資料範圍超過 decimal 的範圍,建議將資料拆成整數和小數分開儲存。
(注意精度損失,小心銀行揍你)
7. 【強制】如果儲存的字串長度幾乎相等,使用 CHAR 定長字串型別。
(萬一不相等可怎麼辦?就跪了)
8. 【強制】 varchar 是可變長字串,不預先分配儲存空間,長度不要超過 5000,如果儲存長度
大於此值,定義欄位型別為 TEXT,獨立出來一張表,用主鍵來對應,避免影響其它欄位索引
效率。
(oracle好像不是text,而是clob型別,當然它的可變長字串型別也不是vachar,而是vachar2,oracle是不是犯病了)
9. 【強制】表必備三欄位: id, gmt_create, gmt_modified。
說明: 其中 id 必為主鍵,型別為 unsigned bigint、單表時自增、步長為 1;分表時改為從
TDDL Sequence 取值,確保分表之間的全域性唯一。 gmt_create, gmt_modified 的型別均為
date_time 型別。
(id可以用uuid,Oracle的xxx..NextVal,SELECT sys_guid() AS ID FROM DUAL等,一般表結構都會有建立日期啊,更新日期啊什麼的一堆附帶資訊)
10.【推薦】表的命名最好是加上“ 業務名稱_表的作用” ,避免上雲梯後,再與其它業務表關聯
時有混淆。
正例: tiger_task / tiger_reader / mpp_config
11.【推薦】庫名與應用名稱儘量一致。
12.【推薦】如果修改欄位含義或對欄位表示的狀態追加時,需要及時更新欄位註釋。
像這樣:
alter table xxxx add xxx varchar2(50) NULL;
comment on column xxxx.xxx is '哈哈,這是欄位 ';
13.【推薦】欄位允許適當冗餘,以提高效能,但是必須考慮資料同步的情況。冗餘欄位應遵循:
1)不是頻繁修改的欄位。
2)不是 varchar 超長欄位,更不能是 text 欄位。
正例: 各業務線經常冗餘儲存商品名稱,避免查詢時需要呼叫 IC 服務獲取。
14.【推薦】單錶行數超過 500 萬行或者單表容量超過 2GB,才推薦進行分庫分表。
說明: 如果預計三年後的資料量根本達不到這個級別,請不要在建立表時就分庫分表。
反例: 某業務三年總資料量才 2 萬行,卻分成 1024 張表,問:你為什麼這麼設計?答:分 1024
張表,不是標配嗎?
15.【參考】合適的字元儲存長度,不但節約資料庫表空間、節約索引儲存,更重要的是提升檢索
速度。
正例: 人的年齡用 unsigned tinyint(表示範圍 0-255,人的壽命不會超過 255 歲);海龜就
必須是 smallint,但如果是太陽的年齡,就必須是 int;如果是所有恆星的年齡都加起來,那
麼就必須使用 bigint。
1. 【強制】業務上具有唯一特性的欄位,即使是組合欄位,也必須建成唯一索引。
說明: 不要以為唯一索引影響了 insert 速度,這個速度損耗可以忽略,但提高查詢速度是明
顯的;另外,即使在應用層做了非常完善的校驗和控制,只要沒有唯一索引,根據墨菲定律,
必然有髒資料產生。
啥是墨菲定律?從百度百科copy了一下:
“墨菲定律”是一種心理學效應,是由愛德華·墨菲(Edward A. Murphy)提出的。
主要內容:
一、任何事都沒有表面看起來那麼簡單;
二、所有的事都會比你預計的時間長;
三、會出錯的事總會出錯;
四、如果你擔心某種情況發生,那麼它就更有可能發生。
墨菲定律的原句是這樣的:如果有兩種或兩種以上的方式去做某件事情,而其中一種選擇方式將導致災難,則必定有人會做出這種選擇。
墨菲定律是其作出的著名論斷,亦稱墨菲定律、墨菲定理,是西方世界常用的俚語。
墨菲定律根本內容是:如果事情有變壞的可能,不管這種可能性有多小,它總會發生。
(我還知道帕累托法則呢,啥,你不知道,好吧,就是二八原則)
2. 【強制】超過三個表禁止 join。需要 join 的欄位,資料型別保持絕對一致;多表關聯查詢時,保證被關聯的欄位需要有索引。
說明: 即使雙表 join 也要注意表索引、 SQL 效能。
(又是效能)
3. 【強制】在 varchar 欄位上建立索引時,必須指定索引長度,沒必要對全欄位建立索引,根據實際文字區分度決定索引長度。
說明: 索引的長度與區分度是一對矛盾體,一般對字串型別資料,長度為 20 的索引,區分度會高達 90%以上,可以使用 count(distinct left(列名, 索引長度))/count(*)的區分度來確定。
4. 【強制】頁面搜尋嚴禁左模糊或者全模糊,如果需要請走搜尋引擎來解決。
說明: 索引檔案具有 B-Tree 的最左字首匹配特性,如果左邊的值未確定,那麼無法使用此索
引。
5. 【推薦】如果有 order by 的場景,請注意利用索引的有序性。 order by 最後的欄位是組合索
引的一部分,並且放在索引組合順序的最後,避免出現 file_sort 的情況,影響查詢效能。
正例: where a=? and b=? order by c; 索引: a_b_c
反例: 索引中有範圍查詢,那麼索引有序性無法利用,如: WHERE a>10 ORDER BY b; 索引 a_b
無法排序。
6. 【推薦】利用覆蓋索引來進行查詢操作,來避免回表操作。
說明: 如果一本書需要知道第 11 章是什麼標題,會翻開第 11 章對應的那一頁嗎?目錄瀏覽一
下就好,這個目錄就是起到覆蓋索引的作用。
正例: IDB 能夠建立索引的種類:主鍵索引、唯一索引、普通索引,而覆蓋索引是一種查詢的
一種效果,用 explain 的結果, extra 列會出現: using index.
7. 【推薦】利用延遲關聯或者子查詢優化超多分頁場景。
說明: MySQL 並不是跳過 offset 行,而是取 offset+N 行,然後返回放棄前 offset 行,返回 N
行,那當 offset 特別大的時候,效率就非常的低下,要麼控制返回的總頁數,要麼對超過特
定閾值的頁數進行 SQL 改寫。
正例: 先快速定位需要獲取的 id 段,然後再關聯:
SELECT a.* FROM 表 1 a, (select id from 表 1 where 條件 LIMIT 100000,20 ) b where a.id=b.id
8. 【推薦】 SQL 效能優化的目標:至少要達到 range 級別, 要求是 ref 級別, 如果可以是 consts
最好。
說明:
1)consts 單表中最多隻有一個匹配行(主鍵或者唯一索引),在優化階段即可讀取到資料。
2) ref 指的是使用普通的索引。( normal index)
3) range 對索引進範圍檢索。
反例: explain 表的結果, type=index,索引物理檔案全掃描,速度非常慢,這個 index 級別
比較 range 還低,與全表掃描是小巫見大巫。
(避免全表查,很慢的,oracle10還是11以前的優化器都是基於規則的,現在都是基於代價的了)
9. 【推薦】建組合索引的時候,區分度最高的在最左邊。
正例: 如果 where a=? and b=? , a 列的幾乎接近於唯一值,那麼只需要單建 idx_a 索引即可。
說明: 存在非等號和等號混合判斷條件時,在建索引時,請把等號條件的列前置。如: where a>?
and b=? 那麼即使 a 的區分度更高,也必須把 b 放在索引的最前列。
(就像偷西瓜,要先確定瓜地的範圍,先定義一個最小的可偷範圍)
10.【參考】建立索引時避免有如下極端誤解:
1)誤認為一個查詢就需要建一個索引。
2)誤認為索引會消耗空間、嚴重拖慢更新和新增速度。
3)誤認為唯一索引一律需要在應用層通過“ 先查後插” 方式解決。
(索引就像衣服,不穿也可以)
1. 【強制】不要使用 count(列名)或 count(常量)來替代 count(*), count(*)就是 SQL92 定義的
標準統計行數的語法,跟資料庫無關,跟 NULL 和非 NULL 無關。
說明: count(*)會統計值為 NULL 的行,而 count(列名)不會統計此列為 NULL 值的行。
(null和" "意思是不同的," "會被count計數的)
2. 【強制】 count(distinct col) 計算該列除 NULL 之外的不重複數量。注意 count(distinct
col1, col2) 如果其中一列全為 NULL,那麼即使另一列有不同的值,也返回為 0。
3. 【強制】當某一列的值全是 NULL 時, count(col)的返回結果為 0,但 sum(col)的返回結果為
NULL,因此使用 sum()時需注意 NPE 問題。
正例: 可以使用如下方式來避免 sum 的 NPE 問題: SELECT IF(ISNULL(SUM(g)),0,SUM(g)) FROM
table;
4. 【強制】使用 ISNULL()來判斷是否為 NULL 值。注意: NULL 與任何值的直接比較都為 NULL。
說明:
1) NULL<>NULL 的返回結果是 NULL,不是 false。
2) NULL=NULL 的返回結果是 NULL,不是 true。
3) NULL<>1 的返回結果是 NULL,而不是 true。
(反正我就是null)
5. 【強制】在程式碼中寫分頁查詢邏輯時,若 count 為 0 應直接返回,避免執行後面的分頁語句。
6. 【強制】不得使用外來鍵與級聯,一切外來鍵概念必須在應用層解決。
說明: (概念解釋)學生表中的 student_id 是主鍵,那麼成績表中的 student_id 則為外來鍵。
如果更新學生表中的 student_id,同時觸發成績表中的 student_id 更新,則為級聯更新。外
鍵與級聯更新適用於單機低併發,不適合分散式、高併發叢集;級聯更新是強阻塞,存在資料
庫更新風暴的風險;外來鍵影響資料庫的插入速度。
(外來鍵連外來鍵,太可怕了)
7. 【強制】禁止使用儲存過程,儲存過程難以除錯和擴充套件,更沒有移植性
(真遇到了上千行的儲存過程咋整)
8. 【強制】 IDB 資料訂正時,刪除和修改記錄時,要先 select,避免出現誤刪除,確認無誤才能提交執行。
(delete需謹慎,貌似不少後臺都不提供delete介面的,或者並不是真正的delete,而是???,修改了一個狀態欄位,記錄並沒擦掉)
9. 【推薦】 in 操作能避免則避免,若實在避免不了,需要仔細評估 in 後邊的集合元素數量,控制在 1000 個之內。
(好吧,1000以內,好像真沒控制過,直接塞一個字串過去,沒看長度)
10.【參考】因阿里巴巴全球化需要,所有的字元儲存與表示,均以 utf-8 編碼,那麼字元計數方法注意:
說明:
SELECT LENGTH("阿里巴巴"); 返回為 12
SELECT CHARACTER_LENGTH("阿里巴巴"); 返回為 4
如果要使用表情,那麼使用 utfmb4 來進行儲存,注意它與 utf-8 編碼。
11.【參考】 TRUNCATE TABLE 比 DELETE 速度快,且使用的系統和事務日誌資源少,但 TRUNCATE無事務且不觸發 trigger,有可能造成事故,故不建議在開發程式碼中使用此語句。
說明: TRUNCATE TABLE 在功能上與不帶 WHERE 子句的 DELETE 語句相同
(都不觸發事務,你怕不怕,想回滾都沒門)
1. 【強制】在表查詢中,一律不要使用 * 作為查詢的欄位列表,需要哪些欄位必須明確寫明。
說明: 1)增加查詢分析器解析成本。 2)增減欄位容易與 resultMap 配置不一致。
(少查點對資料庫總是好的)
2. 【強制】 POJO 類的 boolean 屬性不能加 is,而資料庫欄位必須加 is_,要求在 resultMap 中進行欄位與屬性之間的對映。
說明: 參見定義 POJO 類以及資料庫欄位定義規定,在 sql.xml 增加對映,是必須的。
(和實體是反的,對映時要注意)
3. 【強制】不要用 resultClass 當返回引數,即使所有類屬性名與資料庫欄位一一對應,也需要定義;反過來,每一個表也必然有一個與之對應。
說明: 配置對映關係,使欄位與 DO 類解耦,方便維護。
(又要改程式碼了)
4. 【強制】 xml 配置中引數注意使用: #{}, #param# 不要使用${} 此種方式容易出現 SQL 注入。
5. 【強制】 iBATIS 自帶的 queryForList(String statementName,int start,int size)不推薦使用。
說明:其實現方式是在資料庫取到 statementName 對應的 SQL 語句的所有記錄,再通過 subList
取 start,size 的子集合,線上因為這個原因曾經出現過 OOM。
正例: 在 sqlmap.xml 中引入 #start#, #size#
Map<String, Object> map = new HashMap<String, Object>();
map.put("start", start);
map.put("size", size);
6. 【強制】不允許直接拿 HashMap 與 HashTable 作為查詢結果集的輸出。
反例: 某同學為避免寫一個<resultMap>,直接使用 HashTable 來接收資料庫返回結果,結果
出現日常是把 bigint 轉成 Long 值,而線上由於資料庫版本不一樣,解析成 BigInteger,導致線上問題。
(接手的專案裡還真遇到過不少)
7. 【強制】更新資料表記錄時,必須同時更新記錄對應的 gmt_modified 欄位值為當前時間。
(別忘記set一下就好了)
8. 【推薦】不要寫一個大而全的資料更新介面,傳入為 POJO 類,不管是不是自己的目標更新字
段,都進行 update table set c1=value1,c2=value2,c3=value3; 這是不對的。執行 SQL 時,
儘量不要更新無改動的欄位,一是易出錯;二是效率低;三是 binlog 增加儲存。
(不要整些沒用的進去)
9. 【參考】 @Transactional 事務不要濫用。事務會影響資料庫的 QPS,另外使用事務的地方需要
考慮各方面的回滾方案,包括快取回滾、搜尋引擎回滾、訊息補償、統計修正等。
(註解不能亂加,好多時候習慣性的就加上去了)
10.【參考】 <isEqual>中的 compareValue 是與屬性值對比的常量,一般是數字,表示相等時帶上
此條件; <isNotEmpty>表示不為空且不為 null 時執行; <isNotNull>表示不為 null 值時執行。