Mysql數據庫設計


目的:減少數據冗余、避免數據維護異常、節約存儲空間、高效訪問

設計的步驟

① 需求分析
② 概念結構設計:E-R圖
③ 邏輯結構設計:將E-R圖轉換為某一種數據模型,並優化。
④ 物理結構設計:選哪種數據庫
⑤ 數據庫實施
⑥ 數據庫維護和優化:建表、索引優化、大表拆分

需求分析

  1. 數據是什么、有什么屬性、特點(時效性?核心數據?增長情況?)、哪些屬性或屬性組合可以唯一標識一個實體

    • 用戶模塊:數據需要永久存儲、隨時間逐漸增加、是否需要分庫分表?
    • 商品模塊:永久存儲,下線商品歸檔存儲
    • 訂單模塊:永久存儲、分庫分表
    • 購物車模塊:不用永久存儲(設置歸檔、清理規則)
    • 供應商模塊:永久存儲
  2. 實體與實體之間的關系(1對1?1對多?多對多?)

概念結構設計

關系(一個關系對應一張表)、元組(一行)、屬性(一列)、候選碼(唯一確定一個元組的屬性組)、主碼(其中一個候選碼)、主屬性(可以組成候選碼的屬性)、域(屬性取值范圍)、分量(元組中的一個屬性值)

E-R圖:矩形(實體集,上面的數據模塊)、菱形(聯系集(關系名稱),上面實體與實體之間的關系)、橢圓(實體屬性、下划線標識主碼)

  • 局部E-R圖設計
    1.確定局部范圍
    各個部門或各個主要功能作為局部
    2.確定實體與屬性
    ① 屬性是不能再分的數據項;
    ② 聯系只發生在兩實體之間;
    ③ 原則上,能夠作為屬性,就不要作為實體。
  • 合並成總體E-R圖
    1.消除各局部E-R圖的沖突問題。
    2.按公共實體名合並,生成初步E-R圖。
    3.消除冗余的屬性和冗余的聯系,生成總體E-R圖。

邏輯結構設計

通過ER圖將需求轉化為數據庫的邏輯模型,與具體的DBMS系統無關

邏輯設計的一些條件
① 冗余應盡可能少;

多個地方存在或者可以通過某列計算得到

② 應盡可能避免插入、更新、刪除異常;

插入異常(有這個就會有下面兩個):某實體隨另一個實體的存在而存在,如新開課程沒有學生選修時,新開課程的課程號、課程名插不進去。

更新異常:更改某實體的某一屬性,需要更新多行

刪除異常:刪除某一行數據后,另一個不同實體信息丟失。

③ 消去關系中不合適的屬性依賴關系。(如選修某門課的學生畢業了,在刪除學生信息的同時,把課程信息也刪除掉。)

函數依賴
完全函數依賴:x’是x的真子集,存在x→y,但對每一個x’都有x’!→y,則稱y完全函數依賴於x。如(學號,班級,姓名)假設不同的班級學號有相同的,班級內學號不能相同,在R關系中,(學號,班級)->(姓名),但是(學號)->(姓名)不成立,(班級)->(姓名)不成立,所以姓名完全函數依賴與(學號,班級);

部分函數依賴:存在x→y,若x’是x的真子集,存在x’→y,則稱y部分函數依賴於x。如(學號,身份證號)->(姓名),(學號)->(姓名),(身份證號)->(姓名);所以姓名部分函數依賴與(學號,身份證號)

傳遞函數依賴:x→y,y→z,但y!→x, 則x傳遞函數依賴z,如(學號)->(宿舍),宿舍!=學號,(宿舍)->(費用),費用!=宿舍

范式
一個關系的非主屬性函數依賴於主碼的程度。一個關系從低級范式向高級范式的轉換過程稱為關系規范化。

  • 第一范式(1NF)條件:若關系R的所有屬性不能再分,即沒有HBase中的列族。

  • 第二范式(2NF):若關系R∈1NF,消除非主屬性對主碼的部分依賴,則稱R∈2NF。通常實現有增加一列unique標識,或者拆分。例如:如果一個表有商品名稱、供應商名稱和其他非主屬性的商品相關列,而相同商品名稱的行僅僅是供應商名稱不同(所以商品名稱、供應商名稱這兩列都可作為唯一標識),從而存在部分依賴。解決方法是商品和供應商各自單獨一張表,並加上一張映射兩表關系的表。

  • 第三范式(3NF):消去非主屬性對主碼的傳遞依賴。例子:一個表有商品名稱、分類和分類描述,這里存在“商品名稱 - 分類 - 分類描述”的依賴關系,分類描述對主碼是傳遞依賴。那么就應該把 “商品名稱” 和 “分類和分類描述” 拆分為兩張表,並加上一張映射兩表關系的表。

  • BCNF范式:復合關鍵字之間不存在依賴關系,即去除主屬性對於候選碼的部分函數依賴與傳遞函數依賴。例子:一個表有商品id、供應商、供應商聯系人,其中供應商和聯系人一一對應。此時“商品id + 供應商” 或 “商品id + 供應商聯系人”可以確定一行信息,而供應商和“供應商聯系人+商品id”存在部分依賴關系。解決方法是把這個表拆分為“供應商 + 商品id” 和 “供應商 + 供應商聯系人”兩個表。

