1. 
          

          1. 新聞動(dòng)態(tài)

            MySQL

            網(wǎng)站優(yōu)化 發(fā)布者:ou3377 2021-12-09 09:17 訪(fǎng)問(wèn)量:136

            如果你使用過(guò)mysql數據庫,對它的存儲引擎:innodb,一定不會(huì )感到陌生。

            眾所周知,在mysql8以前,默認的存儲引擎是:myslam。但mysql8之后,默認的存儲引擎已經(jīng)變成了:innodb,它是我們建表的首選存儲引擎。

            那么,問(wèn)題來(lái)了:

            1. innodb底層是如何存儲數據的?
            2. 表中有哪些隱藏列?
            3. 用戶(hù)記錄之間是如何關(guān)聯(lián)起來(lái)的?

            如果你想知道上面三個(gè)問(wèn)題的答案,那么,請繼續往下面看。

            本文主要包含如下內容:

            圖片

            1.磁盤(pán)or內存?

            1.1 磁盤(pán)

            數據對系統來(lái)說(shuō)是非常重要的東西,比如:用戶(hù)的身份證、手機號、銀行號、會(huì )員過(guò)期時(shí)間、積分等等。一旦丟失,會(huì )對用戶(hù)造成很大的影響。

            那么問(wèn)題來(lái)了,如何才能保證這些重要的數據不丟呢?

            答案:把數據存在磁盤(pán)上。

            當然有人會(huì )說(shuō),如果磁盤(pán)壞了怎么辦?

            那就需要備份,或者做主從了。。。

            好了,打住,這不是今天的重點(diǎn)。

            言歸正傳。

            大家都知道,從磁盤(pán)上讀寫(xiě)數據,至少需要兩次IO請求才能完成。一次是讀IO,另一次是寫(xiě)IO。

            而IO請求是比較耗時(shí)的操作,如果頻繁的進(jìn)行IO請求勢必會(huì )影響數據庫的性能。

            那么,如何才能解決數據庫的性能問(wèn)題呢?

            1.2 內存

            把數據存在寄存器?

            沒(méi)錯,操作系統從寄存器中讀取數據是最快的,因為它離CPU最近。

            但是寄存器有個(gè)非常致命的問(wèn)題是:它只能存儲非常少量的數據,設計它的目的主要是用來(lái)暫存指令和地址,并非存儲大量用戶(hù)數據的。

            這樣看來(lái),只能把數據存在內存中了。

            因為內存同樣能滿(mǎn)足我們,快速讀取和寫(xiě)入數據的需求,而且性能是非??捎^(guān)的,只是比較寄存器稍稍慢了一丟丟而已。

            不過(guò)有個(gè)讓人討厭的地方是,內存相對于磁盤(pán)來(lái)說(shuō),是更加昂貴的資源。通常情況下,500G或者1T的磁盤(pán),是很常見(jiàn)的。但你有聽(tīng)說(shuō)過(guò)有500G的內存嗎?別人會(huì )以為你瘋了。內存大小討論的數量級一般是16G或32G。

            內存可以存儲一些用戶(hù)數據,但無(wú)法存儲所有的用戶(hù)數據,因為如果數據量太大了,它可能還是存不下。

            此外,即使用戶(hù)數據能剛好存在內存,以后萬(wàn)一有一天,數據庫服務(wù)器或者部署節點(diǎn)掛了,或者重啟了,數據不就丟了?

            怎么做,才能不會(huì )因為異常情況,而丟數據。同時(shí),又能保證數據的讀寫(xiě)速度呢?

            2.數據頁(yè)

            我們可以把一批數據放在一起。

            寫(xiě)操作時(shí),先將數據寫(xiě)到內存的某個(gè)批次中,然后再將該批次的數據一次性刷到磁盤(pán)上。如下圖所示:圖片

            讀操作時(shí),從磁盤(pán)上一次讀一批數據,然后加載到內存當中,以后就在內存中操作。如下圖所示:圖片

            將內存中的數據刷到磁盤(pán),或者將磁盤(pán)中的數據加載到內存,都是以批次為單位,這個(gè)批次就是我們常說(shuō)的:數據頁(yè)。

            當然innodb中存在多種不同類(lèi)型的頁(yè),數據頁(yè)只是其中一種,我們在這里重點(diǎn)介紹一下數據頁(yè)。

            那么問(wèn)題來(lái)了,什么是數據頁(yè)?

            數據頁(yè)主要是用來(lái)存儲表中記錄的,它在磁盤(pán)中是用雙向鏈表相連的,方便查找,能夠非??焖俚脧囊粋€(gè)數據頁(yè),定位到另一個(gè)數據頁(yè)。

            很多時(shí)候,由于我們表中的數據比較多,在磁盤(pán)中可能存放在多個(gè)數據頁(yè)當中。

            有一天,我們要根據某個(gè)條件查詢(xún)數據時(shí),需要從一個(gè)數據頁(yè)找到另一個(gè)數據頁(yè),這時(shí)候的雙向鏈表就派上大用場(chǎng)了。磁盤(pán)中各數據頁(yè)的整體結構如下圖所示:圖片通常情況下,單個(gè)數據頁(yè)默認的大小是16kb。當然,我們也可以通過(guò)參數:innodb_page_size,來(lái)重新設置大小。不過(guò),一般情況下,用它的默認值就夠了。

            好吧,數據頁(yè)的整體結構已經(jīng)搞明白了。

            那么,單個(gè)數據頁(yè)包含哪些內容呢?

            圖片從上圖中可以看出,數據頁(yè)主要包含如下幾個(gè)部分:

            • 文件頭部
            • 頁(yè)頭部
            • 最大和最小記錄
            • 用戶(hù)記錄
            • 空閑空間
            • 頁(yè)目錄
            • 文件尾部

            3.用戶(hù)記錄

            對于新申請的數據頁(yè),用戶(hù)記錄是空的。當插入數據時(shí),innodb會(huì )將一部分空閑空間分配給用戶(hù)記錄。

            用戶(hù)記錄是innodb的重中之重,我們平時(shí)保存到數據庫中的數據,就存儲在它里面。那么,它里面又包含哪些內容呢?你不好奇嗎?

            其實(shí)在innodb支持的數據行格式有四種:

            1. compact行格式
            2. redundant行格式
            3. dynamic行格式
            4. compressed行格式

            我們以compact行格式為例:圖片一條用戶(hù)記錄主要包含三部分內容:

            1. 記錄額外信息,它包含了變長(cháng)字段、null值列表和記錄頭信息。
            2. 隱藏列,它包含了行id、事務(wù)id和回滾點(diǎn)。
            3. 真正的數據列,包含真正的用戶(hù)數據,可以有很多列。

            下面讓我們一起了解一下這些內容。

            3.1 額外信息

            額外信息并非真正的用戶(hù)數據,它是為了輔助存數據用的。

            3.1.1 變長(cháng)字段列表

            有些數據如果直接存會(huì )有問(wèn)題,比如:如果某個(gè)字段是varchar或text類(lèi)型,它的長(cháng)度不固定,可以根據存入數據的長(cháng)度不同,而隨之變化。

            如果不在一個(gè)地方記錄數據真正的長(cháng)度,innodb很可能不知道要分配多少空間。假如都按某個(gè)固定長(cháng)度分配空間,但實(shí)際數據又沒(méi)占多少空間,豈不是會(huì )浪費?

            所以,需要在變長(cháng)字段中記錄某個(gè)變長(cháng)字段占用的字節數,方便按需分配空間。

            3.1.2 null值列表

            數據庫中有些字段的值允許為null,如果把每個(gè)字段的null值,都保存到用戶(hù)記錄中,顯然有些浪費存儲空間。

            有沒(méi)有辦法只簡(jiǎn)單的標記一下,不存儲實(shí)際的null值呢?

            答案:將為null的字段保存到null值列表。

            在列表中用二進(jìn)制的值1,表示該字段允許為null,用0表示不允許為null。它只占用了1位,就能表示某個(gè)字符是否為null,確實(shí)可以節省很多存儲空間。

            3.1.3 記錄頭信息

            記錄頭信息用于描述一些特殊的屬性。

            圖片它主要包含:

            • deleted_flag:即刪除標記,用于標記該記錄是否被刪除了。
            • min_rec_flag:即最小目錄標記,它是非葉子節點(diǎn)中的最小目錄標記。
            • n_owned:即擁有的記錄數,記錄該組索引記錄的條數。
            • heap_no:即堆上的位置,它表示當前記錄在堆上的位置。
            • record_type:即記錄類(lèi)型,其中:0表示普通記錄,1表示非葉子節點(diǎn),2表示Infrimum記錄, 3表示Supremum記錄。
            • next_record:即下一條記錄的位置。

            3.2 隱藏列

            數據庫在保存一條用戶(hù)記錄時(shí),會(huì )自動(dòng)創(chuàng )建一些隱藏列。如下圖所示:圖片目前innodb自動(dòng)創(chuàng )建的隱藏列有三種:

            • db_row_id,即行id,它是一條記錄的唯一標識。
            • db_trx_id,即事務(wù)id,它是事務(wù)的唯一標識。
            • db_roll_ptr,即回滾點(diǎn),它用于事務(wù)回滾。

            如果表中有主鍵,則用主鍵做行id,無(wú)需額外創(chuàng )建。如果表中沒(méi)有主鍵,假如有不為null的unique唯一鍵,則用它做為行id,同樣無(wú)需額外創(chuàng )建。

            如果表中既沒(méi)有主鍵,又沒(méi)有唯一鍵,則數據庫會(huì )自動(dòng)創(chuàng )建行id。

            也就是說(shuō)在innodb中,隱藏列中事務(wù)id回滾點(diǎn)是一定會(huì )被創(chuàng )建的,但行id要根據實(shí)際情況決定。

            3.3 真正數據列

            真正的數據列中存儲了用戶(hù)的真實(shí)數據,它可以包含很多列的數據。這個(gè)比較簡(jiǎn)單,沒(méi)有什么好多說(shuō)的。

            3.4 用戶(hù)記錄是如何相連的?

            通過(guò)上面介紹的內容,大家對一條用戶(hù)記錄是如何存儲的,應該有了一定的認識。

            但問(wèn)題來(lái)了,一條用戶(hù)記錄和另一條用戶(hù)記錄是如何相連的,innodb是怎么知道,某條記錄的下一條記錄是誰(shuí)?

            答案是:用前面提到過(guò)的, 記錄額外信息 》 記錄頭信息 》下一條記錄的位置。

            圖片多條用戶(hù)記錄之間通過(guò)下一條記錄的位置,組成了一個(gè)單向鏈表。這樣就能從前往后,找到所有的記錄了。

            4.最大和最小記錄

            從上面可以得知,在一個(gè)數據頁(yè)當中,如果存在多條用戶(hù)記錄,它們是通過(guò)下一條記錄的位置相連的。

            不過(guò)有個(gè)問(wèn)題:如果才能快速找到最大的記錄和最小的記錄呢?

            這就需要在保存用戶(hù)記錄的同時(shí),也保存最大和最小記錄了。

            最大記錄保存到Supremum記錄中。

            最小記錄保存在Infimum記錄中。

            在保存用戶(hù)記錄時(shí),數據庫會(huì )自動(dòng)創(chuàng )建兩條額外的記錄:Supremum 和 Infimum。它們之間的關(guān)系,如下圖所示:

            圖片從圖中可以看出用戶(hù)數據是從最小記錄開(kāi)始,通過(guò)下一條記錄的位置,從小到大,一步步查找,最后找到最大記錄為止。

            5.頁(yè)目錄

            從上面可以看出,如果我們要查詢(xún)某條記錄的話(huà),數據庫會(huì )從最小記錄開(kāi)始,一條條查找所有記錄。如果中途找到了,則直接返回該記錄。如果一直找到最大記錄,還沒(méi)有找到想要的記錄,則返回空。

            咋一看,沒(méi)有問(wèn)題。

            但如果仔細想想。

            效率會(huì )不會(huì )有點(diǎn)低?

            這不是要對整頁(yè)用戶(hù)數據進(jìn)行掃描嗎?

            有沒(méi)有更高效的方法?

            這就需要使用頁(yè)目錄了。

            說(shuō)白了,就是把一頁(yè)用戶(hù)記錄分為若干組,每一組的最大記錄都保存到一個(gè)地方,這個(gè)地方就是頁(yè)目錄。每一組的最大記錄叫做。

            由此可見(jiàn),頁(yè)目錄是有多個(gè)槽組成的。所下圖所示:

            圖片假設一頁(yè)的數據分為4組,這樣在頁(yè)目錄中,就對應了4個(gè)槽,每個(gè)槽中都保存了該組數據的最大值。

            這樣就能通過(guò)二分查找,比較槽中的記錄跟需要找到的記錄的大小。如果用戶(hù)需要查找的記錄,小于當前槽中的記錄,則向上查找上一個(gè)槽。如果用戶(hù)需要查找的記錄,大于當前槽中的記錄,則向下查找下一個(gè)槽。

            如此一來(lái),就能通過(guò)二分查找,快速的定位需要查找的記錄了。

            so easy

            6.文件頭部和尾部

            6.1 文件頭部

            通過(guò)前面介紹的行記錄中下一條記錄的位置頁(yè)目錄,innodb能非??焖俚亩ㄎ荒骋粭l記錄。但有個(gè)前提條件,就是用戶(hù)記錄必須在同一個(gè)數據頁(yè)當中。

            如果用戶(hù)記錄非常多,在第一個(gè)數據頁(yè)找不到我們想要的數據,需要到另外一頁(yè)找該怎么辦呢?

            這時(shí)就需要使用文件頭部了。

            它里面包含了多個(gè)信息,但我只列出了其中4個(gè)最關(guān)鍵的信息:

            1. 頁(yè)號
            2. 上一頁(yè)頁(yè)號
            3. 下一頁(yè)頁(yè)號
            4. 頁(yè)類(lèi)型

            顧名思義,innodb是通過(guò)頁(yè)號、上一頁(yè)頁(yè)號和下一頁(yè)頁(yè)號來(lái)串聯(lián)不同數據頁(yè)的。如下圖所示:圖片不同的數據頁(yè)之間,通過(guò)上一頁(yè)頁(yè)號和下一頁(yè)頁(yè)號構成了雙向鏈表。這樣就能從前向后,一頁(yè)頁(yè)查找所有的數據了。

            此外,頁(yè)類(lèi)型也是一個(gè)非常重要的字段,它包含了多種類(lèi)型,其中比較出名的有:數據頁(yè)、索引頁(yè)(目錄項頁(yè))、溢出頁(yè)、undo日志頁(yè)等。

            6.2 文件尾部

            我之前提過(guò),數據庫的數據是以數據頁(yè)為單位,加載到內存中,如果數據有更新的話(huà),需要刷新到磁盤(pán)上。

            但如果某一天比較倒霉,程序在刷新到磁盤(pán)的過(guò)程中,出現了異常,比如:進(jìn)程被kill掉了,或者服務(wù)器被重啟了。

            這時(shí)候數據可能只刷新了一部分,如何判斷上次刷盤(pán)的數據是完整的呢?

            這就需要用到文件尾部。

            它里面記錄了頁(yè)面的校驗和。

            在數據刷新到磁盤(pán)之前,會(huì )先計算一個(gè)頁(yè)面的校驗和。后面如果數據有更新的話(huà),會(huì )計算一個(gè)新值。文件頭部中也會(huì )記錄這個(gè)校驗和,由于文件頭部在前面,會(huì )先被刷新到磁盤(pán)上。

            接下來(lái),刷新用戶(hù)記錄到磁盤(pán)的時(shí)候,假設刷新了一部分,恰好程序出現異常了。這時(shí),文件尾部的校驗和,還是一個(gè)舊值。數據庫會(huì )去校驗,文件尾部的校驗和,不等于文件頭部的新值,說(shuō)明該數據頁(yè)的數據是不完整的。

            7.頁(yè)頭部

            通過(guò)上面介紹的內容,數據頁(yè)之間能夠輕松訪(fǎng)問(wèn)了,但剩下還有個(gè)比較重要的問(wèn)題,就是記錄的狀態(tài)信息。

            比如一頁(yè)數據到底保存了多條記錄,或者頁(yè)目錄到底使用了多個(gè)槽等。這些信息是實(shí)時(shí)統計,還是事先統計好了,保存到某個(gè)地方?

            為了性能考慮,上面的這些統計數據,當然是先統計好,保存到一個(gè)地方。后面需要用到該數據時(shí),再讀取出來(lái)會(huì )更好。這個(gè)保存統計數據的地方,就是頁(yè)頭部。

            當然頁(yè)頭部不僅僅只保存:槽的數量、記錄條數等信息。

            它還記錄了:

            • 已刪除記錄所占的字節數
            • 最后插入記錄的位置
            • 最大事務(wù)id
            • 索引id
            • 索引層級

            其實(shí)還有很多,在這里就不一一列舉了,有興趣的朋友可以找我私聊。

            總結

            多個(gè)數據頁(yè)之間通過(guò)頁(yè)號構成了雙向鏈表。而每一個(gè)數據頁(yè)的行數據之間,又通過(guò)下一條記錄的位置構成了單項鏈表。整體架構圖如下:

            圖片



            關(guān)鍵字: MySQL innodb

            文章連接: http://www.gostscript.com/wzyh/788.html

            版權聲明:文章由 晨展科技 整理收集,來(lái)源于互聯(lián)網(wǎng)或者用戶(hù)投稿,如有侵權,請聯(lián)系我們,我們會(huì )立即刪除。如轉載請保留

            双腿国产亚洲精品无码不卡|国产91精品无码麻豆|97久久久久久久极品|无码人妻少妇久久中文字幕
                1.