0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

數(shù)據(jù)庫字段要使用NOT NULL究竟是為何?

數(shù)據(jù)分析與開發(fā) ? 來源:博客園 ? 作者:艾小仙 ? 2021-04-19 15:24 ? 次閱讀

最近剛?cè)肼毿?a target="_blank">公司,發(fā)現(xiàn)數(shù)據(jù)庫設(shè)計有點小問題,數(shù)據(jù)庫字段很多沒有NOT NULL,對于強迫癥晚期患者來說,簡直難以忍受,因此有了這篇文章。

基于目前大部分的開發(fā)現(xiàn)狀來說,我們都會把字段全部設(shè)置成NOT NULL并且給默認值的形式。

通常,對于默認值一般這樣設(shè)置:

整形,我們一般使用0作為默認值。

字符串,默認空字符串

時間,可以默認1970-01-01 0801,或者默認0000-00-00 0000,但是連接參數(shù)要添加zeroDateTimeBehavior=convertToNull,建議的話還是不要用這種默認的時間格式比較好

但是,考慮下原因,為什么要設(shè)置成NOT NULL?

來自高性能Mysql中有這樣一段話:

盡量避免NULL

很多表都包含可為NULL(空值)的列,即使應(yīng)用程序并不需要保存NULL也是如此,這是因為可為NULL是列的默認屬性。通常情況下最好指定列為NOT NULL,除非真的需要存儲NULL值。

如果查詢中包含可為NULL的列,對MySql來說更難優(yōu)化,因為可為NULL的列使得索引、索引統(tǒng)計和值比較都更復雜??蔀镹ULL的列會使用更多的存儲空間,在MySql里也需要特殊處理。當可為NULL的列被索引時,每個索引記錄需要一個額外的字節(jié),在MyISAM里甚至還可能導致固定大小的索引(例如只有一個整數(shù)列的索引)變成可變大小的索引。

通常把可為NULL的列改為NOT NULL帶來的性能提升比較小,所以(調(diào)優(yōu)時)沒有必要首先在現(xiàn)有schema中查找并修改掉這種情況,除非確定這會導致問題。但是,如果計劃在列上建索引,就應(yīng)該盡量避免設(shè)計成可為NULL的列。

當然也有例外,例如值得一提的是,InnoDB使用單獨的位(bit)存儲NULL值,所以對于稀疏數(shù)據(jù)有很好的空間效率。但這一點不適用于MyISAM。

書中的描述說了幾個主要問題,我這里暫且拋開MyISAM的問題不談,這里我針對InnoDB作為考量條件。

如果不設(shè)置NOT NULL的話,NULL是列的默認值,如果不是本身需要的話,盡量就不要使用NULL

使用NULL帶來更多的問題,比如索引、索引統(tǒng)計、值計算更加復雜,如果使用索引,就要避免列設(shè)置成NULL

如果是索引列,會帶來的存儲空間的問題,需要額外的特殊處理,還會導致更多的存儲空間占用

對于稀疏數(shù)據(jù)有更好的空間效率,稀疏數(shù)據(jù)指的是很多值為NULL,只有少數(shù)行的列有非NULL值的情況

默認值

對于MySql而言,如果不主動設(shè)置為NOT NULL的話,那么插入數(shù)據(jù)的時候默認值就是NULL。

NULL和NOT NULL使用的空值代表的含義是不一樣,NULL可以認為這一列的值是未知的,空值則可以認為我們知道這個值,只不過他是空的而已。

舉個例子,一張表中的某一條name字段是NULL,我們可以認為不知道名字是什么,反之如果是空字符串則可以認為我們知道沒有名字,他就是一個空值。

而對于大多數(shù)程序的情況而言,沒有什么特殊需要非要字段要NULL的吧,NULL值反而會對程序造成比如空指針的問題。

對于現(xiàn)狀大部分使用MyBatis的情況來說,我建議使用默認生成的insertSelective方法或者純手動寫插入方法,可以避免新增NOT NULL字段導致的默認值不生效或者插入報錯的問題。

值計算

聚合函數(shù)不準確

對于NULL值的列,使用聚合函數(shù)的時候會忽略NULL值。

現(xiàn)在我們有一張表,name字段默認是NULL,此時對name進行count得出的結(jié)果是1,這個是錯誤的。

count(*)是對表中的行數(shù)進行統(tǒng)計,count(name)則是對表中非NULL的列進行統(tǒng)計。

