數據結構與算法筆記:高級篇 - B+樹:MySql數據庫索引是如何實現的?

概述

作為一名軟件開發工程師,你對數據庫肯定再熟悉不過了。MySQL 作為主流的數據庫存儲系統,它在我們的業務開發中,有著舉足輕重的地位。在工作中,為了加速數據庫中數據的查找速度,我們常用的處理思路是,對表中的數據創建索引。那你是否考慮過,數據庫索引是如何實現的呢?底層使用的是什么數據結構和算法呢?


算法解析

思考的過程比結論重要,本章會盡量還原這個解決方案的思考過程,讓你知其然,并知其所以然。

1.解決問題的前提是定義清楚問題

如何定義清楚問題呢?除了對問題進行詳細的調研,還有一個辦法,那就是,通過對一些模糊的需求進行假設,來限定要解決的問題的范圍。

如果你對數據庫的操作非常了解,針對我們現在這個問題,你就能把索引的需求定義得非常清楚。但是,對于大部分軟件工程師來說,我們可能只了解一小部分常用的 SQL 語句,所以,我們這里假設要解決的問題,只包含這樣兩個常用的需求:

  • 根據某個值查找數據,比如 select * from user where id = 1234
  • 根據區間來查找某些數據,比如 select * from user where id > 1234 and id < 2345

除了這些功能性需求之外,這種問題往往還會涉及一些非功能性需求,比如安全、性能、用戶體驗等等。限于本章要討論的是數據結構和算法,對于非功能性需求,我們著重考慮性能方面的需求。性能方面的需求,我們主要考察時間和空間兩方面,也就是執行效率和存儲空間

在執行效率方面,我們系統通過索引,查詢數據的效率盡可能地高;在存儲空間方面,我們希望索引不要消耗太多的內存空間。

2.嘗試用學過的數據結構解決這個問題

問題的需求大致定義清楚了,現在回想一下,能否利用已經學習過的數據結構解決這個問題呢?支持快速查詢、插入等操作的動態數據結構,我們學習過散列表、平衡二叉查找樹、跳表。

先來看散列表。散列表的查詢性能很好,時間復雜度是 O ( 1 ) O(1) O(1)。但是,散列表不支持按區間快速查找數據。所以,散列表不能滿足我們的需求。

再看下平衡二叉查找樹。盡管平衡二叉查找樹查詢的性能也很高,時間復雜度是 O ( l o g n ) O(logn) O(logn)。而且,對數進行中序遍歷,還可以得到一個從小到大的有序的數據序列,但仍然不足以支持按照區間快速查找數據。

最后看下跳表。跳表是在鏈表之上加上多層索引構成的。它支持快速插入、查找、刪除數據,對應的時間復雜度是 O ( l o g n ) O(logn) O(logn)。并且跳表也支持按區間快速查找數據。我們只需要定位區間的起點值對應在鏈表中的位置,然后從這個節點開始,順序遍歷鏈表,直到區間終點對應的結點為止,這期間遍歷得到的數據就是滿足區間值的數據。

在這里插入圖片描述

這樣看來,跳表是可以解決這個問題。實際上,數據庫索引所用到的數據結構跟跳表非常相似,叫作 B+ 樹。不過,它是通過二叉查找樹演化過來的,而非跳表。為了給你還原發明 B+ 樹的整個思考過程,所以,接下來,我還要從二叉查找樹將其,看它是如何一步一步被改造成 B+ 樹的。

改造二叉查找樹來解決這個問題

為了讓二叉查找樹支持按照區間來查找數據,我們可以對它進行這樣的改造:樹中的節點并不存儲數據本身,而是只是作為索引。此外,我們把每個葉子節點串在一條鏈表上,鏈表中的數據時從小到大有序的。經過改造之后的二叉樹,就像圖中這樣,看起來是不是很像跳表。

在這里插入圖片描述

改造之后,如果我們要求某個區間的數據。我們只需要拿到區間的起始值,在樹中進行查找,當查找到某個葉子節點后,我們再順著鏈表往后遍歷,直到鏈表中的節點數據值大于區間的終止值為止。所有遍歷的數據,就是符合區間值的所有數據。

在這里插入圖片描述