物理結構設計

  • 選擇數據庫管理系統、存儲引擎(如無意外都是Innodb)
  • 定義數據庫、表及字段命名規范
  • 字段類型:處理速度,數字 > 日期 > 字符;IO速度,列越短,即每個字段定義的大小。
  • 反范式化設計

命名

  1. 表達是與否概念的字段,必須使用is_xxx的方式命名,數據類型是unsigned tinyint( 1表示是,0表示否)。
  2. 表名、字段名必須使用小寫字母或數字,禁止出現數字開頭,禁止兩個下划線中間只出現數字。數據庫字段名的修改代價很大,因為無法進行預發布,所以字段名稱需要慎重考慮。
  3. 表名不使用復數名詞。
  4. 臨時表以tmp前綴,以日期為后綴。備份庫以bak前綴,以日期后綴。
  5. 禁用保留字,如descrangematchdelayed等,請參考MySQL官方保留字。
  6. 主鍵索引名為pk_字段名;唯一索引名為uk_字段名;普通索引名則為idx_字段名。
  7. 表的命名最好是加上“業務名稱_表的作用”。
  8. 庫名與應用名稱盡量一致。

字段類型

  1. 合適的字符存儲長度,如127 or 200百以上smallint、3 or 6萬以上mediumint、8 or 16百int、20 or 40億以上bingint
  2. 小數類型為decimal,禁止使用float和double(除非不用精確)。如果存儲的數據范圍超過decimal的范圍,建議將數據拆成整數和小數分開存儲。
  3. 如果存儲的字符串長度幾乎相等,使用char定長字符串類型。如果列中最大數據長度小於50Byte,也用char,大於這個值就用VARCHAR。
  4. varchar是可變長字符串,不預先分配存儲空間,長度不要超過5000,如果存儲長度大於此值,定義字段類型為text,獨立出來一張表,用主鍵來對應,避免影響其它字段索引效率。
  5. 所有存儲相同數據的列名和列類型必須一致
  6. 使用inet_aton 和 int_ntoa實現字符串和數字類型的轉換

反范式化

字段允許適當冗余,以提高查詢性能,但必須考慮數據一致。冗余字段應遵循:

  • 不是頻繁修改的字段。
  • 不是varchar超長字段,更不能是text字段。

商品類目名稱使用頻率高,字段長度短,名稱基本一成不變,可在相關聯的表中冗余存儲類目名稱,避免關聯查詢。

補充

  1. 表必備三字段:id, gmt_create, gmt_modified。 其中id必為主鍵,類型為unsigned bigint、單表時自增、步長為1。gmt_create, gmt_modified的類型均為datetime類型,前者現在時表示主動創建,后者過去分詞表示被動更新。
  2. 如果修改字段含義或對字段表示的狀態追加時,需要及時更新字段注釋。
  3. 單表行數超過500萬行或者單表容量超過2GB,才推薦進行分庫分表。
  4. 庫和表字符集合統一UTF8
  5. 表和字段都需要添加注釋
  6. 禁止預留字段、存儲圖片等二進制數據、在線上做壓力測試、從開發/測試環境連接數據庫
  7. 避免觸發器
  8. 冷熱分表
  9. 對大表使用pt-online-schema-change修改表結構

數據庫維護和優化

維護數據字典

索引

強制

  1. 業務上具有唯一特性的字段,即使是多個字段的組合,也必須建成唯一索引。
  2. 超過三個表禁止join。需要join的字段,數據類型必須絕對一致;多表關聯查詢時,保證被關聯的字段需要有索引。
  3. 在varchar字段上建立索引時,必須指定索引長度,沒必要對全字段建立索引,根據實際文本區分度決定索引長度即可。(一般對字符串類型數據,長度為20的索引,區分度會高達90%以上,可以使用count(distinct left(列名, 索引長度))/count(*)的區分度來確定。)
  4. 頁面搜索嚴禁左模糊或者全模糊,如果需要請走搜索引擎來解決。 (索引文件具有B-Tree的最左前綴匹配特性,如果左邊的值未確定,那么無法使用此索引。)

