Mysql表設計需要注意的問題
下面探討的數據庫爲MySQL 存儲引擎爲innodb因爲這是最常見的,使用最多的數據庫和引擎
什麼是頁分裂?
這是因爲聚簇索引採用的是平衡二叉樹算法,而且每個節點都保存了該主鍵所對應行的數據,假設插入數據的主鍵是自增長的,那麼根據二叉樹算法會很快的把該數據添加到某個節點下,而其他的節點不用動;但是如果插入的是不規則的數據,那麼每次插入都會改變二叉樹之前的數據狀態。從而導致了頁分裂。
爲什麼一定要設置一個主鍵
因爲就算你不主動設置一個主鍵,innodb也會幫你生成一個隱藏列,作爲自增主鍵來使用。所以,不管怎麼樣都會有自增主鍵,還不如自己指定一個,主鍵索引可以提高查詢效率。
主鍵使用自增還是UUID
主鍵肯定是用自增。innodb種的主鍵是聚族索引,如果主鍵是自增的,那麼每次插入的新數據都會順序添加到當前索引節點的後續位置,當一頁寫滿時,就會自動開啓第二頁,如果不是自增主鍵,那麼就可能在中間插入,就會引發頁的分裂,產生很多碎片,總之用自增主鍵性能更好。
UUID產生的索引文件更大,當數據量超過一百萬行時,主鍵查詢性能和索引文件大小都比UUID要更有效率和更小。
主鍵爲什麼不推薦有業務含義
1)因爲任何含有業務的列都有改變的可能性,主鍵一旦帶上業務含義,那麼主鍵就有可能發生改變。主鍵發生改變,該數據在磁盤上的存儲位置就會發生更改,有可能引發頁分裂,產生空間碎片。
2)帶有業務含義的主鍵不一定是順序自增的,就有可能導致後邊插入的數據主鍵一定比前面的大,如果出現後邊插入的數據主鍵比前面插入的小,就肯能引發頁分裂,產生空間碎片。
表示枚舉的字段爲什麼不用enum類型
首先,枚舉在MySQL中一般用tinyint類型,爲什麼不使用enum呢
1)ENUM類型,order by操作效率低,需要額外的操作
2)如果枚舉是數值,有陷阱
例如:表結構如下create table test(sex enum('0','1'))此時執行插入語句insert into test values(1) 查詢的結果爲0
只有這樣插入insert into test values('1')此時結果纔是1
貨幣字段用什麼類型
貨幣:如果貨幣單位是分,可以使用int類型,如果堅持用元的話,可以使用Decimal類型,不能使用float和double類型,因爲這兩個類型是以二進制存儲的,所有有一定的誤差。
例如:create table 't'('price' float(10,2) default null) engine=innodb default charset=utf8然後插入一條數據price=120.23,然後你會發現數據變爲120.25,精度失準。
時間字段用什麼類型
1)如果使用varchar,優點是顯示直觀,缺點是做時間比較運算的時候,需要使用str_to_date等函數把它轉化爲事件類型,你會發現這是無法命中索引的,數據量一大,這就是一個大坑。
2)如果使用timestamp類型,該類型爲4個字節,表示的時間範圍爲1970-01-01 到2038-01-19 也就是說2038年以後的時間不能使用timestamp存儲。該類型有一個優勢,就是自帶時區,如果修改了時區,該字段值會跟着改變。
3)dtatime類型,該類型爲8個字節,自己維護一個時間戳,表示範圍比timestamp大,因爲需要自己維護,所以不太方便。
爲什麼不直接存儲圖片、音頻、視頻等大容量內容
在實際生產中,我們都是在文件服務器上存真實文件,數據庫中保存對應的文件路勁即可,MySQL有存放大文件的類型,text和blob類型,但是基本不使用
1)MySQL內存臨時表不支持text、blob這樣的大數據類型,如果查詢中包含這樣的數據,在排序等操作時,就不能使用內存臨時表,必須使用磁盤臨時表,導致效率低下。
2)binlog內容太多,因爲數據內容過大,會導致binlog內容較多,而MySQL主從同步靠binlog進行同步,binlog太大就會導致主從同步效率問題。
字段爲什麼要定義爲NOT NULL
1)索引性能不好
2)查詢會出現一些不可預料的結果
我們都只是茫茫星辰中的一粒沙。
互聯網產品mysql數據庫設計注意事項
mysql數據庫性能不比oracle數據庫,所以設計上,和oracle有一些不同。下面總結一些互聯網產品的數據庫設計。
1.主鍵
主鍵可以使用bigint(20) unsigned也可以使用varchar,使用bigint,可以設置爲自增主鍵auto_increment。使用varchar,要生成主鍵。
2.gmt_create、gmt_modified
在TB所有表中都添加gmt_create、gmt_modified字段,都是datetime類型。gmt_create表示記錄創建時間,gmt_modified表示最近修改時間,如果記錄沒有修改,gmt_create和gmt_modified一致。
那麼,這兩個字段可以做什麼用呢?這兩個字段可以方便統計每天對某個表做了多少次的DML。另一種統計方式可以通過binlog。
查看昨天insert:
select * FROM test_table WHERE
gmt_create < date_format(DATE(now()), '%Y-%m-%e %H-%i-%s') AND
gmt_create >= date_format(DATE(date_add(now(), INTERVAL - 1 DAY)),'%Y-%m-%e %H-%i-%s');
查看昨天update:
select * FROM test_table WHERE gmt_create < date_format(DATE(now()), '%Y-%m-%e %H-%i-%s')
AND gmt_create >= date_format(DATE(date_add(now(), INTERVAL - 1 DAY)),'%Y-%m-%e %H-%i-%s')
AND date_add(gmt_create, INTERVAL - 1 DAY) != date_add(gmt_modified, INTERVAL - 1 DAY)
3.邏輯刪除 is_deleted
數據庫不做物理刪除,只做邏輯刪除,用is_deleted做邏輯刪除,如果刪除,則爲1,不刪除則爲0.is_deleted字段可以使用tinyint型。
4.禁止使用物理外鍵,使用邏輯外鍵
由於mysql性能不如oracle,外鍵還是一個比較消耗性能的東西,所以我們不要使用物理外鍵,就是我們在建表的時候,禁止使用foreign key。
例如表b記錄了表a的id,我們只需在表b中添加一列:aid就可以了,然後通過程序來控制外鍵關係。
5.命名規範
1.庫名,表名,字段名必須都用小寫字母,並用下劃線_分隔,並且見名知意,使用名詞。
原因如下:
a) MySQL有配置參數lower_case_table_names,不可動態更改,linux系統默認爲0,即庫表名以實際情況存儲,大小寫敏感。如果是1,以小寫存儲,大小寫不敏感。如果是2,以實際情況存儲,但以小寫比較。
b) 如果大小寫混合用,可能存在abc,Abc,ABC等多個表共存,容易導致混亂。
c) 字段名顯示區分大小寫,但實際使用不區分,即不可以建立兩個名字一樣但大小寫不一樣的字段。
d) 爲了統一規範, 庫名、表名、字段名使用小寫字母。
6.使用innoDB存儲引擎
5.5以後的默認引擘,支持事務,行級鎖,更好的恢復性,高併發下性能更好,對多核,大內存,ssd等硬件支持更好。
7.關於數據類型
a.存儲精確浮點數必須使用DECIMAL替代FLOAT和DOUBLE。
例如:對於金錢的存儲,使用decimal,或者以分爲單位,用bigint(20) unsigned。
b.使用UNSIGNED存儲非負數值。同樣的字節數,存儲的數值範圍更大。如tinyint 有符號爲 -128-127,無符號爲0-255
c.建議使用INT UNSIGNED存儲IPV4。
使用INT UNSIGNED而不是char(15)來存儲ipv4地址,通過MySQL函數inet_ntoa和inet_aton來進行轉化。Ipv6地址目前沒有轉化函數,需要使用DECIMAL或者兩個bigINT來存儲。例如:
SELECT INET_ATON('209.207.224.40');
3520061480
SELECT INET_NTOA(3520061480);
209.207.224.40
d.整形定義中不添加(4),比如使用INT,而不是INT(4)
注意數值類型括號後面的數字只是表示寬度而跟存儲範圍沒有關係,比如INT(3)默認顯示3位,空格補齊,超出時正常顯示,python、java客戶端等不具備這個功能。
e.使用短數據類型,比如取值範圍爲0-80時,使用TINYINT UNSIGNED。
f.不建議使用ENUM類型,使用TINYINT來代替。
ENUM,有三個問題:添加新的值要做DDL,默認值問題(將一個非法值插入ENUM(也就是說,允許的值列之外的字符串),將插入空字符串以作爲特殊錯誤值),索引值問題(插入數字實際是插入索引對應的值)
實例:
drop table if exists t;
create table t(sex enum('0','1'));
insert into t values(1);
insert into t values('3');
select * from t;
+------+
| sex |
+------+
| 0 |
| |
+------+
2 rows in set (0.00 sec)
g.儘可能不使用TEXT、BLOB類型。
h.VARCHAR(N),N表示存的個數,比如:VARCHAR(10) 下面語句都可以成功:
update test set testvarchar='一二三四五六七八九十' where id=1
update test set testvarchar='1234567890' where id=1
update test set testvarchar='abcdefghij' where id=1
i.存儲年使用YEAR類型。
j.存儲日期使用DATE類型。
k.存儲時間(精確到秒)建議使用TIMESTAMP類型,因爲TIMESTAMP使用4字節,DATETIME使用8個字節。
l.將過大字段拆分到其他表中。
m.禁止在數據庫中使用VARBINARY、BLOB存儲圖片、文件等
8.適當建立索引
非唯一索引必須按照“idx_字段名稱_字段名稱[_字段名]”進行命名。
唯一索引必須按照“uniq_字段名稱_字段名稱[_字段名]”進行命名。
索引名稱必須使用小寫。
索引中的字段數建議不超過5個。
單張表的索引數量控制在5個以內。
不建議使用%前綴模糊查詢,例如LIKE “%weibo”。
合理創建聯合索引(避免冗餘),(a,b,c) 相當於 (a) 、(a,b) 、(a,b,c)。
唯一鍵由3個以下字段組成,並且字段都是整形時,使用唯一鍵作爲主鍵。
沒有唯一鍵或者唯一鍵不符合5中的條件時,使用自增id作爲主鍵。
唯一鍵不和主鍵重複。
索引字段的順序需要考慮字段值去重之後的個數,個數多的放在前面。
ORDER BY,GROUP BY,DISTINCT的字段需要添加在索引的後面。