面試官:mysql表設計要注意什麼?

原文鏈接:https://mp.weixin.qq.com/s?__biz=MzIwMDgzMjc3NA==&mid=2247484617&idx=1&sn=afddfe5c3d66a9fcaba65516a0ab5d18&chksm=96f666a0a181efb60f2109714cf34f4bc68e424ed364d15375284b23f81259e8181c60f7ad10&scene=21#wechat_redirect

面試官:mysql表設計要注意什麼?

OK,具體有下面這些問題

  • 1、爲什麼一定要設一個主鍵?

  • 2、你們主鍵是用自增還是UUID?

  • 3、主鍵爲什麼不推薦有業務含義?

  • 4、表示枚舉的字段爲什麼不用enum類型?

  • 5、貨幣字段用什麼類型?

  • 6、時間字段用什麼類型?

  • 7、爲什麼不直接存儲圖片、音頻、視頻等大容量內容?

  • 8、字段爲什麼要定義爲NOT NULL?

其實上面這些問題,我最早想法是,每個問題都可以囉嗦出一篇文章。後來由於良心發現,煙哥就決定用一篇文章將這些問題都講明白。
當然,我給的回答可能並非標準答案,畢竟是自己的一些工作總結。各位讀者有更好的回答,也歡迎交流!

這裏我要說一下,我用mysql只用過innodb存儲引擎,其他的引擎真沒用過。因此我的回答,都是基於innodb存儲引擎中的。

正文

問題1:爲什麼一定要設一個主鍵?
回答:因爲你不設主鍵的情況下,innodb也會幫你生成一個隱藏列,作爲自增主鍵。所以啦,反正都要生成一個主鍵,那你還不如自己指定一個主鍵,在有些情況下,就能顯式的用上主鍵索引,提高查詢效率!

問題2:主鍵是用自增還是UUID?
回答:肯定答自增啊。innodb 中的主鍵是聚簇索引。如果主鍵是自增的,那麼每次插入新的記錄,記錄就會順序添加到當前索引節點的後續位置,當一頁寫滿,就會自動開闢一個新的頁。如果不是自增主鍵,那麼可能會在中間插入,就會引發頁的分裂,產生很多表碎片!。
上面那句話看不懂沒事,大白話一句就是:用自增插入性能好!


另外,附一個測試表給你們,表名帶uuid的就是用uuid作爲主鍵。大家看一下就知道性能差距了:


如上圖所示,當主鍵是UUID的時候,插入時間更長,而且佔用空間更大!

額,大家千萬不要忘了,當你回答自增主鍵後,想一下《自增主鍵用完該怎麼辦?

ps:這個問題,你要是能把UUID講出合理的理由也行。

問題3:主鍵爲什麼不推薦有業務含義?
回答:有如下兩個原因

  • (1)因爲任何有業務含義的列都有改變的可能性,主鍵一旦帶上了業務含義,那麼主鍵就有可能發生變更。主鍵一旦發生變更,該數據在磁盤上的存儲位置就會發生變更,有可能會引發頁分裂,產生空間碎片。

  • (2)帶有業務含義的主鍵,不一定是順序自增的。那麼就會導致數據的插入順序,並不能保證後面插入數據的主鍵一定比前面的數據大。如果出現了,後面插入數據的主鍵比前面的小,就有可能引發頁分裂,產生空間碎片。

問題4:表示枚舉的字段爲什麼不用enum類型?
回答:在工作中表示枚舉的字段,一般用tinyint類型。
那爲什麼不用enum類型呢?下面兩個原因
(1)ENUM類型的ORDER BY操作效率低,需要額外操作
(2)如果枚舉值是數值,有陷阱
舉個例子,表結構如下

CREATE TABLE test (foobar ENUM('0', '1', '2'));

此時,你執行語句

mysql> INSERT INTO test VALUES (1);

查詢出的結果爲

foobar
0

就產生了一個坑爹的結果。
插入語句應該像下面這麼寫,插入的纔是1

mysql> INSERT INTO test VALUES (`1`);

