1. 程式人生 > 其它 >大佬都在用的資料庫設計規範!你不點進來看看嘛?

大佬都在用的資料庫設計規範!你不點進來看看嘛?

建表規約

  • 表達是與否概念的欄位,必須使用is_xxx命名,資料型別是unsigned tinyint(1-是,0-否)
    • 任何欄位如果是非負數,必須是unsigned
    • POJO類中的任何布林型變數,都不要加is字首
    • 需要在< resultMap >設定從is_xxx到Xxx的對映關係
    • 資料庫表示是與否的值,使用tinyint型別
    • 堅持is_ xxx的命名方式是為了明確取值含義和取值範圍
  • 表名,欄位名必須使用小寫字母(或數字),禁止出現數字開頭,禁止兩個下劃線中間只出現數字.資料庫欄位名的修改代價很大,因為無法進行預釋出,所以欄位名稱需要慎重考慮
    • MySQL在windows下不區分大小寫,但在Linux下預設是區分大小寫的
    • 因此,資料庫名,表名,欄位名,都不允許出現任何大寫字母
  • 表名不使用複數名詞
    • 表名應該僅僅表示表裡面的實體內容,不應該表示實體數量
    • 對於DAO類名也是單數形式,符合表達習慣
  • 禁止使用MySQL的官方保留字命名:
    • desc
    • range
    • match
    • delayed
  • 索引命名:
    • pk_欄位名: 主鍵primary key索引
    • uk_欄位名: 唯一unique key索引名
    • idx_欄位名: 普通index索引名
  • 小數型別為decimal, 禁止使用float,double
    • float和double在儲存的時候,存在精度損失的問題,很可能在值比較時,得到不正確的結果
    • 如果儲存的資料範圍超過decimal
      的範圍,建議將資料拆分成整數和小數分開儲存
  • 如果儲存的字串長度幾乎相等,使用char定長字串型別
  • varchar是可變長字串,不預先分配儲存空間,長度不要超過5000
    • 如果長度大於此值,定義字串型別為text, 獨立出來一張表,用主鍵來對應,避免影響其它欄位索引效率
  • 表必備的三個欄位:
    • id: 主鍵,型別為bigint,unsigned,單表時自增,步長為1
    • gmt_create: 型別為datetime,現在時表示主動建立
    • gmt_modified 型別為datetime,過去分詞表示被動更新
  • 表的命名最好加上[業務名稱_表的作用]
  • 庫名與應用名稱儘量一致
  • 如果修改欄位含義或者對欄位的表示狀態追加時,需要及時更新欄位註釋
  • 欄位允許適當冗餘以提高查詢效能,但必須考慮資料一致.冗餘的欄位應遵循:
    • 不是頻繁修改的欄位
    • 不是varchar超長欄位,更不能是text欄位
      • 商品類目名稱使用頻率高,欄位長度短,名稱基本一成不變,可在相關聯的表中冗餘儲存類目名稱,避免關聯查詢
  • 單錶行數超過500萬行或者單表容量超過2GB, 才推薦進行分庫分表
    • 如果預計三年後的資料量根本達不到這個級別,不要在建立表時就分庫分表
  • 合適的字元儲存長度,不但節約資料庫表空間,節約索引儲存,更重要的是提升檢索速度

索引規約

  • 業務上具有唯一特性的欄位,即使是多個欄位的組合,也必須建成唯一索引
    • 索引不會影響insert的速度,這個速度可以忽略,但提高查詢速度是明顯的
    • 即使在應用層做了非常完善的校驗控制,只要沒有唯一索引,必然有髒資料產生
  • 超過三個表禁止join, 需要join的欄位 ,資料型別必須絕對一致. 多表關聯查詢時,保證被關聯的欄位需要有索引
  • 在varchar欄位上建立索引時,必須指定索引長度,沒必要對全欄位建立索引,根據實際文字區分度決定索引長度即可
    • 索引長度與區分度是一對矛盾體
      • 一般對字串型別資料,長度為20的索引,區分度會高達90%以上
      • 可以使用count(distinct left(列名, 索引長度)) / count(*) 的區分度來確定
  • 頁面搜尋嚴禁左模糊或者全模糊,如果需要要使用搜索引擎來解決
    • 索引檔案具有B-Tree的最左字首匹配特性,如果左邊的值未確定,無法使用此索引
  • 如果有order by的場景,要注意利用索引的有序性 .order by最後的欄位是組合索引的一部分,並且放在索引組合順序的最後,避免出現file_sort的情況,影響查詢效能
where a=? and b=? order by c;
索引: a_b_c

要是在索引中有範圍查詢,那麼索引有序性就無法利用(WHERE a>10 ORDER BY b; 索引:a_b無法排序)

  • 利用覆蓋索引來進行查詢操作,避免回表
    • 比如一本書需要知道第11章是什麼標題,只需要目錄瀏覽一下就更好,這個目錄就起到覆蓋索引的作用
    • 能夠建立索引的種類分為主鍵索引,唯一索引,普通索引三種,而覆蓋索引只是一種查詢的效果
    • explain的結果,extra列會出現: using index
  • 利用延遲關聯或者子查詢優化超多分頁場景:
    • MySQL不是跳過offset行,而是取offset+N行,然後返回放棄前offset行,返回N行
    • 當offset特別大的時候,效率就非常低下,要麼控制返回的總頁數,要麼對超過特定閾值的頁數進行SQL改寫
    • 先快速定位需要獲取的id欄位,然後再關聯:
