MySQL中建表時可空(NULL)和非空(NOT NULL)的一些思考

来源:https://www.cnblogs.com/wy123/archive/2020/06/20/13167894.html
-Advertisement-
Play Games

對於MySQL的一些個規範,某些公司建表規範中有一項要求是所有欄位非空,意味著沒有值的時候存儲一個預設值。其實所有欄位非空這麼說應該是絕對了,應該說是儘可能非空,某些情況下不可能給出一個預設值。那麼這條要求,是基於哪些考慮因素,存儲空間?相關增刪查改操作的性能?亦或是其他考慮?該理論到底有沒有道理或 ...


對於MySQL的一些個規範,某些公司建表規範中有一項要求是所有欄位非空,意味著沒有值的時候存儲一個預設值。其實所有欄位非空這麼說應該是絕對了,應該說是儘可能非空,某些情況下不可能給出一個預設值。
那麼這條要求,是基於哪些考慮因素,存儲空間?相關增刪查改操作的性能?亦或是其他考慮?該理論到底有沒有道理或者可行性,本文就個人的理解,做一個粗淺的分析。

 

1,基於存儲的考慮

這裡對存儲的分析要清楚MySQL數據行的存儲格式,這裡直接從這篇文章白嫖一部分結論,文章里分析的非常清楚(其實也是參考《MySQL技術內容Innodb存儲引擎》)。
對於預設的Dynamic或者Compact格式的數據行結構,其行結構格式如下:
|變長欄位長度列表(1~2位元組)|NULL標誌位(1位元組)|記錄頭信息(5位元組)|RowID(6位元組)|事務ID(6位元組)|回滾指針(7位元組)|row content

1,對於變長欄位,當相關的欄位值為NULL時,相關欄位不會占用存儲空間。NULL值沒有存儲,不占空間,但是需要一個標誌位(一行一個)。
2,對於變長欄位,相關欄位要求NOT NULL,存儲成''的時候,也不占用空間,如果一個表中所有的字典都NOT NULL,行頭不需要NULL的標誌位
3,所有欄位都是定長,不管是否要求為NOT NULL,都不需要標誌位,同時不需要存儲變長列長度

鑒於null值和非空(not null  default '')兩種情況,如果一個欄位存儲的內容是空,也就是什麼都沒有,前者存儲為null,後者存儲為空字元串'',兩者欄位內容本身存儲空間大小是一樣的。
但是如果一個表中存儲在可空欄位的情況下,其對應的數據行的頭部,都需要一個1位元組的NULL標誌位,這個就決定了存儲同樣的數據,如果允許為null,相比not null的情況下,每行多了一個位元組的存儲空間的。
這個因素或者就是某些公司或者個人堅持“所有表禁止null欄位”這個信仰的原因之一(個人持否定態度,可以嘗試將資料庫中所有的欄位都至為not null 然後default一個值後會不會雞飛狗跳)。
這裡不再去做“微觀”的分析,直接從“巨集觀”的角度來看一下差異。

測試demo
直接創建結構一致,但是一個表欄位not null,一個表欄位為null,然後使用存儲此過程,兩張表同時按照null值與非null值1:10的比例寫入數據,也就是說每10行數據中1行數據欄位為null的方式寫入600W行數據。

CREATE TABLE a
(
    id INT AUTO_INCREMENT,
    c2 VARCHAR(50) NOT NULL DEFAULT '',
    c3 VARCHAR(50) NOT NULL DEFAULT '',
    PRIMARY KEY (id)
);

CREATE TABLE b
(
    id INT AUTO_INCREMENT,
    c2 VARCHAR(50),
    c3 VARCHAR(50),
    PRIMARY KEY (id)
);


CREATE DEFINER=`root`@`%` PROCEDURE `create_test_data`(
    IN `loop_cnt` INT
)
LANGUAGE SQL
NOT DETERMINISTIC
CONTAINS SQL
SQL SECURITY DEFINER
COMMENT ''
BEGIN
    DECLARE v2 , v3 VARCHAR(36);
    
    START TRANSACTION;
    
        while loop_cnt>0 do
            SET v2 = UUID();
            SET v3 = UUID();

            
            if (loop_cnt MOD 10) = 0 then
                INSERT INTO a (c2,c3) VALUES(DEFAULT,DEFAULT);
                INSERT INTO b (c2,c3) VALUES(DEFAULT,DEFAULT);
            else
                INSERT INTO a (c2,c3) VALUES (v2,v3);
                INSERT INTO b (c2,c3) VALUES (v2,v3);
            END if ;
            
            SET loop_cnt=loop_cnt-1;
        END while;
    COMMIT;
    