5e7ffcca-9f5a-11eb-8b86-12bb97331649.jpg

=失效

對于NULL值的列,是不能使用=表達式進行判斷的,下面對name的查詢是不成立的,必須使用is NULL。

5e908a04-9f5a-11eb-8b86-12bb97331649.jpg

與其他值運算

NULL和其他任何值進行運算都是NULL,包括表達式的值也是NULL。

user表第二條記錄age是NULL,所以+1之后還是NULL,name是NULL,進行concat運算之后結(jié)果還是NULL。

5e98c872-9f5a-11eb-8b86-12bb97331649.jpg

可以再看下下面的例子,任何和NULL進行運算的話得出的結(jié)果都會是NULL,想象下你設(shè)計的某個字段如果是NULL還不小心進行各種運算,最后得出的結(jié)果。。。

5ea43040-9f5a-11eb-8b86-12bb97331649.jpg

distinct、group by、order by

對于distinct和group by來說,所有的NULL值都會被視為相等,對于order by來說升序NULL會排在最前

5eca059a-9f5a-11eb-8b86-12bb97331649.jpg

其他問題

表中只有一條有名字的記錄,此時查詢名字!=a預(yù)期的結(jié)果應(yīng)該是想查出來剩余的兩條記錄,會發(fā)現(xiàn)與預(yù)期結(jié)果不匹配。

5ed4b576-9f5a-11eb-8b86-12bb97331649.jpg

索引問題

為了驗證NULL字段對索引的影響,分別對name和age添加索引。

5eea93f0-9f5a-11eb-8b86-12bb97331649.jpg

關(guān)于網(wǎng)上很多說如果NULL那么不能使用索引的說法,這個描述其實并不準確,根據(jù)引用官方文檔[3]里描述,使用is NULL和范圍查詢都是可以和正常一樣使用索引的,實際驗證的結(jié)果好像也是這樣,看以下例子。

5ef74ffa-9f5a-11eb-8b86-12bb97331649.jpg

然后接著我們往數(shù)據(jù)庫中繼續(xù)插入一些數(shù)據(jù)進行測試,當NULL列值變多之后發(fā)現(xiàn)索引失效了。

5f027aec-9f5a-11eb-8b86-12bb97331649.jpg

我們知道,一個查詢SQL執(zhí)行大概是這樣的流程:

5f0caefe-9f5a-11eb-8b86-12bb97331649.jpg

首先連接器負責連接到指定的數(shù)據(jù)庫上,接著看看查詢緩存中是否有這條語句,如果有就直接返回結(jié)果。

如果緩存沒有命中的話,就需要分析器來對SQL語句進行語法和詞法分析,判斷SQL語句是否合法。

現(xiàn)在來到優(yōu)化器,就會選擇使用什么索引比較合理,SQL語句具體怎么執(zhí)行的方案就確定下來了。

最后執(zhí)行器負責執(zhí)行語句、有無權(quán)限進行查詢,返回執(zhí)行結(jié)果。

從上面的簡單測試結(jié)果其實可以看到,索引列存在NULL就會存在書中所說的導致優(yōu)化器在做索引選擇的時候更復雜,更加難以優(yōu)化。

存儲空間

數(shù)據(jù)庫中的一行記錄在最終磁盤文件中也是以行的方式來存儲的,對于InnoDB來說,有4種行存儲格式:REDUNDANT、COMPACT、DYNAMIC和COMPRESSED。

InnoDB的默認行存儲格式是COMPACT,存儲格式如下所示,虛線部分代表可能不一定會存在。

5f14e5e2-9f5a-11eb-8b86-12bb97331649.jpg

變長字段長度列表:有多個字段則以逆序存儲,我們只有一個字段所有不考慮那么多,存儲格式是16進制,如果沒有變長字段就不需要這一部分了。

NULL值列表:用來存儲我們記錄中值為NULL的情況,如果存在多個NULL值那么也是逆序存儲,并且必須是8bit的整數(shù)倍,如果不夠8bit,則高位補0。1代表是NULL,0代表不是NULL。如果都是NOT NULL那么這個就存在了。

ROW_ID:一行記錄的唯一標志,沒有指定主鍵的時候自動生成的ROW_ID作為主鍵。

TRX_ID:事務(wù)ID。

ROLL_PRT:回滾指針。

最后就是每列的值。

