InnoDB數據存儲結構

来源:https://www.cnblogs.com/gmkun/archive/2022/05/03/mysql_innodb.html
-Advertisement-
Play Games

MySQL伺服器上 存儲引擎 負責對錶中數據的讀取和寫入工作,不同存儲引擎中 存放的格式 一般是不同的,甚至有的存儲引擎(Memory)不用磁碟來存儲數據。 頁 (Page) 是磁碟和記憶體之間交互的基本單位 ,也就是說資料庫管理存儲空間的基本單位是頁,資料庫I/O操作的最小單位是頁 (InnoDB頁 ...


MySQL伺服器上 存儲引擎 負責對錶中數據的讀取和寫入工作,不同存儲引擎中 存放的格式 一般是不同的,甚至有的存儲引擎(Memory)不用磁碟來存儲數據。

     

  • 頁 (Page) 是磁碟和記憶體之間交互的基本單位 ,也就是說資料庫管理存儲空間的基本單位是頁,資料庫I/O操作的最小單位是頁 (InnoDB頁預設大小16KB)

  • 區 (Extent) 是比頁大一級的存儲結構,在InnoDB存儲引擎中 ,一個區會分配64個連續的頁。因為InnoDB中頁的預設大小為16KB,所以一個區的大小是1MB=64*16KB

  • 段 (Segment) 由一個或多個區組成,段中不要求區與區之間是相鄰的。段是 資料庫中的分配單位,不同類型的資料庫對象以不同的段形式存在,當我們創建數據表、索引時,會創建對應的段。

  • 表空間 (Tablespace) 是一個邏輯容器,在一個表空間中可以有一個或多個段,一個段只能屬於一個表空間。資料庫由一個或多個表空間組成,表空間從管理上可以劃分為系統表空間、用戶表空間、撤銷表空間、臨時表空間等。

 

1_InnoDB頁結構

資料庫中的頁可以 不在物理結構上相連 ,只要通過 雙向鏈表 關聯即可。每個數據頁中的記錄會按照列 (主鍵或其他) 的順序 (從小到大或者從大到小) 組成一個 單向鏈表 ,每個數據頁都會為存儲在頁內的記錄生成一個 頁目錄 ,在通過該列查找某條記錄的時候可以在頁目錄中使用 二分法 快讀定位到對應的槽,然後再遍歷該槽對應的分組中的記錄即可快速找到指定的紀錄。

   

 

1.1.File Header

File Header 文件頭部 (38位元組) :描述頁的通用信息。

  • FIL_PAGE_OFFSET(4位元組) : 頁號(唯一)

  • FIL_PAGE_TYPE(2位元組) : 代表當前頁的類型。

  • FIL_PAGE_PREV(4位元組) 和 FIL_PAGE_NEXT(4位元組) : 分別代表本頁的上一個和下一個頁的頁號。

    通過建立一個雙向鏈表把許多頁都串聯起來,保證這些頁之間不需要是物理上的連續,而是邏輯上的連續

  • FIL_PAGE_SPACE_OR_CHKSUM(4位元組) : 當前頁面的校驗和(checksum)。 為了檢測一個頁是否完整(也就是在同步的時候有沒有發生只同步一半的尷尬情況)

    什麼是校驗和?

    就是對於一個很長的位元組串來說,我們會通過某種演算法來計算一個比較短的值來代表這個很長的位元組串,這個比較短的值就稱為校驗和。 在比較兩個很長的位元組串之前,先比較這兩個長位元組串的校驗和,如果校驗和都不一樣,則兩個長位元組串肯定是不同的,所以省去了直接比較兩個比較長的位元組串的時間損耗。

     

    頁的校驗和的作用:

    為了檢測一個頁是否完整,這時可以通過文件尾的校驗和文件頭的校驗和做比對,如果兩個值不相等則證明頁的傳輸有問題,需要重新進行傳輸,否則認為頁的傳輸已經完成。

    每當一個頁面在記憶體中修改了,在同步之前就要把它的校驗和算出來,因為File Header在頁面的前邊,所以校驗和會被首先同步到磁碟,當完全寫完時,校驗和也會被寫到頁的尾部,如果完全同步成功,則頁的首部和尾部的校驗和應該是一致的。如果寫了一半兒斷電了,那麼在File Header中的校驗和就代表著已經修改過的頁,而在File Trailer中的校驗和代表著原先的頁,二者不同則意味著同步中間出了錯。

  • FIL_PAGE_LSN(8位元組) : 頁面被最後修改時對應的日誌序列位置

 

 

1.2.File Trailer

File Trailer 文件尾部(8位元組):檢驗頁是否完整

  • 前4個位元組代表頁的校驗和:這個部分是和File Header中的校驗和相對應的。

  • 後4個位元組代表頁面被最後修改時對應的日誌序列位置(LSN):這個部分也是為了校驗頁的完整性的,如果首部和尾部的LSN值校驗不成功的話,就說明同步過程出現了問題。

 

1.3.Free Space

Free Space 空閑記錄:頁中還沒有被使用的記錄

我們自己存儲的記錄會按照指定的行格式存儲到User Records部分。但是在一開始生成頁的時候,其實並沒有User Records這個部分,每當我們插入一條記錄,都會從Free Space部分,也就是尚未使用的存儲空間中申請一個記錄大小的空間劃分到User Records部分,當Free Space部分的空間全部被User Records部分替代掉之後,也就意味著這個頁使用完了,如果還有新的記錄插入的話,就需要去申請新的頁了。

 

1.4.User Records

User Records 用戶記錄:存儲行記錄的內容

User Records中的這些記錄按照指定的行格式一條一條擺在User Records部分,相互之間形成單鏈表

 

1.5.Infimum+Supremum

Infimum+Supremum 最小和最大記錄(26位元組):虛擬的行記錄

記錄可以比大小,對於一條完整的記錄來說,比較記錄的大小就是比較主鍵的大小

比方說我們插入的4行記錄的主鍵值分別是:1、2、3、4,這也就意味著這4條記錄是從小到大依次遞增。

 

InnoDB規定的最小記錄與最大記錄這兩條記錄的構造十分簡單,都是由5位元組大小的記錄頭信息和8位元組大小的一個固定的部分組成的

這兩條記錄不是我們自己定義的記錄,所以它們並不存放在頁的User Records部分,他們被單獨放在一個稱為Infimum + Supremum的部分

 

1.6.Page Directory

Page Directory 頁目錄(56位元組):存儲用戶記錄的相對位置

在頁中,記錄是以單向鏈表的形式進行存儲的。單向鏈表的特點就是插入、刪除非常方便,但是檢索效率不高,最差的情況下需要遍歷鏈表上的所有節點才能完成檢索。因此在頁結構中專門設計了頁目錄這個模塊,專門給記錄做一個目錄,通過二分查找法的方式進行檢索,提升效率。

  • 將所有的記錄分成幾個組,這些記錄包括最小記錄和最大記錄,但不包括標記為“已刪除”的記錄。

    InnoDB規定:

    • 對於最小記錄所在的分組只能有1條記錄,

    • 最大記錄所在的分組擁有的記錄條數只能在1~8條之間,

    • 剩下的分組中記錄的條數範圍只能在是 4~8 條之間。

  • 在每個組中最後一條記錄的頭信息中會存儲該組一共有多少條記錄,作為 n_owned 欄位。

    n_owned欄位存在於行的記錄頭中

    n_owned(4bit):頁目錄中每個組中最後一條記錄的頭信息中會存儲該組一共有多少條記錄

  • 頁目錄用來存儲每組最後一條記錄的地址偏移量,這些地址偏移量會按照先後順序存儲起來,每組的地址偏移量也被稱之為槽(slot),每個槽相當於指針指向了不同組的最後一個記錄。

 

分組的步驟:

  • 初始情況下一個數據頁里只有最小記錄和最大記錄兩條記錄,它們分屬於兩個分組。

  • 每插入一條記錄,都會從頁目錄中找到主鍵值比本記錄的主鍵值大並且差值最小的槽,然後把該槽對應的記錄的n_owned值加1,表示本組內又添加了一條記錄,直到該組中的記錄數等於8個。

  • 在一個組中的記錄數等於8個後再插入一條記錄時,會將組中的記錄拆分成兩個組,一個組中4條記錄,另一個5條記錄。這個過程會在頁目錄中新增一個槽來記錄這個新增分組中最大的那條記錄的偏移量。

     

舉例:

 

 

頁目錄結構下查找記錄

在一個數據頁中查找指定主鍵值的記錄的過程分為兩步:

  1. 通過二分法確定該記錄所在的槽,並找到該槽所在分組中主鍵值最小的那條記錄。

  2. 通過記錄的next_record屬性遍歷該槽所在的組中的各個記錄。

 

舉例:

查找記錄中主鍵值為6的記錄:

因為各個槽代表的記錄的主鍵值都是從小到大排序的,所以我們可以使用二分法來進行快速查找。

5個槽的編號分別是:0、1、2、3、4,所以初始情況下最低的槽就是low=0,最高的槽就是high=4。

  1. 計算中間槽的位置:(0+4)/2=2,所以查看槽2對應記錄的主鍵值為8,又因為8 > 6,所以設置high=2,low保持不變。

  2. 重新計算中間槽的位置:(0+2)/2=1,所以查看槽1對應的主鍵值為4,又因為4 < 6,所以設置low=1,high保持不變。

  3. 因為high - low的值為1,所以確定主鍵值為6的記錄在槽2對應的組中。此刻我們需要找到槽2中主鍵值最小的那條記錄,然後沿著單向鏈表遍歷槽2中的記錄。

通過二分法確定主鍵值為6的記錄在槽2對應的組中,定位槽2中最小的數據

怎麼定位一個組中最小的記錄呢?

別忘了各個槽都是挨著的,我們可以很輕易的拿到槽1對應的記錄(主鍵值為4),該條記錄的下一條記錄就是槽2中主鍵值最小的記錄 (記錄之間通過單鏈錶鏈接),該記錄的主鍵值為5。

從槽2中最小的數據 (主鍵值為5的記錄) 出發,遍歷槽2中的各條記錄,直到找到主鍵值為6的那條記錄即可。

 

1.7.Page Header

Page Header 頁面頭部(56位元組):頁的狀態信息

為了能得到一個數據頁中存儲的記錄的狀態信息,比如本頁中已經存儲了多少條記錄,第一條記錄的地址是什麼,頁目錄中存儲了多少個槽等等,特意在頁中定義了一個叫Page Header的部分,這個部分占用固定的56個位元組,專門存儲各種狀態信息。

 

2_InnoDB行格式

我們平時的數據以行為單位來向表中插入數據,這些記錄在磁碟上的存放方式也被稱為行格式或者記錄格式。InnoDB存儲引擎設計了4種不同類型的行格式,分別是CompactRedundantDynamicCompressed行格式。

 

行格式操作

查看MySQL8的預設行格式:

SELECT @@innodb_default_row_format;

查看具體表的行格式:

SHOW TABLE STATUS like '表名';

指定行格式:

#創建表時指定行格式
CREATE TABLE 表名 (列的信息) ROW_FORMAT=行格式名稱
#修改表的行格式
ALTER TABLE 表名 ROW_FORMAT=行格式名稱

 

 

2.1.compact行格式

在MySQL 5.1版本中,預設設置為Compact行格式。一條完整的記錄其實可以被分為記錄的額外信息記錄的真實數據兩大部分。

 

1.變長欄位長度列表

變長欄位長度列表:記錄所有變長欄位的真實數據占用的位元組長度

MySQL支持一些變長的數據類型,比如VARCHAR(M)、VARBINARY(M)、TEXT類型,BLOB類型,這些數據類型修飾列稱為變長欄位,變長欄位中存儲多少位元組的數據不是固定的,所以我們在存儲真實數據的時候需要順便把這些數據占用的位元組數也存起來。

註意:存儲的變長長度和欄位順序相反

 

舉例:

各個列都使用的是ASCII字元集(每個字元只需要1個位元組來進行編碼)。

長度值按照列的逆序存放,所以最後變長欄位長度列表的位元組串用十六進位表示的效果就是:060408

行格式:

 

 

2.NULL值列表

NULL值列表:把可以為NULL的列統一管理起來,標記對應列的值是否為NULL

註意:

  • 這裡面存儲的NULL值列表和欄位順序相反

  • 如果表中沒有允許存儲 NULL 的列,則 NULL值列表也不存在了。

 

為什麼定義NULL值列表?

  • 數據都是需要對齊的,如果沒有標註出來NULL值的位置,就有可能在查詢數據的時候出現混亂。

  • 如果使用一個特定的符號放到相應的數據位表示空置的話,雖然能達到效果,但是這樣很浪費空間,所以直接就在行數據得頭部開闢出一塊空間專門用來記錄該行數據哪些是非空數據,哪些是空數據

    • 二進位位的值為1時,代表該列的值為NULL。

    • 二進位位的值為0時,代表該列的值不為NULL。

 

舉例:

行格式:

 

 

 

 

3.記錄頭信息

 

  • delete_mask(1bit):標記著當前記錄是否被刪除(1被刪除,0沒有刪除)

    被刪除的記錄為什麼還在頁中存儲呢? 你以為它刪除了,可它還在真實的磁碟上。這些被刪除的記錄之所以不立即從磁碟上移除,是因為移除它們之後其他的記錄在磁碟上需要重新排列,導致性能消耗。所以只是打一個刪除標記而已,所有被刪除掉的記錄都會組成一個所謂的垃圾鏈表,在這個鏈表中的記錄占用的空間稱之為可重用空間,之後如果有新記錄插入到表中的話,可能把這些被刪除的記錄占用的存儲空間覆蓋掉。

  • min_rec_mask(1bit):標識是否為最小的目錄項記錄(1最小記錄,0不是最小記錄)

  • n_owned(4bit):頁目錄中每個組中最後一條記錄的頭信息中會存儲該組一共有多少條記錄

  • heap_no(13bit):表示當前記錄在本頁中的位置

    怎麼不見heap_no值為0和1的記錄呢? MySQL會自動給每個頁裡加了兩個記錄,由於這兩個記錄並不是我們自己插入的,所以有時候也稱為偽記錄或者虛擬記錄。這兩個偽記錄一個代表最小記錄,一個代表最大記錄。最小記錄和最大記錄的heap_no值分別是0和1,也就是說它們的位置最靠前。

  • record_type(3bit):當前記錄的類型

    • 0:表示普通記錄

    • 1:表示目錄項記錄

    • 2:表示最小記錄

    • 3:表示最大記錄

  • next_record(16bit):表示從當前記錄的真實數據到下一條記錄的真實數據的地址偏移量。

    下一條記錄:

    下一條記錄指得並不是按照我們插入順序的下一條記錄,而是按照主鍵值由小到大的順序的下一條記錄。而且規定Infimum記錄(也就是最小記錄)的下一條記錄就是本頁中主鍵值最小的用戶記錄,而本頁中主鍵值最大的用戶記錄的下一條記錄就是 Supremum記錄(也就是最大記錄)。

     

 

4.記錄的真實數據

記錄的真實數據除了我們自己定義的列的數據以外,還會有三個隱藏列:

  • row_id:一個表沒有手動定義主鍵,則會選取一個Unique鍵作為主鍵,如果連Unique鍵都沒有定義的話,則會為表預設添加一個名為row_id的隱藏列作為主鍵。所以row_id是在沒有自定義主鍵以及Unique鍵的情況下才會存在的。

 

2.2.dynamic&compressed行格式

在MySQL 8.0中,預設行格式就是Dynamic,Dynamic、Compressed行格式和Compact行格式挺像,只不過在處理行溢出數據時有分歧:

行溢出:

一個頁的大小一般是16KB,也就是16384位元組,而一個VARCHAR(M)類型的列就最多可以存儲65533個位元組,這樣就可能出現一個頁存放不了一條記錄,這種現象稱為行溢出。

  • Compressed和Dynamic兩種記錄格式對於存放在BLOB中的數據採用了完全的行溢出的方式。

  • Compact和Redundant兩種格式會在記錄的真實數據處存儲一部分數據

     

 

Compressed行記錄格式的另一個功能就是,存儲在其中的行數據會以zlib的演算法進行壓縮,因此對於BLOB、TEXT、VARCHAR這類大長度類型的數據能夠進行非常有效的存儲。

 

 

2.3.redundant行格式

欄位長度偏移列表與變長欄位長度列表有兩處不同:

  • 少了“變長”兩個字:Redundant行格式會把該條記錄中所有列(包括隱藏列)的長度信息都按照逆序存儲到欄位長度偏移列表。

  • 多了“偏移”兩個字:這意味著計算列值長度的方式不像Compact行格式那麼直觀,它是採用兩個相鄰數值的差值來計算各個列值的長度。

 

記錄頭信息與Compact行格式的記錄頭信息對比來看,有兩處不同:

  • Redundant行格式多了n_field和1byte_offs_flag這兩個屬性。

    • n_fields:代表一行中列的數量,占用10位

    • 1byte_offs_flags:定義了偏移列表占用的大小(1:1個位元組,0:2個位元組)

      1byte_offs_flag的值是怎麼選擇的 根據該條Redundant行格式記錄的真實數據占用的總大小來判斷的:

      • 當記錄的真實數據占用的位元組數值不大於127(十六進位0x7F,二進位01111111)時,每個列對應的偏移量占用1個位元組。

      • 當記錄的真實數據占用的位元組數大於127,但不大於32767(十六進位0x7FFF,二進位0111111111111111)時,每個列對應的偏移量占用2個位元組。

      • 當記錄的真實數據占用的位元組數大於32767時,將部分記錄存放到溢出頁中,在本頁中只保留前768個位元組和20個位元組的溢出頁面地址。

  • Redundant行格式沒有record_type這個屬性。

 

Redundant行格式中NULL值的處理:

因為Redundant行格式並沒有NULL值列表,所以Redundant行格式在欄位長度偏移列表中的各個列對應的偏移量處做了一些特殊處理 —— 將列對應的偏移量值的第一個比特位作為是否為NULL的依據,該比特位也可以被稱之為NULL比特位。也就是說在解析一條記錄的某個列時,首先看一下該列對應的偏移量的NULL比特位是不是為1。如果為1,那麼該列的值就是NULL,否則不是NULL。


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

-Advertisement-
Play Games
更多相關文章
  • 指令 描述 echo 說明:回顯命令信息,也就是顯示該命令 使用方法: 1.echo [on(打開回顯) | off(關閉回顯)] 常用的是echo off 2.echo [信息內容] 相當於編程語言中的print 3.echo 文件內容>>文件名 給創建一個文件並添加內容 @ 說明:字元放在命令前 ...
  • 本文講講 Ubuntu 18 及以上版本配置 IP 的方法,為什麼它值得一講,因為以 Ubuntu 16 為首的版本的配置方法已經不適用了,如果你還不知道,那本文正好 get 一個新技能。 Ubuntu 18 之後版本配置方法 需要使用 netplan 工具。 對應配置文件: /etc/netpla ...
  • 原文鏈接:https://www.caituotuo.top/c56bd0c5.html 0. 前言 假設一次執行20條SQL,我們如何判斷哪條SQL是執行慢的爛SQL,這裡就需要用到慢查詢日誌。 在SQL中,廣義的查詢就是crud操作,而狹義的查詢僅僅是select查詢操作,慢查詢指的是廣義的查詢 ...
  • 概述 日誌文件記錄 MySQL 資料庫運行期間發生的變化,當資料庫遭到意外的損害時,可以通過日誌文件查詢出錯原因,併進件數據恢復 MySQL 日誌文件可以分成以下幾類: 二進位日誌:記錄所有更改數據的語句,可以用於主從複製 錯誤日誌:記錄 MySQL 服務出現的問題 查詢日誌:記錄建立的客戶端連接和 ...
  • mybatis操作資料庫的過程中,如果只考慮單表操作,mapper和dao層基本80%的都是固定的,故而可以使用工具進行生成,文末提供自己編寫的工具(基於mysql存儲過程):作者其實就是使用(mybatis-generator)這個工具過程中,有些想法,實踐下,編寫時很多實現留了口子,後續方便集成 ...
  • 由於MHA(mha4mysql-manager)工具在2018年已經停止維護更新,且不支持GTID複製模式,在原版基礎上增補功能難度較大,因此考慮將其重構。 ...
  • 毫不誇張地說,咱們後端工程師,無論在哪家公司,呆在哪個團隊,做哪個系統,遇到的第一個讓人頭疼的問題絕對是資料庫性能問題。如果我們有一套成熟的方法論,能讓大家快速、準確的去選擇出合適的優化方案,我相信能夠快速準備解決咱們日常遇到的80%-90%的性能問題。 從解決問題的角度出發,我們得先瞭解到問題的原... ...
  • 1.1 MySQL安裝 1.1.1 下載wget命令 yum -y install wget 1.1.2 線上下載mysql安裝包 wget https://dev.mysql.com/get/mysql57-community-release-el7-8.noarch.rpm 1.1.3 安裝My ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...