View Code

a,b兩張表生產完全一致的數據。

查看占用的存儲空間情況,從information_schema.TABLES中查詢這兩個表的存儲信息

1,一個位元組的差別,體現在avg_row_length,a表因為所有的欄位都是not null,因此相比b表,每行節省了每行節省了一個位元組的存儲
2,總得空間的差別:a表662683648/1024/1024=631.98437500MB,b表666877952/1024/1024=635.98437500MB,
  也當前情況下,600W行數據有4MB的差異,差異在1%之內,其實實際情況下,欄位多,table size更大的的時候,這個差異會遠遠小於1%。

就存儲空間來說,你跟我說1T的資料庫你在乎1GB的存儲空間,隨便一點數據/索引碎片空間,一點預留空間,垃圾文件空間,無用索引空間……,都遠遠大於可為空帶來的額外這一點差異。

 

2,增刪查改的效率

讀寫操作對比,通過連續讀寫一個範圍之內的數據,來對比a,b兩張表在讀上面的情況。
  2.1.)首先buffer pool是遠大於table size的,因此不用擔心物理IO引起的差異,目前兩張表的數據完全都存在與buffer pool中。
  2.1.)讀測試操作放在MySQL實例機器上,因此網路不穩定引起的差異可以忽略。

增刪查改的差異與存儲空間的差異類似,甚至更小,因為單行相差1個位元組,放大到600W+才能看到一個5MB級別的差異,增刪查改的話,各種測試下來,沒有發現有明顯的差異

#!/usr/bin/env python3
import pymysql
import time
mysql_conn_conf = {'host': '127.0.0.1', 'port': 3306, 'user': 'root', 'password': '******', 'db': 'db01'}


def mysql_read(table_name):
    conn = pymysql.connect(host=mysql_conn_conf['host'], port=mysql_conn_conf['port'], database=mysql_conn_conf['db'],user=mysql_conn_conf['user'],password = mysql_conn_conf['password'])
    cursor = conn.cursor()
    try:
        cursor.execute(''' select id,c2,c3 from {0} where id>3888888 and id<3889999;'''.format(table_name))
        row = cursor.fetchall()
    except pymysql.Error as e:
        print("mysql execute error:", e)
    cursor.close()
    conn.close()


def mysql_write(loop,table_name):
    conn = pymysql.connect(host=mysql_conn_conf['host'], port=mysql_conn_conf['port'], database=mysql_conn_conf['db'],user=mysql_conn_conf['user'],password = mysql_conn_conf['password'])
    cursor = conn.cursor()
    try:
        if loop%10 == 0:
            cursor.execute(''' insert into {0}} (c2,c3) values(DEFAULT,DEFAULT)'''.format(table_name))
        else:
            cursor.execute(''' insert into {1}} (c2,c3) values(uuid(),uuid())'''.format(table_name))
    except pymysql.Error as e:
        print("mysql execute error:", e)
    cursor.close()
    conn.commit()
    conn.close()


if __name__ == '__main__':
    time_start = time.time()
    loop=10
    while loop>0:
        mysql_write(loop)
        loop = loop-1

    time_end = time.time()
    time_c= time_end - time_start
    print('time cost', time_c, 's')
View Code

 

3,相關欄位上的語義解析和邏輯考慮

這一點就觀點差異就太多了,也是最容易引起口水或者爭議的了。