但是,我們要為幾千萬、上億的數據構建索引,如果將索引存儲在內存中,盡管內存訪問的速度非常快,查詢的效率非常高,但是,占用的內存會非常多。

比如,我們給一億個數據構建二級索引,那索引中會保護大約 1 億個節點,每個節點假設占 16 個字節,那就需要大約 1GB 的內存空間。給一張表建立索引,我們需要 1GB 的內存空間。如果我們要給 10 張表構建索引,那對內存的需求是無法滿足的。如何解決索引占用太多內存這個問題呢?

我們可以借助時間換空間的思路,把索引存儲到磁盤中,而非內存中。我們都知道,硬盤是一個非常慢速的存儲設備。通常內存的訪問速度是納秒級的,而磁盤的訪問速度是毫秒級別的。讀取同樣大小的數據,從磁盤總讀取花費的時間,是從內存中讀取所花費時間的上萬倍,甚至幾十萬倍。

這種將索引存儲在磁盤中的方案,盡管減少了內存消耗,但是在查找數據的過程中,需要讀取磁盤中的索引,因此數據查詢效率就相應降低很多。

二叉查找樹經過改造之后,支持區間查找的功能實現了。不過,為了節省內存,如果把樹存儲在硬盤中,那么每個節點的讀取(或者訪問),都對應一次磁盤 IO 操作。樹的高度就等于每次查詢數據時磁盤 IO 操作的次數。

前面說過,比起內存讀寫操作,磁盤 IO 操作非常耗時,所以我們優化的重點就是盡量減少磁盤 IO 的次數,也就是盡量降低樹的高度。那如何降低樹的高度呢?

我們來看下,如果我們把索引構建成 m 叉樹,高度是不是比二叉樹要小呢?如圖所示,給 16 個數據構建二叉樹索引,樹的高度是 4,查找一個數據,需要 4 個磁盤 IO 操作(如果根節點存儲子內存中,其他節點存儲在磁盤中),如果對 16 個數據構建五叉樹索引,那高度只有 2,查找一個數據,對應只需要 2 次磁盤操作。如果 m 叉樹中的 m 是 100,那對一億個數據構建索引,樹的高度也只是 3,最多只要 3 次磁盤 IO 就能獲取到數據。磁盤 IO 變少了,查找數據的效率也就提高了。

在這里插入圖片描述
在這里插入圖片描述

如果我們將 m 叉樹實現 B+ 樹索引,用代碼實現出來,就是下面這樣剛子(假設我們給 int 類型的數據庫字段添加索引,所以代碼中的 keywords 是 int 類型的)。

/*** 這是B+樹非葉子節點的定義** 假設keywords=[3, 5, 8, 10]* 4個鍵值將數據分為5個區間:(-INF,3), [3,5), [5,8), [8,10), [10,INF)* 5個區間分別對應: children[0]...children[4]** m值是事先計算得到的,計算的依據是讓所有信息的大小正好等于頁的大小:* PAGE_SIZE = (m-1)*4[keywords大小] + m*8[children大小]*/
public class BPlusTreeNode {public static int m = 5; // 5叉樹public int[] keywords = new int[m-1]; // 鍵值,用來劃分數據區間public BPlusTreeNode[] children = new BPlusTreeNode[m]; // 保存子節點指針
}/*** 這是B+樹的葉子節點的定義。** B+樹中的葉子節點跟內部節點是不一樣的* 葉子節點存儲的是值,而非區間。* 這個定義里,每個葉子節點存儲3個數據行的鍵值及地址信息。** k是事先計算得到的,計算的基于是讓所有信息的大小正好等于頁的大小* PAGE_SIZE = k*4[keywords大小] + k*8[dataAddress大小]+8[prev大小]+8[next大小]*/
public class BPlusTreeLeafNode {public static int k = 3;public int[] keywords = new int[k]; // 數據的鍵值public long[] dataAddress = new long[k]; // 數據地址public BPlusTreeLeafNode prev; // 這個節點在鏈表中的前驅節點public BPlusTreeLeafNode next; // 這個節點在鏈表中的后繼節點
}

我稍微解釋下這段代碼。

