本文轉自網際網路
本系列文章将整理到我在GitHub上的《Java面試指南》倉庫,更多精彩内容請到我的倉庫裡檢視
https://github.com/h2pl/Java-Tutorial
喜歡的話麻煩點下Star哈
文章首發于我的個人部落格:
www.how2playlife.com
本文是微信公衆号【Java技術江湖】的《重新學習MySQL資料庫》其中一篇,本文部分内容來源于網絡,為了把本文主題講得清晰透徹,也整合了很多我認為不錯的技術部落格内容,引用其中了一些比較好的部落格文章,如有侵權,請聯系作者。
該系列博文會告訴你如何從入門到進階,從sql基本的使用方法,從MySQL執行引擎再到索引、事務等知識,一步步地學習MySQL相關技術的實作原理,更好地了解如何基于這些知識來優化sql,減少SQL執行時間,通過執行計劃對SQL性能進行分析,再到MySQL的主從複制、主備部署等内容,以便讓你更完整地了解整個MySQL方面的技術體系,形成自己的知識架構。
如果對本系列文章有什麼建議,或者是有什麼疑問的話,也可以關注公衆号【Java技術江湖】聯系作者,歡迎你參與本系列博文的創作和修訂。
<!-- more -->
作為一名開發人員,在日常的工作中會難以避免地接觸到資料庫,無論是基于檔案的 sqlite 還是工程上使用非常廣泛的 MySQL、PostgreSQL,但是一直以來也沒有對資料庫有一個非常清晰并且成體系的認知,是以最近兩個月的時間看了幾本資料庫相關的書籍并且閱讀了 MySQL 的官方文檔,希望對各位了解資料庫的、不了解資料庫的有所幫助。
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZuBnLsF3c512LcxWczlXbvw1cldWYtl2LcV2chJWY0FGRvw1c05WZ052bj9CXyVGdzFWbvwVZ6lHbh5WQvw1czVmblZXYyR0Lc12bj5CduVGdu92YyV2c1JWdoRXan5ydhJ3Lc9CX6MHc0RHaiojIsJye.png)
本文中對于資料庫的介紹以及研究都是在 MySQL 上進行的,如果涉及到了其他資料庫的内容或者實作會在文中單獨指出。
資料庫的定義
很多開發者在最開始時其實都對資料庫有一個比較模糊的認識,覺得資料庫就是一堆資料的集合,但是實際卻比這複雜的多,資料庫領域中有兩個詞非常容易混淆,也就是_資料庫_和執行個體:
- 資料庫:實體操作檔案系統或其他形式檔案類型的集合;
- 執行個體:MySQL 資料庫由背景線程以及一個共享記憶體區組成;
對于資料庫和執行個體的定義都來自于 MySQL 技術内幕:InnoDB 存儲引擎 一書,想要了解 InnoDB 存儲引擎的讀者可以閱讀這本書籍。
資料庫和執行個體
在 MySQL 中,執行個體和資料庫往往都是一一對應的,而我們也無法直接操作資料庫,而是要通過資料庫執行個體來操作資料庫檔案,可以了解為資料庫執行個體是資料庫為上層提供的一個專門用于操作的接口。
在 Unix 上,啟動一個 MySQL 執行個體往往會産生兩個程序,
mysqld
就是真正的資料庫服務守護程序,而
mysqld_safe
是一個用于檢查和設定
mysqld
啟動的控制程式,它負責監控 MySQL 程序的執行,當
mysqld
發生錯誤時,
mysqld_safe
會對其狀态進行檢查并在合适的條件下重新開機。
MySQL 的架構
MySQL 從第一個版本釋出到現在已經有了 20 多年的曆史,在這麼多年的發展和演變中,整個應用的體系結構變得越來越複雜:
最上層用于連接配接、線程處理的部分并不是 MySQL 『發明』的,很多服務都有類似的組成部分;第二層中包含了大多數 MySQL 的核心服務,包括了對 SQL 的解析、分析、優化和緩存等功能,存儲過程、觸發器和視圖都是在這裡實作的;而第三層就是 MySQL 中真正負責資料的存儲和提取的存儲引擎,例如:InnoDB、MyISAM 等,文中對存儲引擎的介紹都是對 InnoDB 實作的分析。
資料的存儲
在整個資料庫體系結構中,我們可以使用不同的存儲引擎來存儲資料,而絕大多數存儲引擎都以二進制的形式存儲資料;這一節會介紹 InnoDB 中對資料是如何存儲的。
在 InnoDB 存儲引擎中,所有的資料都被邏輯地存放在表空間中,表空間(tablespace)是存儲引擎中最高的存儲邏輯機關,在表空間的下面又包括段(segment)、區(extent)、頁(page):
同一個資料庫執行個體的所有表空間都有相同的頁大小;預設情況下,表空間中的頁大小都為 16KB,當然也可以通過改變
innodb_page_size
選項對預設大小進行修改,需要注意的是不同的頁大小最終也會導緻區大小的不同:
從圖中可以看出,在 InnoDB 存儲引擎中,一個區的大小最小為 1MB,頁的數量最少為 64 個。
如何存儲表
MySQL 使用 InnoDB 存儲表時,會将表的定義和資料索引等資訊分開存儲,其中前者存儲在
.frm
檔案中,後者存儲在
.ibd
檔案中,這一節就會對這兩種不同的檔案分别進行介紹。
.frm 檔案
無論在 MySQL 中選擇了哪個存儲引擎,所有的 MySQL 表都會在硬碟上建立一個
.frm
檔案用來描述表的格式或者說定義;
.frm
檔案的格式在不同的平台上都是相同的。
CREATE TABLE test_frm( column1 CHAR(5), column2 INTEGER);
當我們使用上面的代碼建立表時,會在磁盤上的
datadir
檔案夾中生成一個
test_frm.frm
的檔案,這個檔案中就包含了表結構相關的資訊:
MySQL 官方文檔中的 11.1 MySQL .frm File Format 一文對于 .frm
檔案格式中的二進制的内容有着非常詳細的表述,在這裡就不展開介紹了。
.ibd 檔案
InnoDB 中用于存儲資料的檔案總共有兩個部分,一是系統表空間檔案,包括
ibdata1
、
ibdata2
等檔案,其中存儲了 InnoDB 系統資訊和使用者資料庫表資料和索引,是所有表公用的。
當打開
innodb_file_per_table
選項時,
.ibd
檔案就是每一個表獨有的表空間,檔案存儲了目前表的資料和相關的索引資料。
如何存儲記錄
與現有的大多數存儲引擎一樣,InnoDB 使用頁作為磁盤管理的最小機關;資料在 InnoDB 存儲引擎中都是按行存儲的,每個 16KB 大小的頁中可以存放 2-200 行的記錄。
當 InnoDB 存儲資料時,它可以使用不同的行格式進行存儲;MySQL 5.7 版本支援以下格式的行存儲方式:
Antelope 是 InnoDB 最開始支援的檔案格式,它包含兩種行格式 Compact 和 Redundant,它最開始并沒有名字;Antelope 的名字是在新的檔案格式 Barracuda 出現後才起的,Barracuda 的出現引入了兩種新的行格式 Compressed 和 Dynamic;InnoDB 對于檔案格式都會向前相容,而官方文檔中也對之後會出現的新檔案格式預先定義好了名字:Cheetah、Dragon、Elk 等等。
兩種行記錄格式 Compact 和 Redundant 在磁盤上按照以下方式存儲:
Compact 和 Redundant 格式最大的不同就是記錄格式的第一個部分;在 Compact 中,行記錄的第一部分倒序存放了一行資料中列的長度(Length),而 Redundant 中存的是每一列的偏移量(Offset),從總體上上看,Compact 行記錄格式相比 Redundant 格式能夠減少 20% 的存儲空間。
行溢出資料
當 InnoDB 使用 Compact 或者 Redundant 格式存儲極長的 VARCHAR 或者 BLOB 這類大對象時,我們并不會直接将所有的内容都存放在資料頁節點中,而是将行資料中的前 768 個位元組存儲在資料頁中,後面會通過偏移量指向溢出頁。
但是當我們使用新的行記錄格式 Compressed 或者 Dynamic 時都隻會在行記錄中儲存 20 個位元組的指針,實際的資料都會存放在溢出頁面中。
當然在實際存儲中,可能會對不同長度的 TEXT 和 BLOB 列進行優化,不過這就不是本文關注的重點了。
想要了解更多與 InnoDB 存儲引擎中記錄的資料格式的相關資訊,可以閱讀 InnoDB Record Structure
資料頁結構
頁是 InnoDB 存儲引擎管理資料的最小磁盤機關,而 B-Tree 節點就是實際存放表中資料的頁面,我們在這裡将要介紹頁是如何組織和存儲記錄的;首先,一個 InnoDB 頁有以下七個部分:
每一個頁中包含了兩對 header/trailer:内部的 Page Header/Page Directory 關心的是頁的狀态資訊,而 Fil Header/Fil Trailer 關心的是記錄頁的頭資訊。
在頁的頭部和尾部之間就是使用者記錄和空閑空間了,每一個資料頁中都包含 Infimum 和 Supremum 這兩個虛拟的記錄(可以了解為占位符),Infimum 記錄是比該頁中任何主鍵值都要小的值,Supremum 是該頁中的最大值:
User Records 就是整個頁面中真正用于存放行記錄的部分,而 Free Space 就是空餘空間了,它是一個連結清單的資料結構,為了保證插入和删除的效率,整個頁面并不會按照主鍵順序對所有記錄進行排序,它會自動從左側向右尋找空白節點進行插入,行記錄在實體存儲上并不是按照順序的,它們之間的順序是由
next_record
這一指針控制的。
B+ 樹在查找對應的記錄時,并不會直接從樹中找出對應的行記錄,它隻能擷取記錄所在的頁,将整個頁加載到記憶體中,再通過 Page Directory 中存儲的稀疏索引和
n_owned
next_record
屬性取出對應的記錄,不過因為這一操作是在記憶體中進行的,是以通常會忽略這部分查找的耗時。
InnoDB 存儲引擎中對資料的存儲是一個非常複雜的話題,這一節中也隻是對表、行記錄以及頁面的存儲進行一定的分析和介紹,雖然作者相信這部分知識對于大部分開發者已經足夠了,但是想要真正消化這部分内容還需要很多的努力和實踐。
索引
索引是資料庫中非常非常重要的概念,它是存儲引擎能夠快速定位記錄的秘密武器,對于提升資料庫的性能、減輕資料庫伺服器的負擔有着非常重要的作用;索引優化是對查詢性能優化的最有效手段,它能夠輕松地将查詢的性能提高幾個數量級。
索引的資料結構
在上一節中,我們談了行記錄的存儲和頁的存儲,在這裡我們就要從更高的層面看 InnoDB 中對于資料是如何存儲的;InnoDB 存儲引擎在絕大多數情況下使用 B+ 樹建立索引,這是關系型資料庫中查找最為常用和有效的索引,但是 B+ 樹索引并不能找到一個給定鍵對應的具體值,它隻能找到資料行對應的頁,然後正如上一節所提到的,資料庫把整個頁讀入到記憶體中,并在記憶體中查找具體的資料行。
B+ 樹是平衡樹,它查找任意節點所耗費的時間都是完全相同的,比較的次數就是 B+ 樹的高度;在這裡,我們并不會深入分析或者動手實作一個 B+ 樹,隻是對它的特性進行簡單的介紹。
聚集索引和輔助索引
資料庫中的 B+ 樹索引可以分為聚集索引(clustered index)和輔助索引(secondary index),它們之間的最大差別就是,聚集索引中存放着一條行記錄的全部資訊,而輔助索引中隻包含索引列和一個用于查找對應行記錄的『書簽』。
聚集索引
InnoDB 存儲引擎中的表都是使用索引組織的,也就是按照鍵的順序存放;聚集索引就是按照表中主鍵的順序建構一顆 B+ 樹,并在葉節點中存放表中的行記錄資料。
CREATE TABLE users( id INT NOT NULL, first_name VARCHAR(20) NOT NULL, last_name VARCHAR(20) NOT NULL, age INT NOT NULL, PRIMARY KEY(id), KEY(last_name, first_name, age) KEY(first_name));
如果使用上面的 SQL 在資料庫中建立一張表,B+ 樹就會使用
id
作為索引的鍵,并在葉子節點中存儲一條記錄中的所有資訊。
圖中對 B+ 樹的描述與真實情況下 B+ 樹中的資料結構有一些差别,不過這裡想要表達的主要意思是:聚集索引葉節點中儲存的是整條行記錄,而不是其中的一部分。
聚集索引與表的實體存儲方式有着非常密切的關系,所有正常的表應該有且僅有一個聚集索引(絕大多數情況下都是主鍵),表中的所有行記錄資料都是按照聚集索引的順序存放的。
當我們使用聚集索引對表中的資料進行檢索時,可以直接獲得聚集索引所對應的整條行記錄資料所在的頁,不需要進行第二次操作。
輔助索引
資料庫将所有的非聚集索引都劃分為輔助索引,但是這個概念對我們了解輔助索引并沒有什麼幫助;輔助索引也是通過 B+ 樹實作的,但是它的葉節點并不包含行記錄的全部資料,僅包含索引中的所有鍵和一個用于查找對應行記錄的『書簽』,在 InnoDB 中這個書簽就是目前記錄的主鍵。
輔助索引的存在并不會影響聚集索引,因為聚集索引構成的 B+ 樹是資料實際存儲的形式,而輔助索引隻用于加速資料的查找,是以一張表上往往有多個輔助索引以此來提升資料庫的性能。
一張表一定包含一個聚集索引構成的 B+ 樹以及若幹輔助索引的構成的 B+ 樹。
如果在表
users
中存在一個輔助索引
(first_name, age)
,那麼它構成的 B+ 樹大緻就是上圖這樣,按照
(first_name, age)
的字母順序對表中的資料進行排序,當查找到主鍵時,再通過聚集索引擷取到整條行記錄。
上圖展示了一個使用輔助索引查找一條表記錄的過程:通過輔助索引查找到對應的主鍵,最後在聚集索引中使用主鍵擷取對應的行記錄,這也是通常情況下行記錄的查找方式。
索引的設計
索引的設計其實是一個非常重要的内容,同時也是一個非常複雜的内容;索引的設計與建立對于提升資料庫的查詢性能至關重要,不過這不是本文想要介紹的内容,有關索引的設計與優化可以閱讀 資料庫索引設計與優化 一書,書中提供了一種非常科學合理的方法能夠幫助我們在資料庫中建立最适合的索引,當然作者也可能會在之後的文章中對索引的設計進行簡單的介紹和分析。
鎖
我們都知道鎖的種類一般分為樂觀鎖和悲觀鎖兩種,InnoDB 存儲引擎中使用的就是悲觀鎖,而按照鎖的粒度劃分,也可以分成行鎖和表鎖。
并發控制機制
樂觀鎖和悲觀鎖其實都是并發控制的機制,同時它們在原理上就有着本質的差别;
- 樂觀鎖是一種思想,它其實并不是一種真正的『鎖』,它會先嘗試對資源進行修改,在寫回時判斷資源是否進行了改變,如果沒有發生改變就會寫回,否則就會進行重試,在整個的執行過程中其實都沒有對資料庫進行加鎖;
- 悲觀鎖就是一種真正的鎖了,它會在擷取資源前對資源進行加鎖,確定同一時刻隻有有限的線程能夠通路該資源,其他想要嘗試擷取資源的操作都會進入等待狀态,直到該線程完成了對資源的操作并且釋放了鎖後,其他線程才能重新操作資源;
雖然樂觀鎖和悲觀鎖在本質上并不是同一種東西,一個是一種思想,另一個是一種真正的鎖,但是它們都是一種并發控制機制。
樂觀鎖不會存在死鎖的問題,但是由于更新後驗證,是以當沖突頻率和重試成本較高時更推薦使用悲觀鎖,而需要非常高的響應速度并且并發量非常大的時候使用樂觀鎖就能較好的解決問題,在這時使用悲觀鎖就可能出現嚴重的性能問題;在選擇并發控制機制時,需要綜合考慮上面的四個方面(沖突頻率、重試成本、響應速度和并發量)進行選擇。
鎖的種類
對資料的操作其實隻有兩種,也就是讀和寫,而資料庫在實作鎖時,也會對這兩種操作使用不同的鎖;InnoDB 實作了标準的行級鎖,也就是共享鎖(Shared Lock)和互斥鎖(Exclusive Lock);共享鎖和互斥鎖的作用其實非常好了解:
- 共享鎖(讀鎖):允許事務對一條行資料進行讀取;
- 互斥鎖(寫鎖):允許事務對一條行資料進行删除或更新;
而它們的名字也暗示着各自的另外一個特性,共享鎖之間是相容的,而互斥鎖與其他任意鎖都不相容:
稍微對它們的使用進行思考就能想明白它們為什麼要這麼設計,因為共享鎖代表了讀操作、互斥鎖代表了寫操作,是以我們可以在資料庫中并行讀,但是隻能串行寫,隻有這樣才能保證不會發生線程競争,實作線程安全。
鎖的粒度
無論是共享鎖還是互斥鎖其實都隻是對某一個資料行進行加鎖,InnoDB 支援多種粒度的鎖,也就是行鎖和表鎖;為了支援多粒度鎖定,InnoDB 存儲引擎引入了意向鎖(Intention Lock),意向鎖就是一種表級鎖。
與上一節中提到的兩種鎖的種類相似的是,意向鎖也分為兩種:
- 意向共享鎖:事務想要在獲得表中某些記錄的共享鎖,需要在表上先加意向共享鎖;
- 意向互斥鎖:事務想要在獲得表中某些記錄的互斥鎖,需要在表上先加意向互斥鎖;
随着意向鎖的加入,鎖類型之間的相容矩陣也變得愈加複雜:
意向鎖其實不會阻塞全表掃描之外的任何請求,它們的主要目的是為了表示是否有人請求鎖定表中的某一行資料。
有的人可能會對意向鎖的目的并不是完全的了解,我們在這裡可以舉一個例子:如果沒有意向鎖,當已經有人使用行鎖對表中的某一行進行修改時,如果另外一個請求要對全表進行修改,那麼就需要對所有的行是否被鎖定進行掃描,在這種情況下,效率是非常低的;不過,在引入意向鎖之後,當有人使用行鎖對表中的某一行進行修改之前,會先為表添加意向互斥鎖(IX),再為行記錄添加互斥鎖(X),在這時如果有人嘗試對全表進行修改就不需要判斷表中的每一行資料是否被加鎖了,隻需要通過等待意向互斥鎖被釋放就可以了。
鎖的算法
到目前為止已經對 InnoDB 中鎖的粒度有一定的了解,也清楚了在對資料庫進行讀寫時會擷取不同的鎖,在這一小節将介紹鎖是如何添加到對應的資料行上的,我們會分别介紹三種鎖的算法:Record Lock、Gap Lock 和 Next-Key Lock。
Record Lock
記錄鎖(Record Lock)是加到索引記錄上的鎖,假設我們存在下面的一張表
users
:
CREATE TABLE users( id INT NOT NULL AUTO_INCREMENT, last_name VARCHAR(255) NOT NULL, first_name VARCHAR(255), age INT, PRIMARY KEY(id), KEY(last_name), KEY(age));
如果我們使用
id
或者
last_name
作為 SQL 中
WHERE
語句的過濾條件,那麼 InnoDB 就可以通過索引建立的 B+ 樹找到行記錄并添加索引,但是如果使用
first_name
作為過濾條件時,由于 InnoDB 不知道待修改的記錄具體存放的位置,也無法對将要修改哪條記錄提前做出判斷就會鎖定整個表。
Gap Lock
記錄鎖是在存儲引擎中最為常見的鎖,除了記錄鎖之外,InnoDB 中還存在間隙鎖(Gap Lock),間隙鎖是對索引記錄中的一段連續區域的鎖;當使用類似
SELECT * FROM users WHERE id BETWEEN 10 AND 20 FOR UPDATE;
的 SQL 語句時,就會阻止其他事務向表中插入
id = 15
的記錄,因為整個範圍都被間隙鎖鎖定了。
間隙鎖是存儲引擎對于性能和并發做出的權衡,并且隻用于某些事務隔離級别。
雖然間隙鎖中也分為共享鎖和互斥鎖,不過它們之間并不是互斥的,也就是不同的事務可以同時持有一段相同範圍的共享鎖和互斥鎖,它唯一阻止的就是其他事務向這個範圍中添加新的記錄。
Next-Key Lock
Next-Key 鎖相比前兩者就稍微有一些複雜,它是記錄鎖和記錄前的間隙鎖的結合,在
users
表中有以下記錄:
+------|-------------|--------------|-------+| id | last_name | first_name | age ||------|-------------|--------------|-------|| 4 | stark | tony | 21 || 1 | tom | hiddleston | 30 || 3 | morgan | freeman | 40 || 5 | jeff | dean | 50 || 2 | donald | trump | 80 |+------|-------------|--------------|-------+
如果使用 Next-Key 鎖,那麼 Next-Key 鎖就可以在需要的時候鎖定以下的範圍:
(-∞, 21](21, 30](30, 40](40, 50](50, 80](80, ∞)
既然叫 Next-Key 鎖,鎖定的應該是目前值和後面的範圍,但是實際上卻不是,Next-Key 鎖鎖定的是目前值和前面的範圍。
當我們更新一條記錄,比如
SELECT * FROM users WHERE age = 30 FOR UPDATE;
,InnoDB 不僅會在範圍
(21, 30]
上加 Next-Key 鎖,還會在這條記錄後面的範圍
(30, 40]
加間隙鎖,是以插入
(21, 40]
範圍内的記錄都會被鎖定。
Next-Key 鎖的作用其實是為了解決幻讀的問題,我們會在下一節談事務的時候具體介紹。
死鎖的發生
既然 InnoDB 中實作的鎖是悲觀的,那麼不同僚務之間就可能會互相等待對方釋放鎖造成死鎖,最終導緻事務發生錯誤;想要在 MySQL 中制造死鎖的問題其實非常容易:
兩個會話都持有一個鎖,并且嘗試擷取對方的鎖時就會發生死鎖,不過 MySQL 也能在發生死鎖時及時發現問題,并保證其中的一個事務能夠正常工作,這對我們來說也是一個好消息。
事務與隔離級别
在介紹了鎖之後,我們再來談談資料庫中一個非常重要的概念 —— 事務;相信隻要是一個合格的軟體工程師就對事務的特性有所了解,其中被人經常提起的就是事務的原子性,在資料送出工作時,要麼保證所有的修改都能夠送出,要麼就所有的修改全部復原。
但是事務還遵循包括原子性在内的 ACID 四大特性:原子性(Atomicity)、一緻性(Consistency)、隔離性(Isolation)和持久性(Durability);文章不會對這四大特性全部展開進行介紹,相信你能夠通過 Google 和資料庫相關的書籍輕松獲得有關它們的概念,本文最後要介紹的就是事務的四種隔離級别。
幾種隔離級别
事務的隔離性是資料庫處理資料的幾大基礎之一,而隔離級别其實就是提供給使用者用于在性能和可靠性做出選擇和權衡的配置項。
ISO 和 ANIS SQL 标準制定了四種事務隔離級别,而 InnoDB 遵循了 SQL:1992 标準中的四種隔離級别:
READ UNCOMMITED
READ COMMITED
REPEATABLE READ
和
SERIALIZABLE
;每個事務的隔離級别其實都比上一級多解決了一個問題:
-
:使用查詢語句不會加鎖,可能會讀到未送出的行(Dirty Read);RAED UNCOMMITED
-
:隻對記錄加記錄鎖,而不會在記錄之間加間隙鎖,是以允許新的記錄插入到被鎖定記錄的附近,是以再多次使用查詢語句時,可能得到不同的結果(Non-Repeatable Read);READ COMMITED
-
:多次讀取同一範圍的資料會傳回第一次查詢的快照,不會傳回不同的資料行,但是可能發生幻讀(Phantom Read);REPEATABLE READ
-
:InnoDB 隐式地将全部的查詢語句加上共享鎖,解決了幻讀的問題;SERIALIZABLE
MySQL 中預設的事務隔離級别就是
REPEATABLE READ
,但是它通過 Next-Key 鎖也能夠在某種程度上解決幻讀的問題。
接下來,我們将資料庫中建立如下的表并通過個例子來展示在不同的事務隔離級别之下,會發生什麼樣的問題:
CREATE TABLE test( id INT NOT NULL, UNIQUE(id));
髒讀
在一個事務中,讀取了其他事務未送出的資料。
當事務的隔離級别為
READ UNCOMMITED
時,我們在
SESSION 2
中插入的未送出資料在
SESSION 1
中是可以通路的。
不可重複讀
在一個事務中,同一行記錄被通路了兩次卻得到了不同的結果。
READ COMMITED
時,雖然解決了髒讀的問題,但是如果在
SESSION 1
先查詢了一行資料,在這之後
SESSION 2
中修改了同一行資料并且送出了修改,在這時,如果
SESSION 1
中再次使用相同的查詢語句,就會發現兩次查詢的結果不一樣。
不可重複讀的原因就是,在
READ COMMITED
的隔離級别下,存儲引擎不會在查詢記錄時添加行鎖,鎖定
id = 3
這條記錄。
幻讀
在一個事務中,同一個範圍内的記錄被讀取時,其他事務向這個範圍添加了新的記錄。
重新開啟了兩個會話
SESSION 1
SESSION 2
,在
SESSION 1
中我們查詢全表的資訊,沒有得到任何記錄;在
SESSION 2
中向表中插入一條資料并送出;由于
REPEATABLE READ
的原因,再次查詢全表的資料時,我們獲得到的仍然是空集,但是在向表中插入同樣的資料卻出現了錯誤。
這種現象在資料庫中就被稱作幻讀,雖然我們使用查詢語句得到了一個空的集合,但是插入資料時卻得到了錯誤,好像之前的查詢是幻覺一樣。
在标準的事務隔離級别中,幻讀是由更高的隔離級别
SERIALIZABLE
解決的,但是它也可以通過 MySQL 提供的 Next-Key 鎖解決:
REPERATABLE READ
READ UNCOMMITED
其實是沖突的,如果保證了前者就看不到已經送出的事務,如果保證了後者,就會導緻兩次查詢的結果不同,MySQL 為我們提供了一種折中的方式,能夠在
REPERATABLE READ
模式下加鎖通路已經送出的資料,其本身并不能解決幻讀的問題,而是通過文章前面提到的 Next-Key 鎖來解決。
總結
文章中的内容大都來自于 高性能 MySQL、MySQL 技術内幕:InnoDB 存儲引擎、資料庫索引設計與優化 以及 MySQL 的 官方文檔。
由于篇幅所限僅能對資料庫中一些重要内容進行簡單的介紹和總結,文中内容難免有所疏漏,如果對文章内容的有疑問,可以在部落格下面評論留言。
Innodb與Myisam引擎的差別與應用場景
1. 差別:
(1)事務處理:
MyISAM是非事務安全型的,而InnoDB是事務安全型的(支援事務處理等進階處理);
(2)鎖機制不同:
MyISAM是表級鎖,而InnoDB是行級鎖;
(3)select ,update ,insert ,delete 操作:
MyISAM:如果執行大量的SELECT,MyISAM是更好的選擇
InnoDB:如果你的資料執行大量的INSERT或UPDATE,出于性能方面的考慮,應該使用InnoDB表
(4)查詢表的行數不同:
MyISAM:select count() from table,MyISAM隻要簡單的讀出儲存好的行數,注意的是,當count()語句包含 where條件時,兩種表的操作是一樣的
InnoDB : InnoDB 中不儲存表的具體行數,也就是說,執行select count(*) from table時,InnoDB要掃描一遍整個表來計算有多少行
(5)外鍵支援:
mysiam表不支援外鍵,而InnoDB支援
2. 為什麼MyISAM會比Innodb 的查詢速度快。
INNODB在做SELECT的時候,要維護的東西比MYISAM引擎多很多;
1)資料塊,INNODB要緩存,MYISAM隻緩存索引塊, 這中間還有換進換出的減少;
2)innodb尋址要映射到塊,再到行,MYISAM 記錄的直接是檔案的OFFSET,定位比INNODB要快
3)INNODB還需要維護MVCC一緻;雖然你的場景沒有,但他還是需要去檢查和維護
MVCC ( Multi-Version Concurrency Control )多版本并發控制