問題5:貨幣字段用什麼類型?
回答:如果貨幣單位是分,可以用Int類型。如果堅持用元,用Decimal
千萬不要答floatdouble,因爲float和double是以二進制存儲的,所以有一定的誤差。
打個比方,你建一個列如下

CREATE TABLE `t` (
  `price` float(10,2) DEFAULT NULL,
) ENGINE=InnoDB DEFAULT CHARSET=utf8

然後insert給price列一個數據爲1234567.23,你會發現顯示出來的數據變爲1234567.25,精度失準!

問題6:時間字段用什麼類型?
回答:此題無固定答案,應結合自己項目背景來答!把理由講清楚就行!
(1)varchar,如果用varchar類型來存時間,優點在於顯示直觀。但是坑的地方也是挺多的。比如,插入的數據沒有校驗,你可能某天就發現一條數據爲2013111的數據,請問這是代表2013年1月11日,還是2013年11月1日?
其次,做時間比較運算,你需要用STR_TO_DATE等函數將其轉化爲時間類型,你會發現這麼寫是無法命中索引的。數據量一大,是個坑!

(2)timestamp,該類型是四個字節的整數,它能表示的時間範圍爲1970-01-01 08:00:01到2038-01-19 11:14:07。2038年以後的時間,是無法用timestamp類型存儲的。
但是它有一個優勢,timestamp類型是帶有時區信息的。一旦你係統中的時區發生改變,例如你修改了時區

SET TIME_ZONE = "america/new_york";

你會發現,項目中的該字段的值自己會發生變更。這個特性用來做一些國際化大項目,跨時區的應用時,特別注意!

(3)datetime,datetime儲存佔用8個字節,它存儲的時間範圍爲1000-01-01 00:00:00 ~ 9999-12-31 23:59:59。顯然,存儲時間範圍更大。但是它坑的地方在於,他存儲的是時間絕對值,不帶有時區信息。如果你改變數據庫的時區,該項的值不會自己發生變更!

(4)bigint,也是8個字節,自己維護一個時間戳,表示範圍比timestamp大多了,就是要自己維護,不大方便。

問題7:爲什麼不直接存儲圖片、音頻、視頻等大容量內容?
回答:我們在實際應用中,都是用HDFS來存儲文件。然後mysql中,只存文件的存放路徑。mysql中有兩個字段類型被用來設計存放大容量文件,也就是textblob類型。但是,我們在生產中,基本不用這兩個類型直接存儲圖片、音頻、視頻等大容量內容!
主要原因有如下兩點

  • (1)Mysql內存臨時表不支持TEXT、BLOB這樣的大數據類型,如果查詢中包含這樣的數據,在排序等操作時,就不能使用內存臨時表,必須使用磁盤臨時表進行。導致查詢效率緩慢

  • (2)binlog內容太多。因爲你數據內容比較大,就會造成binlog內容比較多。大家也知道,主從同步是靠binlog進行同步,binlog太大了,就會導致主從同步效率問題!

因此,不推薦使用textblob類型直接存儲圖片、音頻、視頻等大容量內容!

問題8:字段爲什麼要定義爲NOT NULL?
回答:OK,這問題從兩個角度來答
(1)索引性能不好

Mysql難以優化引用可空列查詢,它會使索引、索引統計和值更加複雜。可空列需要更多的存儲空間,還需要mysql內部進行特殊處理。可空列被索引後,每條記錄都需要一個額外的字節,還能導致MYisam 中固定大小的索引變成可變大小的索引。                                                                                 —— 出自《高性能mysql第二版》

(2)查詢會出現一些不可預料的結果
這裏舉一個例子,大家就懂了。假設,表結構如下

create table table_2 (
     `id` INT (11) NOT NULL,
    name varchar(20) NOT NULL
)

表數據是這樣的

id name
1 孤獨煙
3 null
5 肥朝
7 null

你執行語句

select count(name) from table_2;

你會發現結果爲2,但是實際上是有四條數據的!類似的查詢問題,其實有很多,不一一列舉。
記住,因爲null列的存在,會出現很多出人意料的結果,從而浪費開發時間去排查Bug.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章