1,對於字元類型,NULL就是不存在,‘’就是空,不存在和空本身就不是一回事,不太認同一定要NOT NULL,然後給出預設值。
2,對於字元類型,任何資料庫中,NULL都是不等於NULL的,因為在處理相關欄位上進行join或者where篩選的時候,是不需要考慮連接雙方都為NULL的情況的,一旦用''替代了NULL,''是等於''的,此時就會出現與存儲NULL完全不用的語義
3,對於字元類型,一旦將相關欄位default成'',如何區分''與空字元串,比如備註欄位,不允許為NULL,default成‘’,那麼怎麼區分,NULL表達的空和預設值的空字元串''
4,對於相關的查詢操作,如果允許為NULL,篩選非NULL值就是where *** is not null,語義上很清晰直觀,一旦用欄位非空,預設成'',會使用where *** <>''這種看起來超級噁心的寫法,究竟要表達什麼,語義上就已經開始模糊了
5,對於時間類型,絕大多數時候是不允許有預設值的,預設多少合適,當前時間合適麽,千禧年2000合適麽,2008年北京奧運會開幕時間合適麽?
6,對於數值類型,比如int,比如decimal,在可空的情況下,如果禁止為NULL,預設給多少合適,0合適嗎?-1合適嗎?-9999999……合適嗎?10086合適嗎?1024合適嗎?說實話,預設多少都不合適,NULL自身就是最合適的。

個人觀點很明確,除非有特殊的需求要求一個欄位絕對不能出現NULL值的情況,正常情況下,該NULL就NULL。
如果NULL沒有存在的意義,乾脆資料庫就不要存在這個NULL就好了,事實上,哪個資料庫沒有NULL類型?
當然也不排除,某些DBA為了顯得自己專業,弄出來一些莫須有的東西,現在就是有一種風氣,在資料庫上能提出來的限制條件越多,越有優越感。

個人認識有限,數據實話,非常想知道“所有欄位非空”會帶來什麼其他哪些正面的影響,以及如何衡量這個正面的因素,還有,你們真的做到了,可以禁止整個實例下所有的庫表中的欄位禁止可空(nullable)?

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一 Kubetcl namespace 1.1 namespace描述 Kubernetes namespace提供了將一組相關資源組合在一起的機制。在Red Hat OpenShift容器平臺中,project是一個帶有附加註釋的Kubernetes namespace。 namespace提供以 ...
  • 前言 使用Nginx搭建一個私人網盤 安裝Nginx 增加 Nginx 官方源 cat << EOF > /etc/yum.repos.d/nginx.repo [nginx-stable] name=nginx stable repo baseurl=http://nginx.org/packag ...
  • 我們在 Linux 下進行開發時,有時也需要知道當前的硬體信息,比如:CPU幾核?使用情況?記憶體大小及使用情況?USB設備是否被識別?等等類似此類問題。下麵良許介紹一些常用的硬體查看命令。 lshw lshw 這個命令是一個比較通用的工具,它可以詳細的列出本機的硬體信息。但這個命令並非所有的發行版都 ...
  • 基本許可權管理 許可權的介紹 許可權位的含義 前面講解ls命令時,我們已經知道長格式顯示的第一列就是文件的許可權,例如: [root@es ~]# ls -l anaconda-ks.cfg -rw . 1 root root 1573 May 18 23:28 anaconda-ks.cfg 第一位為文件 ...
  • 1. 測試環境搭建步驟 為什麼要安裝tomcat、mysql? —所測試的項目需求環境決定。tomcat-項目容器(放置開發打包的項目代碼),mysql-數據倉庫。 為什麼要安裝jdk? —java軟體開發包(Java Development Kit),沒有JDK的話,無法編譯運行Java程式。 1 ...
  • 一 CLI訪問OpenShift資源 1.1 資源操作 OCP將OpenShift集群中的為由主節點管理的對象統稱為資源,如:node、service、pod、project、deployment、user。 即使針對的是不同的資源,OpenShift命令行工具也提供了一種統一的、一致的方法來更新、 ...
  • ffmpeg -i test.mp4 -codec copy -bsf: h264_mp4toannexb -f h264 test.264 從MP4文件內提取視頻流,忽略音頻流,指定幀頻、碼率 ffmpeg -i test.mp4 -vcodec h264 -an -r 25 -b:v 256k ...
  • 初用MySQL Mysql示例庫 Navicat15 查看初始密碼 MySQl首次啟動會創建“超級管理員賬號”root@localhost,初始密碼存儲在日誌文件中,通過grep搜索並查看: grep 'temporary password' /var/log/mysqld.log 進入mysql ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...