為了說明清楚這個存儲格式的問題,我弄張表來測試,這張表只有c1字段是NOT NULL,其他都是可以為NULL的。

5f25503a-9f5a-11eb-8b86-12bb97331649.jpg

可變字段長度列表:c1和c3字段值長度分別為1和2,所以長度轉(zhuǎn)換為16進制是0x01 0x02,逆序之后就是0x02 0x01。

NULL值列表:因為存在允許為NULL的列,所以c2,c3,c4分別為010,逆序之后還是一樣,同時高位補0滿8位,結(jié)果是00000010。

其他字段我們暫時不管他,最后第一條記錄的結(jié)果就是,當然這里我們就不考慮編碼之后的結(jié)果了。

5f464196-9f5a-11eb-8b86-12bb97331649.jpg

這樣就是一個完整的數(shù)據(jù)行數(shù)據(jù)的格式,反之,如果我們把所有字段都設(shè)置為NOT NULL,并且插入一條數(shù)據(jù)a,bb,ccc,dddd的話,存儲格式應(yīng)該這樣:

5f55fdf2-9f5a-11eb-8b86-12bb97331649.jpg

雖然我們發(fā)現(xiàn)NULL本身并不會占用存儲空間,但是如果存在NULL的話就會多占用一個字節(jié)的標志位的空間。

文章參考文檔:

https://dev.mysql.com/doc/refman/8.0/en/problems-with-null.html

https://dev.mysql.com/doc/refman/8.0/en/working-with-null.html

https://dev.mysql.com/doc/refman/5.6/en/is-null-optimization.html

https://dev.mysql.com/doc/refman/5.6/en/innodb-row-format.html

https://www.cnblogs.com/zhoujinyi/articles/2726462.html

編輯:jq

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學習之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • SQL
    SQL
    +關(guān)注

    關(guān)注

    1

    文章

    753

    瀏覽量

    44036
  • 數(shù)據(jù)庫
    +關(guān)注

    關(guān)注

    7

    文章

    3752

    瀏覽量

    64236
  • 函數(shù)
    +關(guān)注

    關(guān)注

    3

    文章

    4285

    瀏覽量

    62333
  • null
    +關(guān)注

    關(guān)注

    0

    文章

    17

    瀏覽量

    3910

原文標題:為什么數(shù)據(jù)庫字段要使用NOT NULL?

