使用 MySQL 數據庫時,對于一個可以為空的字段,如果沒有值,應該保存 NULL 還是給一個默認值呢?多數時候我們不太注意,有時候不賦值,直接保存 NULL,有時候賦值一個業務指定的默認值。今天來聊一聊這個話題。
1.行數據存儲
MySQL 保存一行數據時,不僅僅會保存數據本身,還會保存數據相關的額外信息。InnoDB 存儲引擎支持四種行格式,MySQL 5.7 版本之后,默認使用 Dynamic 行格式。看一下官網給出的 4 種格式說明:
![]()
DYNAMIC 和 COMPRESSED 這兩種格式都是 COMPACT 的改進版,基本結構跟 COMPACT 類似,我們看一下 COMPACT 這種格式。如下圖:
我們創建一張表:
CREATE TABLE`t_user` (`id`bigint(20) NOTNULL AUTO_INCREMENT,`name`varchar(16) DEFAULTNULL,`email`varchar(32) DEFAULTNULL,`address`varchar(255) DEFAULTNULL,PRIMARY KEY (`id`)) ENGINE=InnoDBDEFAULTCHARSET=latin1;插入 2 行數據,
![]()
數據行保存格式如下圖:
![]()
變長字段寬度列表保存變長字段非空值長度。從上圖可以看到,變長字段寬度列表存放的列寬度順序和數據表中的列順序相反,也就是說變長字段寬度列表逆序存放列寬度。
![]()
如果表中所有列都是NOT NULL并且具有固定長度,則沒有變長字段寬度列表這個部分
同樣,NULL 值列表也是逆序保存,當該值是 NULL 時,用二進制 1 表記,否則就保存二進制 0。
![]()
如果表中所有列都是NOT NULL,就沒有 NULL值列表這個部分。
記錄頭信息用 5 個字節保存,主要記錄數據的一些信息,比如:
delete-flag:記錄是否刪除,我們知道,在 MySQL 中刪除一條數據,并不會馬上從磁盤上刪除,而是打上刪除標記,在空余時間再進行異步清理。
record_type:記錄類型,比如普通記錄、非葉子節點記錄。
next_record:指向下一條記錄的地址指針。
n_owned:記錄該組數據的條數。
隱藏列
DB_TRX_ID:修改(插入、更新或刪除)這一條數據的事務 id;
DB_ROLL_PTR:回滾指針,指向修改前的歷史版本,用于回滾操作;
DB_ROW_ID:當表中不定義主鍵時用作主鍵來自動生成聚簇索引。
根據上面的分析和實際使用,如果我們把一個字段直接定義成 NOT NULL,有下面好處:
節省存儲空間:NULL 值雖然不會占用數據存儲空間,但是需要額外 1~2 個字節保存 NULL 值列表。
減少應用程序 NullPointerException 的可能性;
減少統計問題:比如 count(字段)不會統計 NULL 值。
對索引有好處,索引是不會保存 NULL 值的,定義成 NULL 會使索引效率下降。
比較操作:字段定義成 NULL,只能使用 is null 和 is not null 進行判斷,不能使用比較操作比如 =、!=、>、<(都會返回 null) 。
范圍操作:字段定義成 NULL,使用 in、not in 語句時會返回空結果。
當然,設置為 NULL,并不是沒有好處,比如:
語義清晰?:NULL 表示“無值”或“未知”,這在邏輯上更清晰準確;
靈活性?:NULL 值更容易篩選,比如在 WHERE 子句中使用 is null 進行篩選;
兼容性?:類似 JOIN 操作,NULL 跟任何值比較都會返回 NULL,這有助于保持數據的一致性和完整性。
在實際項目開發中,我們經常會在值是 NULL 的情況下給一個默認值,比如”-“、”“、”N/A“等,這一定程度上避免了空指針,但是往往帶來一些額外的問題,比如上下游系統因為默認值的不一致導致業務處理受影響。
在表設計時,我們其實沒有必要過多地考慮定義成 NULL 或默認值在存儲空間上的影響,更多的應該考慮系統整體設計規范、保證各子系統在設計上的一致性,這樣才能讓處理邏輯更加健壯。
特別聲明:以上內容(如有圖片或視頻亦包括在內)為自媒體平臺“網易號”用戶上傳并發布,本平臺僅提供信息存儲服務。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.