前面講了SQL優化以及索引的使用、設計優化了,那么接下來就到表的設計與優化啦!!!真實地去設計優化單表結構以及講述多表設計基本原則(結合真實的生產環境的取舍來講述)。
文章結構:(1)單表設計與優化;(2)基于單表設計的多表設計原則(含表拆分原則);(均以實際生產開發環境下的環境為基準)
文章目錄:
(1)單表設計與優化
設計規范化表,消除數據冗余(以使用正確字段類型最明顯)
- 前三范式
- 所有字段類型
- 所有字段類型羅列
- 針對常用的varchar,我們來思考幾個問題
- 給出幾個類型選取建議
適當的冗余,增加計算列:(實際開發中必須思考的點)
索引的設計
主鍵和外鍵的必要性(實際項目開發的重要取舍)
存儲過程、視圖、函數的適當使用(這些是優化的方法,這幾個后面會講)
傳說中的‘三少原則’
分割你的表,減小表尺寸
字段設計原則
(2)基于單表設計的多表設計原則
表關系
- 一對一關系
- 一對多關系(多對一)
- 多對多關系
- 注意
- 外鍵與索引
- 建立關系
分表原則:(涉及分區分表問題探究,以后的篇章再補充實例)
- 表拆分方式
- 垂直切分
- 水平拆分(分表,分區)–按表中某一字段值的范圍劃分
- 散列庫表(基于hash算法的切分)
- 在了解完分表了,我們先來理解區分分區與分表吧
- 表拆分建議:(針對大系統)
一、單表設計與優化:
(1)設計規范化表,消除數據冗余(以使用正確字段類型最明顯):
數據庫范式是確保數據庫結構合理,滿足各種查詢需要、避免數據庫操作異常的數據庫設計方式。滿足范式要求的表,稱為規范化表,范式產生于20世紀70年代初,一般表設計滿足前三范式就可以,在這里簡單介紹一下前三范式。
第一范式(1NF)無重復的列
所謂第一范式(1NF)是指在關系模型中,對域添加的一個規范要求,所有的域都應該是原子性的,即數據庫表的每一列都是不可分割的原子數據項,而不能是集合,數組,記錄等非原子數據項。
第二范式(2NF)屬性
在1NF的基礎上,非碼屬性必須完全依賴于碼[在1NF基礎上消除非主屬性對主碼的部分函數依賴]
第三范式(3NF)屬性
在1NF基礎上,任何非主屬性不依賴于其它非主屬性[在2NF基礎上消除傳遞依賴。
通俗點講:
第一范式:屬性(字段)的原子性約束,要求屬性具有原子性,不可再分割;
第二范式:記錄的惟一性約束,要求記錄有惟一標識,每條記錄需要有一個屬性來做為實體的唯一標識,即每列都要和主鍵相關。
第三范式:屬性(字段)冗余性的約束,即任何字段不能由其他字段派生出來,在通俗點就是:主鍵沒有直接關系的數據列必須消除(消除的辦法就是再創建一個表來存放他們,當然外鍵除外)。即:確保每列都和主鍵列直接相關,而不是間接相關。
如果數據庫設計達到了完全的標準化,則把所有的表通過關鍵字連接在一起時,不會出現任何數據的復本(repetition)。標準化的優點是明顯的,它避免了數據冗余,自然就節省了空間,也對數據的一致性(consistency)提供了根本的保障,杜絕了數據不一致的現象,同時也提高了效率。
尤其是正確字段類型的選擇:(先列出所有字段類型再寫建議)
所有字段類型:
(一)整型數值:
整數類型
字節數
最小值 ~ 最大值
tinyint
1
-128~127 或 0-255
smallint
2
-32768~32767 或 0~65535
mediumint
3
-8388608~8388607 或 0~1677215
int
4
-2147483648~2147483647 或 0~4294967295
bigint
8
-9223372036854775808~9223372036854775807 或 0~18446744073709551615
(二)浮點數類型
浮點數類型
字節數
最小值 ~ 最大值
double
4
±1.175494351E-38 ~ ± 3.402823466E+38
double
8
±2.2250738585072014E-308 ~ ±1.7976931348623157E+308
(三)定點數類型
定點數類型
字節數
最小值 ~ 最大值
dec(m,d)
m+2
最大取值范圍與double相同,給定decimal的有效值取值范圍由m和d決定
關于浮點數與定點數有點看法:
浮點數相對于定點數的優點是在長度一定的情況下,浮點數能夠表示更大的數據范圍;它的缺點是會引起精度問題。
使用時我們要注意:
1. 浮點數存在誤差問題;
2. 對貨幣等對精度敏感的數據,應該用定點數表示或存儲;
3. 編程中,如果用到浮點數,要特別注意誤差問題,并盡量避免做浮點數比較;
4. 要注意浮點數中一些特殊值的處理。
(四)位類型
位類型
字節數
最小值 ~ 最大值
bit(m)
1~8
bit(1) ~ bit(64)
(五)日期時間類型
時間日期類型
字節數
最小值 ~ 最大值
date
4
1000-01-01 ~ 9999-12-31
datetime
8
1000-01-01 00:00:00 ~ 9999-12-31 23:59:59
timestamp
4
19700101080001 ~ 2038年某個時刻
time
3
-838:59:59 ~ 838:59:59
year
1
1901 ~ 2155
mysql中用now()寫入當前時間。
(六)字符串類型:
字符串類型
字節數
取值范圍
char(m)
m
m為0 ~ 255之間的整數
varchar(m)
值長度+1
m為0~65535之間的整數
tinytext
值長度+2
允許長度0~255字節
text
值長度+2
允許長度0~65535字節
mediumtext
值長度+3
允許長度0~167772150字節
longtext
值長度+3
允許長度0~4294967295字節
binary(m)
m
允許0~m個字節定長的字符串
varbinary(m)
值長度+1
允許0~m個字節變長的字符串
tinyblob
值長度+1
允許長度0~255字節
blob
值長度+2
允許長度0~65535字節
mediumblob
值長度+3
允許長度0~167772150字節
longblob
值長度+4
允許長度0~4294967295字節
enum
1或2
1255個成員需要1個字節存;25565535個成員,2個字節存
set
1/2/3/4/8
類似enum,set一次可以選取多個成員,而enum只能一個
針對常用的varchar,我們來思考幾個問題:
1)varchar的長度?
MySQL的文檔,其中對varchar字段類型這樣描述:varchar(m) 變長字符串。m 表示最大列長度。m的范圍是0到65,535。(VARCHAR的最大實際長度由最長的行的大小和使用的字符集確定,最大有效長度是65,532字節)。
mysql varchar(50) 不管中文 還是英文 都是存50個的,但是一個表中所有varchar字段的總長度跟編碼有關,如果是utf-8,那么大概65535/3,如果是gbk,那么大概65535/2.
2)存儲限制?編碼長度限制?行長度限制?超出了,會變成怎樣?
針對第一個問題:varchar 字段是將實際內容單獨存儲在聚簇索引之外,實際存儲從第二個字節開始,接著要用1到2個字節表示實際長度(長度超過255時需要2個字節),因此最大長度不能超過65535。
針對第二個問題:字符類型若為gbk,每個字符最多占2個字節。字符類型若為utf8,每個字符最多占3個字節。
針對第三個問題:導致實際應用中varchar長度限制的是一個行定義的長度。 MySQL要求一個行的定義長度不能超過65535。若定義的表長度超過這個值,則提示
ERROR 1118 (42000): Row size too large. The maximum row size for the used table type, not counting BLOBs, is 65535. You have to change some columns to TEXT or BLOBs。
針對第四個問題:若定義的時候超過上述限制,則varchar字段會被強行轉為text類型,并產生warning。
3)與char的對比:
CHAR(M)定義的列的長度為固定的,M取值可以為0~255之間,當保存CHAR值時,在它們的右邊填充空格以達到指定的長度。當檢 索到CHAR值時,尾部的空格被刪除掉。在存儲或檢索過程中不進行大小寫轉換。CHAR存儲定長數據很方便,CHAR字段上的索引效率級高,比如定義 char(10),那么不論你存儲的數據是否達到了10個字節,都要占去10個字節的空間,不足的自動用空格填充。
CHAR和VARCHAR最大的不同就是一個是固定長度,一個是可變長度。由于是可變長度,因此實際存儲的時候是實際字符串再加上一個記錄 字符串長度的字節(如果超過255則需要兩個字節)。如果分配給CHAR或VARCHAR列的值超過列的最大長度,則對值進行裁剪以使其適合。如果被裁掉 的字符不是空格,則會產生一條警告。如果裁剪非空格字符,則會造成錯誤(而不是警告)并通過使用嚴格SQL模式禁用值的插入。
4)char、varchar與text的建議:
TEXT只能儲存純文本文件。
效率來說基本是char>varchar>text,但是如果使用的是Innodb引擎的話,推薦使用varchar代替char
char和varchar可以有默認值,text不能指定默認值
以下給出幾個類型選取建議
(一)數字類型:
1)不到不要使用DOUBLE,不僅僅只是存儲長度的問題,同時還會存在精確性的問題。
2)固定精度的小數,也不建議使用DECIMAL,建議乘以固定倍數轉換成整數存儲,可以大大節省存儲空間,且不會帶來任何附加維護成本。
3)對于整數的存儲,在數據量較大的情況下,建議區分開 TINYINT / INT / BIGINT 的選擇,因為三者所占用的存儲空間也有很大的差別,能確定不會使用負數的字段,建議添加unsigned定義。當然,如果數據量較小的數據庫,也可以不用嚴格區分三個整數類型。
4)對于整型數值,mysql支持在類型名稱后面的小括號內指定顯示寬度,例如int(5)表示當數值寬度小于5位時候在數值前面填滿寬度,一般配合zerofill屬性使用。如果一個列指定為zerofill,則MySQL自動為該列添加unsigned屬性。
5)在數據量較大時、建議把實數類型轉為整數類型。原因很簡單:1. 浮點不精確;2.定點計算代價昂貴。例如:要存放財務數據精確到萬分之一、則可以把所有金額乘以一百萬、然后存在BIGINT下。
(二)字符類型:
1)盡量不要使用 TEXT 數據類型,其處理方式決定了他的性能要低于char或者是varchar類型的處理。定長字段,建議使用 CHAR 類型,不定長字段盡量使用 VARCHAR,且僅僅設定適當的最大長度,而不是非常隨意的給一個很大的最大長度限定,因為不同的長度范圍,MySQL也會有不一樣的存儲處理。
2)char會刪除字符串尾部的空格,varchar不會,varchar向前補1-2字節;char定長。binary類似于char,binary只能保存二進制字符串。
char是固定長度,所以它的處理速度比varchar快得多,但缺點是浪費存儲空間,不能在行尾保存空格。在MySQL中,MyISAM建議使用固定長度代替可變長度列;InnoDB建議使用varchar類型,因為在InnoDB中,內部行存儲格式沒有區分固定長度和可變長度。
3)enum類型忽略大小寫。
4)text與blob區別:blob保存二進制數據;text保存字符數據,有字符集。text和blob不能有默認值。
應用:text與blob主要區別是text用來保存字符數據(如文章,日記等),blob用來保存二進制數據(如照片等)。blob與text在執行了大量刪除操作時候,有性能問題(產生大量的“空洞“),為提高性能建議定期optimize table 對這類表進行碎片整理。
關于text與blob我們有些看法建議:
1. BLOB和TEXT值也會引起自己的一些問題,特別是執行了大量的刪除或更新操作的時候。刪除這種值會在數據表中留下很大的"空洞",以后填入這些"空洞"的記錄可能長度不同,為了提高性能,建議定期使用 OPTIMIZE TABLE 功能對這類表進行碎片整理.
2. 在不必要的時候避免檢索大型的BLOB或TEXT值。
3. 把BLOB或TEXT列分離到單獨的表中。在某些環境中,如果把這些數據列移動到第二張數據表中,可以讓你把原數據表中 的數據列轉換為固定長度的數據行格式,那么它就是有意義的。這會減少主表中的碎片,使你得到固定長度數據行的性能優勢。它還使你在主數據表上運行 SELECT *查詢的時候不會通過網絡傳輸大量的BLOB或TEXT值。
(三)時間類型:
1)盡量使用TIMESTAMP類型,因為其存儲空間只需要 DATETIME 類型的一半。對于只需要精確到某一天的數據類型,建議使用DATE類型,因為他的存儲空間只需要3個字節,比TIMESTAMP還少。不建議通過INT類型類存儲一個unix timestamp 的值,因為這太不直觀,會給維護帶來不必要的麻煩,同時還不會帶來任何好處。
2)根據實際需要選擇能夠滿足應用的最小存儲日期類型。
3)timestamp,日期類型中只有它能夠和實際時區相對應。
(四)ENUM & SET:
對于狀態字段,可以嘗試使用 ENUM 來存放,因為可以極大的降低存儲空間,而且即使需要增加新的類型,只要增加于末尾,修改結構也不需要重建表數據。如果是存放可預先定義的屬性數據呢?可以嘗試使用SET類型,即使存在多種屬性,同樣可以游刃有余,同時還可以節省不小的存儲空間。
(五)LOB類型:
強烈反對在數據庫中存放 LOB 類型數據,雖然數據庫提供了這樣的功能,但這不是他所擅長的,我們更應該讓合適的工具做他擅長的事情,才能將其發揮到極致。
(2)適當的冗余,增加計算列:(實際開發中必須思考的點)
數據庫設計的實用原則是:在數據冗余和處理速度之間找到合適的平衡點。
滿足范式的表一定是規范化的表,但不一定是最佳的設計。很多情況下會為了提高數據庫的運行效率,常常需要降低范式標準:適當增加冗余,達到以空間換時間的目的。比如我們有一個表,產品名稱,單價,庫存量,總價值。這個表是不滿足第三范式的,因為“總價值”可以由“單價”乘以“數量”得到,說明“金額”是冗余字段。但是,增加“總價值”這個冗余字段,可以提高查詢統計的速度,這就是以空間換時間的作法。合理的冗余可以分散數據量大的表的并發壓力,也可以加快特殊查詢的速度,冗余字段可以有效減少數據庫表的連接,提高效率。
其中"總價值"就是一個計算列,在數據庫中有兩種類型:數據列和計算列,數據列就是需要我們手動或者程序給予賦值的列,計算列是源于表中其他的數據計算得來,比如這里的"總價值"
在SQL中創建計算列:
create table goods(
id int auto_increment not null,
c1 int,
c2 int,
c3 int as (c1+c2), //這個就是計算列啦
primary key(id)
)
(3)索引的設計:
表優化的重要途徑,百萬級別的表沒有索引,注定卡死。
(4)主鍵和外鍵的必要性(實際項目開發的重要取舍)
概述:主鍵與外鍵的設計,在全局數據庫的設計中,占有重要地位。 因為:主鍵是實體的抽象,主鍵與外鍵的配對,表示實體之間的連接。
主鍵:根據第二范式,需要有一個字段去標識這條記錄,主鍵無疑是最好的標識,但是很多表也不一定需要主鍵,但是對于數據量大,查詢頻繁的數據庫表,一定要有主鍵,主鍵可以增加效率、防止重復等優點。
主鍵的選擇也比較重要,一般選擇總的長度小的鍵,小的鍵的比較速度快,同時小的鍵可以使主鍵的B樹結構的層次更少。
主鍵的選擇還要注意組合主鍵的字段次序,對于組合主鍵來說,不同的字段次序的主鍵的性能差別可能會很大,一般應該選擇重復率低、單獨或者組合查詢可能性大的字段放在前面。
外鍵:外鍵作為數據庫對象,很多人認為麻煩而不用,實際上,外鍵在大部分情況下是很有用的,理由是:外鍵是最高效的一致性維護方法。
數據庫的一致性要求,依次可以用外鍵、CHECK約束、規則約束、觸發器、客戶端程序,一般認為,離數據越近的方法效率越高。但是!!!要謹慎使用級聯刪除和級聯更新,因為級聯刪除和級聯更新有些突破了傳統的關于外鍵的定義,功能有點太過強大,使用前必須確定自己已經把握好其功能范圍,否則,級聯刪除和級聯更新可能讓你的數據莫名其妙的被修改或者丟失。從性能看級聯刪除和級聯更新是比其他方法更高效的方法。
實際項目中的主外鍵取舍設計:(在性能和可擴展性之間尋求平衡)
邊緣模塊指的是小功能不常用需求很少再改的模塊;中心模塊是指關聯的東西太多的模塊、是很多表的主表;物理鍵指的是在表建立主外鍵關聯,邏輯主外鍵指的是利用字段去實現邏輯主外鍵關聯;熱點模塊指的是需求經常要改的模塊
大型系統:
1. 針對性能要求不高,安全要求高的模塊,推薦使用物理主外鍵關聯;針對性能要求高、安全自己控制的模塊,推薦不用物理外鍵;
2. 針對中心模塊和其他模塊的聯系,推薦使用物理主外鍵。
3. 針對熱點模塊,必須使用邏輯主外鍵
4. 針對邊緣模塊,推薦使用物理主外鍵
小系統??
隨便你啦,也就是20張表以下的系統。邏輯不復雜都無所謂啦,不過推薦還是使用外鍵。
注意:
不用外鍵而用程序控制數據一致性和完整性時,應該寫一層來保證,然后個個應用通過這個層來訪問數據庫。
外鍵是有性能問題的,不能過分追求。
(5)存儲過程、視圖、函數的適當使用(這些是優化的方法,這幾個后面會講):
很多人習慣將復雜操作都放在應用程序層,但如果你要優化數據訪問性能,將SQL代碼移植到數據庫上(使用存儲過程,視圖,函數和觸發器)也是一個很大的改進原因如下:
1)存儲過程減少了網絡傳輸、處理及存儲的工作量,且經過編譯和優化,執行速度快,易于維護,且表的結構改變時,不影響客戶端的應用程序
2)使用存儲過程,視圖,函數有助于減少應用程序中SQL復制的弊端,因為現在只在一個地方集中處理SQL
3)使用數據庫對象實現所有的TSQL有助于分析TSQL的性能問題,同時有助于你集中管理TSQL代碼,更好的重構TSQL代碼。
(6)傳說中的‘三少原則’:
1)數據庫的表越少越好
2)表的字段越少越好
3)字段中的組合主鍵、組合索引越少越好
這里的少是相對的,是減少數據冗余的重要設計理念而已。
實際上,我們為了減少單表查詢壓力,會把去分表,從而分發記錄量,避免一個超級表的誕生。
(7)分割你的表,減小表尺寸
如果你發現某個表的記錄太多,例如超過一千萬條,則要對該表進行水平分割。水平分割的做法是,以該表主鍵的某個值為界線,將該表的記錄水平分割為兩個表。
如果你若發現某個表的字段太多,例如超過八十個,則垂直分割該表,將原來的一個表分解為兩個表
(8)字段設計原則:
字段是數據庫最基本的單位,其設計對性能的影響是很大的。需要注意如下:
1)數據類型盡量用數字型,數字型的比較比字符型的快很多。
2)數據類型盡量小,這里的盡量小是指在滿足可以預見的未來需求的前提下的。
3)盡量不要允許NULL,除非必要,可以用NOT NULL+DEFAULT代替。
NULL 類型比較特殊,SQL 難優化。雖然 MySQL NULL類型和 Oracle 的NULL 有差異,會進入索引中,但如果是一個組合索引,那么這個NULL 類型的字段會極大影響整個索引的效率。此外,NULL 在索引中的處理也是特殊的,也會占用額外的存放空間。
4)少用TEXT和IMAGE,二進制字段的讀寫是比較慢的,而且,讀取的方法也不多,大部分情況下最好不用。
5)自增字段要慎用,不利于數據遷移
二、基于單表設計的多表設計原則:
(1)表關系:
一)一對一關系:
定義:
在這種關系中,關系表的每一邊都只能存在一個記錄。每個數據表中的關鍵字在對應的關系表中只能存在一個記錄或者沒有對應的記錄。這種關系和一對配偶之間的關系非常相似——要么你已經結婚,你和你的配偶只能有一個配偶,要么你沒有結婚沒有配偶。大多數的一對一的關系都是某種商業規則約束的結果,而不是按照數據的自然屬性來得到的。如果沒有這些規則的約束,你通常可以把兩個數據表合并進一個數據表,而且不會打破任何規范化的規則。
一對一關系又分為:一對一外鍵關聯和一對一主鍵關聯。
一對一主鍵關聯:要求兩個表的主鍵必須完全一致,通過兩個表的主鍵建立關聯關系。
可以看到下圖,很明顯的,班級編號作為主鍵的話,就是一個主鍵關聯了。
這里寫圖片描述
一對一外鍵關聯:
下面又很明顯看到,以班主任ID作為外鍵關聯起來的一個表。
這里寫圖片描述
二)一對多關系(多對一):
定義:
主鍵數據表中只能含有一個記錄,而在其關系表中這條記錄可以與一個或者多個記錄相關,也可以沒有記錄與之相關。這種關系類似于你和你的父母之間的關系。你只有一位母親,但是你母親可以有幾個孩子。
下圖可以看到:一對多-班級表有多個學生;多對一-多個學生屬于一個班級。
這里寫圖片描述
三)多對多關系:
定義:
兩個數據表里的每條記錄都可以和另一個數據表里任意數量的記錄(或者沒有記錄)相關。例如,如果你有多個兄弟姐妹,這對你的兄弟姐妹也是一樣(有多個兄弟姐妹),多對多這種關系需要引入第三個數據表,這種數據表稱為聯系表或者連接表,因為關系型系統不能直接實現這種關系。
在RDBMS中,必須使用中間表來表示多對多的關系。中間表我們可以分成兩種,一種是純粹表示關系的中間表,一種是表示中間實體的中間表。
純粹表示關系的中間表很簡單,只需要兩列:AID和BID,AID以外鍵關聯到A表的主鍵,BID以外鍵關聯到B表的主鍵,然后這兩個列組成聯合主鍵。這個中間表純粹是表示多對多關系而存在,在業務上不會有對應的實體與之對應。比如前面提到的學生和課程的關系,如果我們只需要知道哪些學生上哪些課,哪些課有哪些學生選,不需要有更多的信息的情況下,我們就可以建立“學生課程”中間表,里面只有學生ID和課程ID兩個字段。
這里寫圖片描述
中間實體是在純粹的中間關系表的基礎上,加上了更多的屬性,從而形成了一個新的實體。比如上面提到的學生和課程的關系,如果我們需要記錄學生選課的時間、學生選擇這門課程后的考試成績,那么我們就像建立一個“選課”實體,該實體具有如下屬性:
選課ID,主鍵
學生ID,與學生表做外鍵關聯
課程ID,與課程表做外鍵關聯
選課時間,DateTime類型
考試成績,記錄選修該課程后考試的最終成績
注意:
一)外鍵與索引:
外鍵是一種約束,與索引的概念不一樣,只是大多數情況下,我們建立外鍵時,都會在外鍵列上建立對應的索引。外鍵的存在會在每一次數據插入、修改時進行約束檢查,如果不滿足外鍵約束,則禁止數據的插入或修改,這必然帶來一個問題,就是在數據量特別大的情況下,每一次約束檢查必然導致性能的下降。索引其實也有類似的問題,索引如果建多了,那么在插入刪除修改數據時也要去維護對應的索引,所以索引的存在也會導致數據操作變慢。
不過外鍵與索引的優點不同,外鍵只是保證數據的一致性,并不能給系統性能帶來任何好處,所以由于外鍵導致的插入數據變慢會隨著數據量的增長而越來越嚴重。而索引的目的是為了檢索數據更快,維護數據時導致的索引數據的變更,對性能的影響不會像外鍵那樣隨著數據量增長而變得嚴重(當然大數量時的索引樹維護會比小數據量的索引樹維護更麻煩,但至少不是像外鍵那樣)。
出于性能的考慮,如果我們的系統完全由我們開發的程序使用,而不需要提供數據庫給其他應用系統寫入數據,而且對性能要求較高,那么我們可以考慮在生產環境中不使用外鍵,只需要建立能夠提高性能的索引。由于整個數據庫的操作都是由我們開發的程序來完成的,所以我們程序可以在開發過程中做好各方面的一致性檢查,保證操作的數據是滿足外鍵約束的,而不需要真正的存在這樣一個外鍵約束。怎么做到這一點呢,首先,我們在建立數據庫時有多個腳本,包括創建表、創建初始化數據、創建索引、創建外鍵等,我們在開發和測試環境中,都把這些腳本運行了,以使開發測試環境中的數據庫是完整的,經過大量測試保證應用程序能夠維護數據之間的約束的情況下,那么我們在生產時,并不需要運行創建外鍵這個腳本文件,只需要創建表、初始化數據、創建索引等即可。
二)建立關系
在開始著手考慮建立關系表之間的關系之前,你可能需要對數據非常熟悉。只有在熟悉數據之后,關聯會比你剛開始的時候更明顯。你的數據庫系統依賴于在兩個數據表中找到的匹配值來建立關系。
進行匹配的值都是主鍵和外鍵的值。(關系模型不要求一個關系必須對應的使用一個主鍵來確定。你可以使用數據表中的任何備選關鍵字來建立關系,但是使用主鍵是大家都已經接受的標準。)主鍵(primary key)唯一的識別表中的每個記錄。而外鍵(foreign key)只是簡單的將一個數據表中的主鍵存放在另外一個數據表中。同樣地,對于你來說也不需要做太多的工作——只是簡單地將主鍵加到關系表中,并將其定義為外鍵。
(2)分表原則:(涉及分區分表問題探究,以后的篇章再補充實例)
分表主要目的是為突破單節點數據庫服務器的 I/O 能力限制,解決數據庫擴展性問題。 同時分表分庫等思想也將引出以后的數據庫集群,主從復制、讀寫分離方案.....
為什么我們要分表分區???
日常開發中我們經常會遇到大表的情況,所謂的大表是指存儲了百萬級乃至千萬級條記錄的表。這樣的表過于龐大,導致數據庫在查詢和插入的時候耗時太長,性能低下,如果涉及聯合查詢的情況,性能會更加糟糕。分表和表分區的目的就是減少數據庫的負擔,提高數據庫的效率,通常點來講就是提高表的增刪改查效率。
(一)表拆分方式:
1)垂直切分:
這里寫圖片描述
定義:
把主鍵和一些數據表的列放在一個表中,然后把主鍵和另一些數據表的列放在一個表中。
如果一個表的某些列常用,另一些不常用,則可以采用垂直拆分。垂直拆分可以使數據行變小,一個數據頁就可以存放更多的數據,在查詢時候可以減少I/O次數。其缺點是需要管理冗余列,查詢所有數據時候需要join查找。
優點:
使得行數據變小,一個數據塊(Block)就能存放更多的數據,在查詢時就會減少I/O次數(每次查詢時讀取的Block 就少)。
可以達到最大化利用Cache的目的。
缺點:
表垂直分割后,主碼(主鍵)出現冗余,需要管理冗余列
會引起表連接JOIN操作(增加CPU開銷)需要從業務上規避
2)水平拆分(分表,分區)--按表中某一字段值的范圍劃分:
這里寫圖片描述
定義:
根據列的范圍值進行合理切分,放在多個獨立的表或分區中。
適用場景:
表很大,分割后可以降低查詢時候需要讀取的數據和索引的頁數,同時降低索引的層數,提高查詢速度。
表中的數據是獨立的,例如表中分別記錄各個地區的數據或不同時期的數據,特別是有些數據常用,而另一些數據不常用。
需要把數據放在多個存儲介質上。
需要把歷史數據和當前的數據拆分開。
例子:
當伴隨著某一個表的數據量越來越大,以至于不能承受的時候,就需要對它進行進一步的切分。一種選擇是根據key 的范圍來做切分,譬如ID 為 1-10000的放到表A上,ID 為10000~20000的放到表B。這樣的擴展就是可預見的。另一種是根據某一字段值來劃分,譬如根據用戶名的首字母,如果是A-D,就屬于表A,E-H就屬于表B。這樣做也存在不均衡性,當某個范圍超出了單點所能承受的范圍就需要繼續切分。還有按日期切分等等。
可以使用Mrg_Myisam引擎實現水平分表。
優點:單表大小可控,天然水平擴展。降低在查詢時需要讀的數據和索引的頁數,同時也降低了索引的層數,加快了查詢速度。
缺點:無法解決集中寫入瓶頸的問題。同時,水平分割會給應用增加復雜度,它通常在查詢時需要多個表名,查詢所有數據需要union操作。在許多數據庫應用中,這種復雜性會超過它帶來的優點,因為只要索引關鍵字不大,則在索引用于查詢時,表中增加兩到三倍數據量,查詢時也就增加讀一個索引層的磁盤次數。
3)散列庫表(基于hash算法的切分):
定義:
表散列與水平分割相似,但沒有水平分割那樣的明顯分割界限,采用Hash算法把數據分散到各個分表中, 這樣IO更加均衡。一般采用mod來切分,一開始確定切分數據庫的個數,通過hash取模來決定使用哪臺。這種方法能夠平均地來分配數據,但是伴隨著數據量的增大,需要進行擴展的時候,這種方式無法做到在線擴容。每增加節點的時候,就需要對hash 算法重新運算。
我們會按照業務或者功能模塊將數據庫進行分離,不同的模塊對應不同的數據庫或者表,再按照一定的策略對某個頁面或者功能進行更小的數據庫散列,比如用戶表,按照用戶ID進行表散列,散列128張表,則應就能夠低成本的提升系統的性能并且有很好的擴展性
優點:
數據分布均勻
缺點:
數據遷移的時候麻煩,不能按照機器性能分攤數據
(二)在了解完分表了,我們先來理解區分分區與分表吧。
分區:
定義:
分區和分表相似,都是按照規則分解表。不同在于分表將大表分解為若干個獨立的實體表,而分區是將數據分段劃分在多個位置存放,可以是同一塊磁盤也可以在不同的機器。分區后,表面上還是一張表,但數據散列到多個位置了。app讀寫的時候操作的還是大表名字,db自動去組織分區的數據。
分表定義:
分表是將一個大表按照一定的規則分解成多張具有獨立存儲空間的實體表,我們可以稱為子表,每個表都對應三個文件,MYD數據文件,.MYI索引文件,.frm表結構文件。這些子表可以分布在同一塊磁盤上,也可以在不同的機器上。app讀寫的時候根據事先定義好的規則得到對應的子表名,然后去操作它。
mysql分表和分區有什么聯系呢?:
1.都能提高mysql的性高,在高并發狀態下都有一個良好的表現。
2.分表和分區不矛盾,可以相互配合的,對于那些大訪問量,并且表數據比較多的表,我們可以采取分表和分區結合的方式(如果merge這種分表方式,不能和分區配合的話,可以用其他的分表試),訪問量不大,但是表數據很多的表,我們可以采取分區的方式等。
3.分表技術是比較麻煩的,需要手動去創建子表,app服務端讀寫時候需要計算子表名。采用merge好一些,但也要創建子表和配置子表間的union關系。
4.表分區相對于分表,操作方便,不需要創建子表。
(三)表拆分建議:(針對大系統)
其實這點沒有明確的判斷標準,比較依賴實際業務情況和經驗判斷。一般MySQL單表1000W左右的數據是沒有問題的(前提是應用系統和數據庫等層面設計和優化的比較好)。
1)對記錄多的表進行拆分。(幾百-上千萬級別的表)
2)需要拆分的表分為動態表和相對靜態表。動態表拆分到不同庫,靜態表存在于公共庫。從公共庫同步到分庫。實現表的連接。
3)按照年、月、地域等來分割,或者根據時間范圍、和很固定又清晰的字段值范圍等,具有確定的分割標志來分割。
好了,MySQL優化筆記(四)--表的設計與優化(單表、多表)講完了,這是我學習優化數據庫時候的筆記,這是積累的必經一步,我會繼續出這個系列文章,分享經驗給大家。歡迎在下面指出錯誤,共同學習!!你的點贊是對我最好的支持!!