文章出處:【微信號:DBDevs,微信公眾號:數(shù)據(jù)分析與開發(fā)】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    數(shù)據(jù)庫數(shù)據(jù)恢復—通過拼接數(shù)據(jù)庫碎片恢復SQLserver數(shù)據(jù)庫

    一個運行在存儲上的SQLServer數(shù)據(jù)庫,有1000多個文件,大小幾十TB。數(shù)據(jù)庫每10天生成一個NDF文件,每個NDF幾百GB大小。數(shù)據(jù)庫包含兩個LDF文件。 存儲損壞,數(shù)據(jù)庫
    的頭像 發(fā)表于 10-31 13:21 ?107次閱讀
    <b class='flag-5'>數(shù)據(jù)庫</b><b class='flag-5'>數(shù)據(jù)</b>恢復—通過拼接<b class='flag-5'>數(shù)據(jù)庫</b>碎片恢復SQLserver<b class='flag-5'>數(shù)據(jù)庫</b>

    PCM1861 INT腳究竟是輸出還是輸入?

    這個芯片activce或是idle. 是否有人解釋下,INT腳究竟是輸出還是輸入。我希望是輸出,我需要讀取到是否有analog audio輸入的信息。 或者,輸入輸出與否還要靠其他什么地方設(shè)置? 盼望有人回復解答,不勝感激!
    發(fā)表于 10-29 07:29

    揭秘貼片功率電感發(fā)燙究竟是不是燒壞了

    電子發(fā)燒友網(wǎng)站提供《揭秘貼片功率電感發(fā)燙究竟是不是燒壞了.docx》資料免費下載
    發(fā)表于 09-30 14:44 ?0次下載

    電感器線徑究竟是粗好還是細好

    電子發(fā)燒友網(wǎng)站提供《電感器線徑究竟是粗好還是細好.docx》資料免費下載
    發(fā)表于 09-20 11:25 ?0次下載

    tas5756m使用GPIO口加內(nèi)部PLL產(chǎn)生MCLK的方法究竟是怎么樣的?

    tas5756m使用GPIO口加內(nèi)部PLL產(chǎn)生MCLK的方法究竟是怎么樣的?
    發(fā)表于 08-19 06:06

    請問cH340G的TX引腳電平究竟是3v還是5v?

    用CD34G來實現(xiàn)usb轉(zhuǎn)串口的時候,直接用usb口的5v作為電源電壓,它的tx引腳輸出的高電平究竟是5v還是3v,我實測是3v,但網(wǎng)上有的人是5v,想進一步得到大家的確認。
    發(fā)表于 05-14 08:15

    STM32擦除后數(shù)據(jù)究竟是0x00還是0xff ?

    STM32擦除后數(shù)據(jù)究竟是0x00還是0xff ,百度查了許多發(fā)現(xiàn)大多數(shù)都是0xff的多,都說SD卡(TF)儲存介質(zhì)是Flash 所以擦除后為0xff,但是我遇到了讀出來的數(shù)據(jù)是0x00的情況,為什么呢
    發(fā)表于 04-18 07:59

    同步電機的轉(zhuǎn)數(shù)同步究竟是與什么同步啊?

    同步電機的轉(zhuǎn)數(shù)同步究竟是與什么同步??? 所有的同步電機的轉(zhuǎn)數(shù)都一樣嗎?還是與電機的極對數(shù)有關(guān)系呢?
    發(fā)表于 12-19 06:44

    如何去掉oracle字符型數(shù)據(jù)里的null

    去掉Oracle字符型數(shù)據(jù)中的NULL值可以通過以下幾種方法實現(xiàn)。在介紹這些方法之前,首先需要了解Oracle數(shù)據(jù)庫中的NULL值是什么。 NULL
    的頭像 發(fā)表于 12-06 09:46 ?1166次閱讀

    NoSQL 數(shù)據(jù)庫如何選型

    什么是NoSQL數(shù)據(jù)庫?為什么要使用NoSQL數(shù)據(jù)庫?鍵值數(shù)據(jù)庫內(nèi)存鍵值數(shù)據(jù)庫文檔數(shù)據(jù)庫列式
    的頭像 發(fā)表于 11-26 08:05 ?428次閱讀
    NoSQL <b class='flag-5'>數(shù)據(jù)庫</b>如何選型

    給Oracle數(shù)據(jù)庫添加表字段的代碼

    Oracle數(shù)據(jù)庫是一款強大的關(guān)系型數(shù)據(jù)庫管理系統(tǒng),常被用于企業(yè)級的應(yīng)用開發(fā)。在實際應(yīng)用中,我們可能會遇到需要給數(shù)據(jù)庫的現(xiàn)有表添加新的字段的需求。本文將詳細介紹如何使用Oracle
    的頭像 發(fā)表于 11-21 11:38 ?786次閱讀

    oracle數(shù)據(jù)庫添加字段語句

    Oracle數(shù)據(jù)庫添加字段語句是在已有表中添加新的列,以擴展表的結(jié)構(gòu)和功能。添加字段語句的語法有多種,可以根據(jù)實際需求選擇不同的語法。下面將詳細介紹Oracle數(shù)據(jù)庫添加
    的頭像 發(fā)表于 11-21 11:37 ?6470次閱讀

    oracle修改數(shù)據(jù)庫字段類型

    要修改Oracle數(shù)據(jù)庫表中的字段類型,您可以按照以下步驟進行操作: 1.備份數(shù)據(jù):在進行任何修改之前,建議先備份數(shù)據(jù)庫以防止數(shù)據(jù)丟失。使用
    的頭像 發(fā)表于 11-21 11:33 ?3146次閱讀

    數(shù)據(jù)庫clob類型的字段怎么取

    數(shù)據(jù)庫中的 CLOB(Character Large Object)類型字段存儲的是大量的字符數(shù)據(jù),如文本文件、XML 文件、圖像等。要獲取 CLOB 類型字段的值,可以使用以
    的頭像 發(fā)表于 11-21 10:47 ?2421次閱讀

    數(shù)據(jù)庫mysql基本增刪改查

    的基本增刪改查操作。 一、增加數(shù)據(jù)(INSERT) 在MySQL中,可以使用INSERT語句來向數(shù)據(jù)庫中添加數(shù)據(jù)。INSERT INTO語句的基本語法如下: INSERT INTO 表名 (
    的頭像 發(fā)表于 11-16 16:35 ?1440次閱讀