推薦

  1. 如果有order by的場景,請注意利用索引的有序性。order by 最后的字段是組合索引的一部分,並且放在索引組合順序的最后,避免出現file_sort的情況,影響查詢性能。

    where a=? and b=? order by c; 索引:a_b_c 
    WHERE a>10 ORDER BY b; 索引a_b無法排序。索引中有范圍查找,那么索引有序性無法利用
    
  2. 利用覆蓋索引來進行查詢操作,避免回表。能夠建立索引的種類分為主鍵索引、唯一索引、普通索引三種,而覆蓋索引只是一種查詢的一種效果,用explain的結果,extra列會出現:using index。

  3. 利用延遲關聯或者子查詢優化超多分頁場景。即要么控制返回的總頁數,要么對超過特定閾值的頁數進行SQL改寫。

    SELECT a.* FROM 表1 a, 
    (select id from 表1 where 條件 LIMIT 100000,20 ) b 
    where a.id=b.id
    
  4. 至少要達到 range 級別,要求是ref級別,如果可以是consts最好。

  5. 建組合索引的時候,區分度最高的在最左邊(另外字段長度小、最頻繁的也是在左邊)。但如果存在非等號和等號混合判斷條件時,在建索引時,請把等號條件的列前置。(一個SQL只能利用復合索引中的一列進行范圍查詢,用了非等號的列,后面列的索引就不會用到了)

  6. 防止因字段類型不同造成的隱式轉換,導致索引失效。如where語句中使用的格式與設定不同

  7. 主鍵選擇:建議自增ID。不使用UUID、MD5、HASH、字符串;不使用頻繁更新的列

  8. 索引選擇:select、update、delete語句中的where從句中的列;包含在order by、group by、distinct中的字段、join的關聯列

  9. 重復索引:如id int primay key和unique(id)重復

    冗余索引:如組合索引包含了主鍵;index(a,b,c), index(a,b)這里一旦涉及到a的查詢,就會用到第一個index。

維護表結構

表拆分

分區

Hash分區:根據分區鍵、分區數來划分,其中鍵值必須為int或通過函數可轉化為int

Range分區:默認情況下使用VALUES LESS THAN屬性,即每個分區不包括指定的那個值。場景:日期或時間類型、定期按分區范圍清理歷史數據。如果用了range分區,所有查詢最好包括分區鍵

List分區:自定義具體哪個值方法哪個分區。

避免跨分區查詢,在where從句中包含分區鍵。

主鍵或唯一索引會是分區鍵的一部分

PARTITION BY HASH(customer_id)/Hash(UNIX TIMESTAMP(login_time))
PARTITIONS 4;

PARTITION BY RANGE (customer_id) (
    PARTITION p0 VALUES LESS THAN (10000),
    PARTITION p1 VALUES LESS THAN (10000),
    PARTITION p2 VALUES LESS THAN (10000),
    PARTITION p3 VALUES LESS THAN MAXVALUE
);

PARTITION BY LIST (login_type) (
    PARTITION p0 VALUES (1,3,5,7,9),
    PARTITION p1 VALUES (2,4,6,8)
);

#增加/刪除分區
ALTER TABLE customer_login_log ADD PARTITION (PARTITION p4 VALUES LESS THAN(2018))
ALTER TABLE customer_login_log DROP PARTITION p0;

歸檔

分區數據歸檔遷移條件

  • Mysql >= 5.7

  • 結構相同

  • 歸檔到的數據表一定要是非分區表

  • 非臨時表;不能有外鍵約束

  • 歸檔引擎為archive

# 遷移和刪除(否則新數據還會插入該分區)
ALTER TABLE customer_login_log EXCHANGE PARTITION p1 WITH TABLE arch_customer_login_log
ALTER TABLE customer_login_log DROP PARTITION p1

# 更改引擎(之后只能讀不能寫)
ALTER TABLE arch_customer_login_log ENGINE=ARCHIVE

其他

  1. 連接不同庫要用不同賬號

參考:

高性能可擴展MySQL數據庫設計及架構優化 電商項目,sqlercn,https://coding.imooc.com/class/79.html

阿里巴巴Java開發手冊


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM