最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

詳解MySQL的主鍵查詢?yōu)槭裁催@么快

 更新時間:2022年04月02日 09:56:52   作者:蟬沐風(fēng)_  
這篇文章主要介紹了想借MySQL的主鍵查詢?yōu)槭裁催@么快,下面根據(jù)主題展開的內(nèi)容有InnoDB行格式、InnoDB頁和InnoDB行格式等詳細(xì)內(nèi)容,下面相關(guān)介紹需要的小伙伴可以參考一下

這篇文章會讓大家清楚地明白:

  • 什么是InnoDB行格式?InnoDB頁是什么?
  • InnoDB頁和InnoDB行格式都有哪些字段信息?
  • 為什么推薦使用自增ID作為主鍵,而不推薦使用UUID?
  • InnoDB設(shè)計者如何設(shè)計高效算法,快速在一個頁中搜索記錄。

正文開始!

注:我們接下來的所有描述,針對的都是InnoDB存儲引擎,如果涉及到其他存儲引擎,將會特殊說明

1. 初探InnoDB行格式(ROW_FORMAT)

我們平時都是以記錄為單位向MySQL的表中插入數(shù)據(jù)的,這些記錄在磁盤中的存放的格式就是InnoDB的行格式。

為了證明我不是瞎說,舉個例子,我查詢一下本地數(shù)據(jù)庫以forward開頭的數(shù)據(jù)表的行格式

為什么MySQL的主鍵查詢這么快?_MySQL

我們平時很少操作行格式,所以對這個概念可能不是很清楚。其實InnoDB存儲引擎為我們提供了4種不同的行格式

  • DYNAMIC(默認(rèn)的行格式)
  • COMPACT
  • REDUNDANT
  • COMPRESSED

我們可以在創(chuàng)建表時指定行格式(如果不指定,默認(rèn)行格式為DYNAMIC),比如我指定row_format_table表的行格式為COMPACT

mysql> CREATE TABLE row_format_table(
    -> id INT,
    -> c1 VARCHAR(10),
    -> c2 CHAR(10),
    -> PRIMARY KEY(id)
    -> ) CHARSET=utf8 ROW_FORMAT=COMPACT;

語法了解到這一步就可以了,接下來我們看一下4種行格式的具體表現(xiàn)形式,畫個圖就是

為什么MySQL的主鍵查詢這么快?_主鍵查詢_02

從圖中可以看出,一條完整的記錄可以分為「記錄的額外信息」和「真實數(shù)據(jù)信息」兩部分,4種行格式的不同也主要體現(xiàn)在「真實數(shù)據(jù)信息」這一部分。也就是說,不同的行格式采用了不同的數(shù)據(jù)格式來存儲我們的真實數(shù)據(jù),至于有什么具體的不同,對我們這篇文章并不重要,不需要關(guān)注。

next_record表示下一條記錄的相對位置,有了這個字段,記錄之間可以串聯(lián)成一個單鏈表,這個比較好理解,看看圖吧。至于其他的字段信息,我們用到的時候再介紹就好了。

為什么MySQL的主鍵查詢這么快?_索引_03

注意:圖中所列字段的排列順序(包括下文即將提及的)并非是InnoDB行格式在存儲設(shè)備上的真實存儲順序,為了方便說明接下來的故事,此處我做了簡化,大家理解思想即可

2. 引入InnoDB頁

對于MySQL的任何存儲引擎而言,數(shù)據(jù)都是存儲在磁盤中的,存儲引擎要操作數(shù)據(jù),必須先把磁盤中的數(shù)據(jù)加載到內(nèi)存中才可以。

那么問題來了,一次性從磁盤中加載多少數(shù)據(jù)到內(nèi)存中合適呢?當(dāng)獲取記錄時,InnoDB存儲引擎需要一條條地把記錄從磁盤中讀取出來嗎?

當(dāng)然不行!我們知道磁盤的讀寫速度和內(nèi)存讀寫速度差了幾個數(shù)量級,如果我們需要讀取的數(shù)據(jù)恰好運行在磁盤的不同位置,那就意味著會產(chǎn)生多次I/O操作。

因此,無論是操作系統(tǒng)也好,MySQL存儲引擎也罷,都有一個預(yù)讀取的概念。概念的依據(jù)便是統(tǒng)治計算機(jī)界的局部性原理。

空間局部性:如果當(dāng)前數(shù)據(jù)是正在被使用的,那么與該數(shù)據(jù)空間地址臨近的其他數(shù)據(jù)在未來有更大的可能性被使用到,因此可以優(yōu)先加載到寄存器或主存中提高效率

就是當(dāng)磁盤上的一塊數(shù)據(jù)被讀取的時候,我們干脆多讀一點,而不是用多少讀多少。

InnoDB存儲引擎將數(shù)據(jù)劃分為若干個,以作為磁盤和內(nèi)存之間交互的最小單位。InnoDB中頁的大小默認(rèn)為16KB。也就是默認(rèn)情況下,一次最少從磁盤中讀取16KB的數(shù)據(jù)到內(nèi)存中,一次最少把內(nèi)存中16KB的內(nèi)容刷新到磁盤上。

為什么MySQL的主鍵查詢這么快?_MySQL_04

對于InnoDB存儲引擎而言,所有的數(shù)據(jù)(存儲用戶數(shù)據(jù)的索引、各種元數(shù)據(jù)、系統(tǒng)數(shù)據(jù))都是以頁的形式進(jìn)行存儲的。

InnoDB頁的種類很多,比如存放Insert Buffer信息的頁,存放undo日志信息的頁等,不過我們今天不關(guān)注其他亂七八糟的頁。這篇文章的主角是存放我們表中記錄的頁,姑且稱之為數(shù)據(jù)頁吧。

3. 數(shù)據(jù)頁的結(jié)構(gòu)

很顯然,數(shù)據(jù)頁也會有自己的格式表示,像行格式一樣,我先列出兩個我們用到的字段,其他的用到再說吧。

3.1 用戶記錄是如何存放的

為什么MySQL的主鍵查詢這么快?_MySQL_05

我們實際存儲的數(shù)據(jù)表記錄會按照指定的行格式存儲到圖中的User Records部分,如果當(dāng)前的數(shù)據(jù)頁是新生成的,還沒有任何記錄的話,User Records部分其實并不會存在,而是從Free Space部分申請一塊空間劃分到User Records部分,當(dāng)Free Space空間全部用完(或者剩余的空間已經(jīng)不足以承載新數(shù)據(jù))的時候,意味著當(dāng)前數(shù)據(jù)頁的空間被占滿了,如果繼續(xù)插入記錄,就需要申請新的數(shù)據(jù)頁了,示意圖如下:

為什么MySQL的主鍵查詢這么快?_索引_06

要注意的是,上圖中的各條記錄之間通過next_record字段串聯(lián)成了一個單鏈表,只不過我沒有在圖中畫出來罷了。

但是,只是串聯(lián)起來就可以了嗎?

如果讓我們來設(shè)計串聯(lián)的規(guī)則的話,我們肯定希望能夠按照某種“大小關(guān)系”來確定串聯(lián)的順序,而不是單純按照插入數(shù)據(jù)的順序,畢竟我們是學(xué)過數(shù)據(jù)結(jié)構(gòu)的人啊!

可是記錄之間能比較大小嗎?能啊,這篇文章的題目就是關(guān)于主鍵啊,我們可以按照主鍵的順序,從小到大來串聯(lián)當(dāng)前數(shù)據(jù)頁中的所有記錄。事實上,MySQL的設(shè)計者也確實是這么設(shè)計的。

如果你足夠叛逆,你可能會想,你不設(shè)置主鍵的話是不是MySQL就崩了???

為什么MySQL的主鍵查詢這么快?_主鍵查詢_07

當(dāng)我們沒有設(shè)置主鍵的時候,為了防止這種情況,InnoDB會優(yōu)先選取一個Unique鍵作為主鍵,如果表中連Unique鍵也沒有的話,就會自動為每一條記錄添加一個叫做DB_ROW_ID的列作為默認(rèn)主鍵,只不過這個主鍵我們看不到罷了。

下面我們補(bǔ)充一下行格式

為什么MySQL的主鍵查詢這么快?_主鍵查詢_08

再次強(qiáng)調(diào)

  • 我畫的字段的順序并非在存儲設(shè)備中實際存儲的順序
  • 只有在InnoDB實在無法確定主鍵的情況下(創(chuàng)建時不指定主鍵,同時沒有Unique鍵),才會添加DB_ROW_ID

3.2 番外:為什么推薦使用自增ID作為主鍵,而不推薦使用UUID?

說到這,順便談一談為什么推薦使用自增ID作為主鍵,而不推薦使用UUID?

除了UUID主鍵索引占據(jù)大量空間的問題之外,在插入數(shù)據(jù)的資源開銷上,自增ID也遠(yuǎn)小于UUID。由于數(shù)據(jù)頁中的記錄是按照主鍵從小到大進(jìn)行串聯(lián)的,自增ID決定了后來插入的記錄一定會排列在上一條記錄的后面,只需要簡單添加next_record指針就可以了;如果當(dāng)前數(shù)據(jù)頁寫滿,那就放心地直接插入新的數(shù)據(jù)頁中就可以了。

而UUID不同,它的大小順序是不確定的,后來插入的記錄有可能(而且概率相當(dāng)大)插入到上一條記錄之前(甚至是當(dāng)前數(shù)據(jù)頁之前),這就意味著需要遍歷當(dāng)前數(shù)據(jù)頁的記錄(或者先找到相關(guān)的數(shù)據(jù)頁),然后找到自己的位置進(jìn)行插入;如果當(dāng)前數(shù)據(jù)頁寫滿了,只能先找到適合自己位置的數(shù)據(jù)頁,然后在數(shù)據(jù)頁中遍歷記錄找到自己的合適位置進(jìn)行插入。

因此使用UUID的方式插入記錄花費的時間更長。

3.3 數(shù)據(jù)頁自帶的兩條偽記錄

實際上,InnoDB的設(shè)計者在InnoDB頁中添加了兩條偽記錄,一條Infimum,一條Supremum。并且設(shè)計者規(guī)定,當(dāng)前數(shù)據(jù)頁的任何用戶記錄都比Infimum大,任何用戶記錄都比Supremum小。

因為是偽記錄,所以需要和User Records中的內(nèi)容區(qū)分開,所以把這兩條偽記錄放在了一個叫做Infimum+Supremum的部分,見下圖:

為什么MySQL的主鍵查詢這么快?_主鍵查詢_09

最終在數(shù)據(jù)頁中,用戶記錄的保存形式就成了這個樣子:

為什么MySQL的主鍵查詢這么快?_主鍵查詢_10

上圖中我把真實數(shù)據(jù)信息中的主鍵id值畫了出來,方便我們后續(xù)進(jìn)行解釋。

你可能不太理解InnoDB設(shè)計者為什么要無緣無故添加這兩個字段,這倆貨對我們的搜索工作看起來沒有任何好處。

沒錯,這倆貨不是方便我們在數(shù)據(jù)頁中檢索數(shù)據(jù)而添加的,他們發(fā)揮作用的戰(zhàn)場是MySQL的LOCK_GAP記錄鎖。啥?不懂?沒事兒,我就是提一嘴而已,對這篇文章沒啥用,具體以后再說。。。

3.4 數(shù)據(jù)頁中主鍵的高效查詢方案

到目前為止,我們已經(jīng)知道了在一個數(shù)據(jù)頁中,用戶記錄是按照主鍵由小到大的順序串聯(lián)而成的單向鏈表。接下來我們要解決的就是如何在一個數(shù)據(jù)頁中根據(jù)主鍵值搜索數(shù)據(jù)了。

如果我們執(zhí)行下面這條查詢語句

SELECT * FROM row_format_table WHERE id = 4;

最簡單的辦法就是遍歷當(dāng)前頁面的所有記錄,從Infimum記錄開始沿著單向鏈表進(jìn)行搜索,直到找到id為4的記錄為止。記錄數(shù)量少的時候還好說,這要是有成千上萬條,那誰能受的了。

所以InnoDb設(shè)計者想出了一種絕妙的搜索方法,把數(shù)據(jù)頁中的所有記錄(包括偽記錄)分成若干個小組,每個小組選出組內(nèi)最大的一條記錄作為“小組長”,接著把所有小組長的地址拿出來,編成目錄。

這就好比我們?nèi)W(xué)校找人,我們只知道他是幾年級的(確定數(shù)據(jù)頁),然后再問問每個班主任有沒有這個人(數(shù)據(jù)頁中的小組),而不是上來就直接遍歷整個年級的所有人。

為了使這種方案最大程度上發(fā)揮它的檢索效率(不能隨便分組,畢竟一個數(shù)據(jù)頁分成一個組或者每條記錄獨占一個分組跟遍歷也沒什么區(qū)別),所以InnoDB的設(shè)計者規(guī)定了如下分組方案:

  • Infimum偽記錄單獨分成一個組
  • Supremum偽記錄所在分組的記錄條數(shù)只能在1~8條之間
  • 其余分組的記錄條數(shù)只能在4~8條之間

規(guī)則是這樣,可是InnoDB怎么確定每個組內(nèi)有多少個組員呢?設(shè)計者又想了一個辦法,給“小組長”添加一個屬性,記錄這個組內(nèi)一共有多少個組員(包括自己)。所以我們再擴(kuò)充一下行格式:

為什么MySQL的主鍵查詢這么快?_主鍵查詢_11

小組長的n_owned值是組員的個數(shù)(包括自己),組員的n_owned值就是0。

接下來我們向表中多添加幾條數(shù)據(jù),看看分組到底是什么回事兒?需要注意的是,由于我們已經(jīng)在表中指定了主鍵id,因此DB_ROW_ID這個參數(shù)不會再畫出來了。

為什么MySQL的主鍵查詢這么快?_索引_12

上圖中的所有記錄(包括偽記錄)分成了4個小組,每個小組的“組長”被單獨提拔,單獨編制成“目錄”,InnoDB官方稱之為「」。槽在物理空間中是連續(xù)的,意味著通過一個槽可以很輕松地找到它的上一個和下一個,這一點非常重要。

槽的編號從0開始,我們查找數(shù)據(jù)的時候先找到對應(yīng)的槽,然后再到小組中進(jìn)行遍歷即可,因為一個小組內(nèi)的記錄數(shù)量并不多,遍歷的性能損耗可以忽略。而且每個槽代表的“組長”的主鍵值也是從小到大進(jìn)行排列的,所以我們可以用二分法進(jìn)行槽的快速查找。

圖中包含4個槽,分別是01、2、3,二分法查找之前,最低的槽low=0,最高的槽high=3?,F(xiàn)在我們再來看看在這個數(shù)據(jù)頁中,我們查詢id為7的記錄,過程是怎樣的。

  • 使用二分法,計算中間槽的位置,(0+3)/2=1,查看槽1對應(yīng)的“組長”的主鍵值為4,因為4<7,所以設(shè)置low=1,high保持不變;
  • 再次使用二分法,計算中間槽的位置,(1+3)/2=2,查看槽2對應(yīng)的“組長”的主鍵值為8,因為8>7,所以設(shè)置high=2low保持不變;
  • 現(xiàn)在high=2,low=1,兩者相差1,已經(jīng)沒有必要繼續(xù)進(jìn)行二分了,可以確定我們的記錄就在槽2中,并且我們也能知道槽2對應(yīng)的“組長”的主鍵是8,但是記錄之間是單向鏈表,我們無法向前遍歷。上文提到過,我們可以通過槽2找到槽1,進(jìn)而找到它的“組長”,然后沿著“組長”向下遍歷直到找到主鍵為7的記錄就可以了。

說到這里,我們已經(jīng)非常清楚在一個數(shù)據(jù)頁中是如何根據(jù)主鍵進(jìn)行搜索的。但是對于我們這篇文章的主題——MySQL的主鍵查詢?yōu)槭裁催@么快,只能算是回答了一半,畢竟在數(shù)據(jù)頁中進(jìn)行搜索的前提是你得先找到數(shù)據(jù)頁啊。這就是每次面試必問的MySQL索引的知識了,下一篇文章再介紹吧。

4. 重要!數(shù)據(jù)頁的其他字段

最后再補(bǔ)充幾個知識點,文章中有兩個問題我并沒有講

  • 槽是怎樣被存儲的?
  • 二分查找的時候,怎么知道目前有幾個槽呢?

先回答第1個問題,我們上文介紹過數(shù)據(jù)頁的結(jié)構(gòu),其實并不完整,下面我們再引入一個字段Page Directory,槽就是保存在了這個字段信息里。

為什么MySQL的主鍵查詢這么快?_主鍵查詢_13

Page Directory翻譯成中文就是「頁目錄」,這么一來是不是更加深了你對槽這種目錄的理解呢?

至于第2個問題,其實也是關(guān)于數(shù)據(jù)頁結(jié)構(gòu)的,之前沒有一下子全畫出來,因為我覺得需要的時候再加上更有助于記憶。

接下來我把所有之后會用到的數(shù)據(jù)頁的結(jié)構(gòu)都給大家畫出來(很簡單,別害怕),暫時沒用的就屏蔽掉了,之后用到再說吧。

為什么MySQL的主鍵查詢這么快?_主鍵查詢_14

  • FIL_PAGE_OFFSET

InnoDB頁的頁號,相當(dāng)于這個頁的身份證

  • FIL_PAGE_PREV,FIL_PAGE_NEXT

看圖你就明白了吧,每個頁之間都是雙向鏈表

  • FIL_PAGE_TYPE

InnoDB頁的種類很多,比如我們這篇文章講的數(shù)據(jù)頁,還有其他的比如存放Insert Buffer信息的頁,存放undo日志信息的頁等,這個字段就是用來標(biāo)識頁面的類型的

  • PAGE_N_DIR_SLOTS

這個字段保存的就是槽的個數(shù)了,二分法就是根據(jù)這個字段的值來確定high的值

  • PAGE_LAST_INSERT

當(dāng)前頁面最后插入記錄的PAGE_N_RECS位置,當(dāng)有新記錄插入的時候,直接讀取這個數(shù)據(jù),將新記錄放到相應(yīng)位置就可以了

    該頁中記錄的數(shù)量(不包括最小和最大記錄)

    到此這篇關(guān)于詳解MySQL的主鍵查詢?yōu)槭裁催@么快的文章就介紹到這了,更多相關(guān)MySQL的主鍵查詢內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

    相關(guān)文章

    • MySQL 一則慢日志監(jiān)控誤報的問題分析與解決

      MySQL 一則慢日志監(jiān)控誤報的問題分析與解決

      這篇文章主要介紹了MySQL 一則慢日志監(jiān)控誤報的問題分析與解決,幫助大家更好的理解和使用MySQL,感興趣的朋友可以了解下
      2021-01-01
    • mysql 8.0.16 winx64安裝配置方法圖文教程

      mysql 8.0.16 winx64安裝配置方法圖文教程

      這篇文章主要為大家分享了mysql 8.0.16 安裝配置方法圖文教程,具有一定的參考價值,感興趣的朋友可以參考一下
      2019-05-05
    • 詳解MySQL的主鍵查詢?yōu)槭裁催@么快

      詳解MySQL的主鍵查詢?yōu)槭裁催@么快

      這篇文章主要介紹了想借MySQL的主鍵查詢?yōu)槭裁催@么快,下面根據(jù)主題展開的內(nèi)容有InnoDB行格式、InnoDB頁和InnoDB行格式等詳細(xì)內(nèi)容,下面相關(guān)介紹需要的小伙伴可以參考一下
      2022-04-04
    • MySQL復(fù)制表結(jié)構(gòu)和內(nèi)容到另一張表中的SQL語句

      MySQL復(fù)制表結(jié)構(gòu)和內(nèi)容到另一張表中的SQL語句

      這篇文章主要介紹了MySQL復(fù)制表結(jié)構(gòu)和內(nèi)容到另一張表中的SQL語句,需要的朋友可以參考下
      2014-07-07
    • MySQL8.x msi版安裝教程圖文詳解

      MySQL8.x msi版安裝教程圖文詳解

      這篇文章主要介紹了MySQL8.x msi版安裝教程 ,本文圖文并茂給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下
      2019-05-05
    • MySQL高級查詢之JOIN、子查詢、窗口函數(shù)實際案例

      MySQL高級查詢之JOIN、子查詢、窗口函數(shù)實際案例

      這篇文章主要介紹了MySQL高級查詢之JOIN、子查詢、窗口函數(shù)實際案例的相關(guān)資料,JOIN用于多表關(guān)聯(lián)查詢,子查詢用于數(shù)據(jù)篩選和過濾,窗口函數(shù)則提供靈活的數(shù)據(jù)統(tǒng)計和排名功能,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下
      2025-04-04
    • MySQL中一條查詢SQL語句的完整執(zhí)行流程

      MySQL中一條查詢SQL語句的完整執(zhí)行流程

      通常我們在使用MySQL時,我們看到的只是輸入一條語句,返回一個結(jié)果,卻不知道這條語句在MySQL內(nèi)部的執(zhí)行過程,這篇文章主要給大家介紹了關(guān)于MySQL中一條查詢SQL語句的完整執(zhí)行流程,需要的朋友可以參考下
      2024-05-05
    • MySQL存儲過程的創(chuàng)建使用以及實現(xiàn)數(shù)據(jù)快速插入

      MySQL存儲過程的創(chuàng)建使用以及實現(xiàn)數(shù)據(jù)快速插入

      因最近想要測試一下MySQL百萬級數(shù)據(jù)處理過程,所以要一次對數(shù)據(jù)庫快速插入大量數(shù)據(jù),下面這篇文章主要給大家介紹了關(guān)于MySQL存儲過程的創(chuàng)建使用以及實現(xiàn)數(shù)據(jù)快速插入的相關(guān)資料,需要的朋友可以參考下
      2023-03-03
    • 101個MySQL的配置和優(yōu)化的提示

      101個MySQL的配置和優(yōu)化的提示

      這里是101條調(diào)節(jié)和優(yōu)化 MySQL安裝的技巧。一些技巧是針對特定的安裝環(huán)境的,但這些思路是通用的。我已經(jīng)把他們分成幾類,來幫助你掌握更多MySQL的調(diào)節(jié)和優(yōu)化技巧
      2013-08-08
    • CentOS下編寫shell腳本來監(jiān)控MySQL主從復(fù)制的教程

      CentOS下編寫shell腳本來監(jiān)控MySQL主從復(fù)制的教程

      這篇文章主要介紹了在CentOS系統(tǒng)下編寫shell腳本來監(jiān)控主從復(fù)制的教程,文中舉了兩個發(fā)現(xiàn)故障后再次執(zhí)行復(fù)制命令的例子,需要的朋友可以參考下
      2015-12-12

    最新評論

    吉安市| 疏勒县| 石嘴山市| 民丰县| 错那县| 赤峰市| 莱芜市| 鹤峰县| 芜湖市| 浑源县| 江都市| 隆尧县| 喀喇沁旗| 石狮市| 新绛县| 白沙| 肇庆市| 淅川县| 凌源市| 信宜市| 远安县| 西贡区| 河津市| 仁布县| 阜阳市| 红河县| 高碑店市| 奉节县| 庄河市| 岳西县| 革吉县| 宾川县| 临猗县| 天长市| 彭泽县| 河南省| 东台市| 漠河县| 寿阳县| 志丹县| 汝州市|