對于相同個數的數據構建 m 叉索引,m 叉樹中的 m 越大,那樹的高度就越小,那 m 叉樹中的 m 是不是越大就越好呢?到底多大才最合適呢?

不管是內存中的數據,還是磁盤中的數據,操作系統都是按頁(一頁大小通常是 4KB,這個值可以通過 getconfig PAGE_SIZE 命令查看)來讀取,一次會讀一頁的數據。如果讀取的數據量超過一頁的大小,就會觸發多次 IO 操作。所以,我們在選擇 m 大小的時候,要盡量讓每個節點大小等于一個頁的大小。讀取一個節點,只需要一次磁盤 IO 操作。

在這里插入圖片描述

正式因為要時刻保證 B+ 樹索引是一個 m 叉樹,所以,索引的存在會導致數據寫入的速度降低。實際上,不光寫入數據會變慢,刪除數據也會變慢。這是為什么呢?

我們在刪除某個數據時,也要對應的更新索引節點。這個處理思路有點類似跳表中刪除數據的處理思路。頻繁的數據刪除,就會導致某個子節點中,子節點的個數變得非常少,長此以往,如果每個節點的子節點都比較少,勢必會影響索引的效率。

我們可以設置一個閾值。在 B+ 樹中,閾值等于 m/2。如果某個節點的子節點個數小于 m/2,我們就將他跟相鄰的兄弟節點合并。不過,合并之后的節點個數有可能超過 m。針對這種情況,我們可以借助插入數據時候的處理方法,再分裂節點。

文字描述不是很直觀,我舉了一個刪除操作的例子,你對比看下(圖中的 B+ 樹是一個五叉樹。我們限定葉子節點中,數據的個數少于 2 個就合并節點;非葉子節點中,子節點的個數少于 3 個就合并節點)。

在這里插入圖片描述

數據庫索引以及 B+ 樹的由來,至此就講完了。你有沒有發現,B+ 樹的結構和操作,跟跳表非常類似。理論上將,對跳表稍加改造,也可以替換 B+ 樹,作為數據的索引實現。

B+ 樹發明與 1972 年,跳表發明與 1989 年,我們可以大膽猜想下,跳表的作者可能就是受到了 B+ 樹的啟發,才發明出跳表來的。不過,這個也無從考證了。

總結

本章,講解了數據庫索引的實現,依賴的底層數據結構,B+ 樹。它通過存儲在磁盤的多叉樹結構,做到了時間、空間的平衡,既保證了執行效率,又節省了內存。

前面的講解中,為一步一步詳細地給你介紹 B+ 樹的由來,內容看起來比較零散。為了方便你掌握和記憶,這里再總結一下 B+ 樹的特點:

  • 每個葉子節點中子節點的個數不能超過 m,也不能小于 m/2。
  • 根節點的子節點個數可以不超過 m/2,這是一個例外。
  • m 叉樹只存儲索引,并不真正存儲數據,這個有點而類似跳表。
  • 通過鏈表將葉子節點串聯在一起,這樣可以方便按區間查找。
  • 一般情況,根節點會被存儲在內存中,其他節點存儲在磁盤中。

除了 B+ 樹,你可能還聽說過 B 樹、B- 樹,這里簡單提一下。實際上 B- 樹就是 B 樹,英文翻譯為 B-Tree,這里的 “-” 并不是相對 B+ 樹中的 “+”,而只是一個連接符。這個很容易誤解,所以我強調下。

而 B 樹實際上是低級版的 B+ 樹,或者說 B+ 樹是 B 樹的改進版。B 樹跟 B+ 樹的不同點主要集中在這幾個地方:

  • B+ 樹中的節點不存儲數據,只存儲索引,而 B 樹中的節點存儲數據;
  • B 樹中的葉子節點并不需要鏈表來串聯。

也就是說,B 樹只是一個每個葉子節點個數不能小于 m/2 的 m 叉樹。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/36701.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/36701.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/36701.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

01.Ambari自定義服務開發-項目初始化

文章目錄 基礎環境在PyCharm中初始化項目配置項目相關依賴在PyCharm中導入依賴 基礎環境 PyCharmPython 2.7已經安裝完成的Ambari服務端 在PyCharm中初始化項目 項目名稱就是我們要安裝服務的名稱&#xff0c;要求名稱為全大寫&#xff0c;如&#xff1a;DORIS創建Python2.7…