SELECT a.* FROM table1 a,(select id from table1 where condition LIMIT 100000,20) b where a.id=b.id
  • SQL效能優化的目標: 至少要達到range級別,要求是ref級別,最好是consts級別
    • consts: 單表中最多隻有一個匹配行(主鍵或者唯一索引),在優化階段即可讀取到資料
    • ref: 指的是使用普通的索引(normal index)
    • range: 指對索引進行範圍檢索
      • explain表的結果,type=index,索引物理檔案全掃描,速度非常慢
      • 這個index級別比range還低,但比全表掃描要好的多
  • 建立組合索引的時候,區分度最高的在最左邊
    • 如果 where a=? and b=?;如果a列幾乎接近於唯一值,只需要單建idx_a索引即可
    • 存在非等號和等號混合時,在建立索引時,等號條件列前置
      • 比如 where c>? and d=?; 即使c的區分度更高,也必須要將d放在索引的最前列,即索引idx_d_c
  • 要注意防止因為欄位型別不同造成隱式轉換,導致索引失效
  • 建立索引有以下錯誤的觀點:
    • 認為一個查詢就需要建一個索引
    • 認為索引會消耗空間,嚴重拖慢更新和新增速度
    • 抵制唯一索引,認為業務的唯一性需要在應用層通過"先查後插"的方式解決

SQL語句規約

  • 不要使用count(列名)count(常量) 來代替count(*), count(*)是SQL92定義的標準統計行數的方法 ,跟資料庫無關,跟NULL和非NULL無關
    • count(*) 會統計只為NULL的行
  • count(distinct col) 計算該列出NULL之外的不重複行數,注意 count(distinct col1, col2) 如果其中一列全為NULL, 那麼即使另一列有不同的值,也返回0
  • 當某一列的值全是NULL時, count(NULL)的返回結果為0,但sum(col)返回結果為NULL, 因此使用sum要注意NPE問題
    • 使用以下方式來規避sum的NPE問題:
SELECT IF(ISNULL(SUM(g)),0,SUM(g)) FROM TABLE;
  • 使用ISNULL來判斷是否為NULL
    • NULL與任何值的直接比較都為NULL:
      • NULL<>NULL的返回結果是NULL,而不是false
      • NULL==NULL的返回結果是NULL,而不是true
      • NULL<>1的返回結果是NULL,而不是true
  • 在程式碼中寫分頁邏輯時,若count為0應直接返回,避免執行後面的分頁語句
  • 不得使用外來鍵與級聯,一切外間的概念必須在應用層解決
    • 比如學生和成績的關係:
      • 學生表中的student_id是主鍵,那麼成績表中的student_id則為外來鍵
      • 如果更新學生表中的student_id,同時觸發成績表中的student_id更新,即為級聯更新
    • 外來鍵與級聯更新適用於單機低併發,不適合分散式,高併發叢集
    • 級聯更新是強阻塞,存在資料庫更新風暴的風險
    • 外來鍵影響資料庫的插入速度
  • 禁止使用儲存過程,儲存過程難以除錯和擴充套件,更沒有移植性
  • 資料訂正(資料刪除,修改記錄操作)時,要先select, 避免出現誤刪除,確認無誤才能執行更新語句
  • in操作能避免就避免,若實在避免不了,需要仔細評估in後面集合元素數量,控制在1000個之內
  • 如果有國際化需要,所有的字元儲存與表示,都要以UTF-8編碼
  • TRUNCATE TABLEDELETE速度快,且使用的系統和事務日誌資源少,但TRUNCATE無事務且不觸發trigger, 有可能造成事故,所以不要使用TRUNCATE語句

ORM對映規約

  • 在表查詢中,一律不要使用 * 作為查詢欄位列表,需要哪些欄位必須明確寫明
    • 增加查詢分析器的解析成本
    • 增減欄位容易與resultMap配置不一致
    • 無用欄位增加網路消耗,尤其是text型別欄位
  • POJO類的布林屬性不能加is, 而資料庫欄位必須加is_, 要求在resultMap中進行欄位與屬性之間的對映
    • 定義POJO類以及資料庫欄位定義規定,在中增加對映,是必須的
    • 在MyBatis Generator生成的程式碼中,需要進行對於的修改
  • 不要使用resultClass當返回引數,即使所有類屬性名與資料庫欄位一一對應,也需要定義,每一個表一定有一個POJO類對應
    • 配置對映關係,使欄位與DAO類解耦,方面維護
  • Sql.xml配置引數使用 #{ } 或者 #param#. 不允許使用 ${ }, 這種方式容易出現SQL注入
  • 不要使用iBATIS自帶的queryForList(String statementName, int start, int size)
    • 這個方法的實現方式是在資料庫取到statementName對應的SQL語句的所有記錄,再通過subList取start,size的子集合
  • 不允許直接使用HashMap與HashTable作為查詢結果集的輸出
    • resultClass="HashTable",會置入欄位名和屬性值,但是值的型別不可控
  • 更新資料表記錄時,必須同時更新記錄對應的gmt_modified欄位值為當前時間
  • 不要寫一個大而全的資料更新介面:
    • 不要傳入一個POJO類進行更新
    • 執行SQL時,不要更新無改動的欄位.一是易出錯,二是效率低,三是增加binlog儲存
  • @Transactional事務不要濫用:
    • 事務會影響資料庫的QPS
    • 使用事務需要考慮各方面的回滾方案,包括快取回滾,搜尋引擎回滾,訊息補償,統計修正
  • < isEqual > 中的compareValue是與屬性值對比的常量,一般是數字,表示相等時帶上此條件
  • < isNotEmpty > 表示不為空且不為null時執行
  • < isNotNull > 表示不為null時執行