如何實現機房的自動化運維-青島佰優聯

要讓機房更穩定地實現自動化運維&#xff0c;可以參考以下幾點建議&#xff1a; 一、實施自動化運維工具和技術 1. 配置管理工具&#xff1a; - 使用如Ansible、Puppet、Chef等開源的自動化運維工具&#xff0c;進行服務器配置的管理。這些工具可以幫助管理員快速部署、更…

龍迅LT8711V TYPE-CDP 1.2轉VGA芯片,內置MCU,成熟批量產品

龍迅LT8711V描述&#xff1a; LT8711V是一種高性能的Type-C/DP1.2到VGA轉換器&#xff0c;設計用于連接USB Type-C源或DP1.2源到VGA接收器。LT8711V集成了一個DP1.2兼容的接收器&#xff0c;和一個高速三通道視頻DAC。此外&#xff0c;還包括兩個CC控制器&#xff0c;用于CC通…

XML selectNodes 模糊查找

public static XmlElement[] FuzzyFindNode(string xmlPath, string key, string valuenull){XmlDocument xmlDoc new XmlDocument();xmlDoc.Load(xmlPath); string xpath $"//節點名字[contains({key},{value})]"; XmlNodeList nodes xmlDoc.SelectNodes(xpath)…

圖像大小調整(縮放)

尺寸調整前尺寸調整前 1、背景介紹 在深度學習中&#xff0c;將圖像調整到固定尺寸&#xff08;如28x28像素&#xff09;的操作是非常常見的&#xff0c;尤其是在處理諸如圖像分類、物體檢測和圖像分割等任務時。這種操作有幾個重要原因&#xff1a; 標準化輸入&#xff1a;許…

【可控圖像生成系列論文(五)】ControlNet 和 IP-Adapter 之間的區別有哪些?

系列文章目錄 【可控圖像生成系列論文&#xff08;一&#xff09;】 簡要介紹了 MimicBrush 的整體流程和方法&#xff1b;【可控圖像生成系列論文&#xff08;二&#xff09;】 就MimicBrush 的具體模型結構、訓練數據和紋理遷移進行了更詳細的介紹。【可控圖像生成系列論文&…

【漏洞復現】銳捷統一上網行為管理與審計系統——遠程命令執行漏洞

聲明&#xff1a;本文檔或演示材料僅供教育和教學目的使用&#xff0c;任何個人或組織使用本文檔中的信息進行非法活動&#xff0c;均與本文檔的作者或發布者無關。 文章目錄 漏洞描述漏洞復現測試工具 漏洞描述 銳捷統一上網行為管理與審計系統naborTable/static_convert.php…

如何應對瀏覽器提示的“存在不安全腳本”問題

哈嘍呀&#xff0c;大家好&#xff0c;淼淼又來和大家見面啦&#xff0c;咱們在互聯網瀏覽過程中&#xff0c;您或許會遇到瀏覽器彈出的安全警告&#xff0c;提示頁面中包含“不安全腳本”。這樣的信息往往讓人心生警惕&#xff0c;擔心自己的隱私和數據安全受到威脅。本文將為…

Linux系統編程(七)進程間通信IPC

進程間通訊的7種方式_進程間通信的幾種方法-CSDN博客 管道 pipe&#xff08;命名管道和匿名管道&#xff09;&#xff1b;信號 signal&#xff1b;共享內存&#xff1b;消息隊列&#xff1b;信號量 semaphore&#xff1b;套接字 socket&#xff1b; 1. 管道 內核提供&#x…

Hive基礎知識(二十三):數據傾斜優化

絕大部分任務都很快完成&#xff0c;只有一個或者少數幾個任務執行的很慢甚至最終執行失敗&#xff0c; 這樣的現象為數據傾斜現象。 一定要和數據過量導致的現象區分開&#xff0c;數據過量的表現為所有任務都執行的很慢&#xff0c;這個 時候只有提高執行資源才可以優化 HQL…

Arduino平臺軟硬件原理及使用——SR04超聲波傳感器的使用

文章目錄&#xff1a; 一、超聲波傳感器工作原理 二、SR04超聲波庫的使用 三、SR04超聲波傳感器在Arduino中的使用 一、超聲波傳感器工作原理 如上圖所示&#xff1a;HCSR04超聲波傳感器擁有4個針腳&#xff0c;除了VCC接正極、GND接負極外&#xff0c;還有兩個引腳“Trig”及“…

Linux線程互斥鎖

目錄 &#x1f6a9;看現象&#xff0c;說原因 &#x1f6a9;解決方案 &#x1f6a9;互斥鎖 &#x1f680;關于互斥鎖的理解 &#x1f680;關于原子性的理解 &#x1f680;如何理解加鎖和解鎖是原子的 &#x1f6a9;對互斥鎖的簡單封裝 引言 大家有任何疑問&#xff0c;可…

【Android面試八股文】如何實現Activity窗口快速變暗

文章目錄 方式一:修改 WindowManager.LayoutParams 的screenBrightness屬性動態調整窗口的亮度方式二:使用 `WindowManager.LayoutParams` 的 `alpha` 屬性結合 `ValueAnimator` 來實現窗口漸變變暗的效果方式三:使用遮罩層在Android中實現Activity窗口快速變暗有幾種方法,…

CCSP自考攻略+經驗總結

備考攻略 備考攻略準備階段通讀階段精度階段總復習階段刷題階段命運審判 寫到最后 備考攻略 趁著對ssp知識點的理解還在&#xff0c;開始ccsp的考證之路&#xff0c;文章結構還是按照cissp備考篇的結構梳理。本次備考和cissp的離職在家備考不同&#xff0c;ccsp是在職利用非工…

如何用亞馬遜合作伙伴網絡快速上線跨境電商

目前跨境電商已成為行業發展主流&#xff0c;如何快速、低成本打造品牌海外獨立站和智能客服營銷中心、構建全鏈路跨境電商體系是出海電商商家都會遇到的難題。亞馬遜云科技憑借與亞馬遜電商平臺易于集成的先天優勢成為首選的電商解決方案平臺。本文介紹了如何用亞馬遜云科技平…

Elasticsearch8.x聚合查詢全面指南:從理論到實戰

聚合查詢的概念 聚合查詢&#xff08;Aggregation Queries&#xff09;是Elasticsearch中用于數據匯總和分析的查詢類型。它不同于普通的查詢&#xff0c;而是用于執行各種聚合操作&#xff0c;如計數、求和、平均值、最小值、最大值、分組等。 聚合查詢的分類 分桶聚合&…

centos7 安裝單機MongoDB

centos7安裝單機 yum 安裝 1、配置yum源 vim /etc/yum.repos.d/mongodb.repo [mongodb-org-7.0] nameMongoDB Repository baseurlhttps://repo.mongodb.org/yum/redhat/$releasever/mongodb-org/7.0/x86_64/ gpgcheck1 enabled1 gpgkeyhttps://www.mongodb.org/static/pgp…

【監控】3.配置 Grafana 以使用 Prometheus 數據源

1 訪問 Grafana 打開瀏覽器&#xff0c;訪問 http://localhost:3000&#xff08;默認端口&#xff09;。使用默認的用戶名和密碼 admin/admin 登錄。 2 添加 Prometheus 數據源 進入 Grafana 儀表板&#xff0c;點擊左側菜單中的“Configuration” -> “Data Sources”。…

未來已來,如何打造智慧養殖場?

近年來&#xff0c;國家出臺了一系列扶持政策&#xff0c;以促進養殖行業高質量發展&#xff0c;推動行業轉型升級。在國家政策和市場需求的雙重驅動下&#xff0c;養殖行業正迎來前所未有的發展機遇。智慧養殖以其高效、智能和可持續的特點&#xff0c;正逐步取代傳統養殖方式…

6.26.4.1 基于交叉視角變換的未配準醫學圖像多視角分析

1. 介紹 許多醫學成像任務使用來自多個視圖或模式的數據&#xff0c;但很難有效地將這些數據結合起來。雖然多模態圖像通常可以在神經網絡中作為多個輸入通道進行配準和處理&#xff0c;但來自不同視圖的圖像可能難以正確配準(例如&#xff0c;[2])。因此&#xff0c;大多數多視…