首頁>技術>

1:預寫日誌,定期刷髒,防止資料丟失,redo log

InnoDB作為MySQL的儲存引擎,資料是存放在磁碟中的,但如果每次讀寫資料都需要磁碟IO,效率會很低。為此,InnoDB提供了快取(Buffer Pool),Buffer Pool中包含了磁碟中部分資料頁的對映,作為訪問資料庫的緩衝:當從資料庫讀取資料時,會首先從Buffer Pool中讀取,如果Buffer Pool中沒有,則從磁碟讀取後放入Buffer Pool;當向資料庫寫入資料時,會首先寫入Buffer Pool,Buffer Pool中修改的資料會定期重新整理到磁碟中(這一過程稱為刷髒)。Buffer Pool的使用大大提高了讀寫資料的效率,但是也帶了新的問題:如果MySQL宕機,而此時Buffer Pool中修改的資料還沒有重新整理到磁碟,就會導致資料的丟失,事務的永續性無法保證。於是,redo log被引入來解決這個問題:當資料修改時,除了修改Buffer Pool中的資料,還會在redo log記錄這次操作;當事務提交時,會呼叫fsync介面對redo log進行刷盤。如果MySQL宕機,重啟時可以讀取redo log中的資料,對資料庫進行恢復。redo log採用的是WAL(Write-ahead logging,預寫式日誌),所有修改先寫入日誌,再更新到Buffer Pool,保證了資料不會因MySQL宕機而丟失,從而滿足了永續性要求。

既然redo log也需要在事務提交時將日誌寫入磁碟,為什麼它比直接將Buffer Pool中修改的資料寫入磁碟(即刷髒)要快呢?主要有以下兩方面的原因:

(1)刷髒是隨機IO,因為每次修改的資料位置隨機,但寫redo log是追加操作,屬於順序IO。

(2)刷髒是以資料頁(Page)為單位的,MySQL預設頁大小是16KB,一個Page上一個小修改都要整頁寫入;而redo log中只包含真正需要寫入的部分,無效IO大大減少。

總之:WAL預寫日誌就是快,要不是能快點誰願意搞得這麼複雜

redo log 主要節省的是隨機寫磁碟的 IO 消耗(轉成順序寫),而 change buffer 主要節省的則是隨機讀磁碟的 IO 消耗。

update t set c=c+1 where id = 2執行流程,崩潰恢復情況

在兩階段提交的不同時刻,MySQL 異常重啟會出現什麼現象。如果在圖中時刻 A 的地方,也就是寫入 redo log 處於 prepare 階段之後、寫 binlog 之前,發生了崩潰(crash),由於此時 binlog 還沒寫,redo log 也還沒提交,所以崩潰恢復的時候,這個事務會回滾。這時候,binlog 還沒寫,所以也不會傳到備庫。到這裡,大家都可以理解。大家出現問題的地方,主要集中在時刻 B,也就是 binlog 寫完,redo log 還沒 commit 前發生 crash,那崩潰恢復的時候 MySQL 會怎麼處理?我們先來看一下崩潰恢復時的判斷規則。如果 redo log 裡面的事務是完整的,也就是已經有了 commit 標識,則直接提交;如果 redo log 裡面的事務只有完整的 prepare,則判斷對應的事務 binlog 是否存在並完整:a. 如果是,則提交事務;b. 否則,回滾事務。

2:髒讀、不可重複讀和幻讀

髒讀:A事務讀到B事務還沒提交的修改

不可重複讀:一次事務中的兩次讀的同一條資料內容不同

幻讀:一次事務中的兩次讀資料條數不同,幻讀僅專指“新插入的行”,應修改被第二次查出的資料不屬於幻讀

產生幻讀的原因:行鎖只能鎖住行,但是新插入記錄這個動作,要更新的是記錄之間的“間隙”。因此,為了解決幻讀問題,InnoDB 只好引入新的鎖,也就是間隙鎖 (Gap Lock)。

隔離級別

1:都未提交(read uncommitted),一個事務還沒提交時,它做的變更就能被別的事務看到,直接返回最新的資料,不需要建立檢視

2:讀提交(read committed),一個事務提交之後,它做的變更才會被其他事務看到,執行每條語句都會建立一個檢視,相當於實時更新

3:可重複讀(repeatable read),一個事務執行過程中看到的資料,總是跟這個事務在啟動時看到的資料是一致的。當然在可重複讀隔離級別下,未提交變更對其他事務也是不可見的,在事務啟動的時候,會建立一個檢視,事務執行期間都讀這個檢視

4:序列化(serializable ),顧名思義是對於同一行記錄,“寫”會加“寫鎖”,“讀”會加“讀鎖”。當出現讀寫鎖衝突的時候,後訪問的事務必須等前一個事務執行完成,才能繼續執行,加鎖保證了事務執行期間資料不被修改,不需要建立檢視

3:磁碟IO與閱讀

前面提到了訪問磁碟,那麼這裡先簡單介紹一下磁碟IO和預讀,磁碟讀取資料靠的是機械運動,每次讀取資料花費的時間可以分為尋道時間、旋轉延遲、傳輸時間三個部分,尋道時間指的是磁臂移動到指定磁軌所需要的時間,主流磁碟一般在5ms以下;旋轉延遲就是我們經常聽說的磁碟轉速,比如一個磁碟7200轉,表示每分鐘能轉7200次,也就是說1秒鐘能轉120次,旋轉延遲就是1/120/2 = 4.17ms;傳輸時間指的是從磁碟讀出或將資料寫入磁碟的時間,一般在零點幾毫秒,相對於前兩個時間可以忽略不計。那麼訪問一次磁碟的時間,即一次磁碟IO的時間約等於5+4.17 = 9ms左右,聽起來還挺不錯的,但要知道一臺500 -MIPS的機器每秒可以執行5億條指令,因為指令依靠的是電的性質,換句話說執行一次IO的時間可以執行40萬條指令,資料庫動輒十萬百萬乃至千萬級資料,每次9毫秒的時間,顯然是個災難。下圖是計算機硬體延遲的對比圖,供大家參考:

4:髒頁

當記憶體資料頁跟磁碟資料頁內容不一致的時候,我們稱這個記憶體頁為“髒頁”。記憶體資料寫入到磁碟後,記憶體和磁碟上的資料頁的內容就一致了,稱為“乾淨頁”。

MySQL 偶爾“抖”一下的那個瞬間,可能就是在刷髒頁(flush)

5:為什麼表資料刪掉一半,表文件大小不變?

delete 命令其實只是把記錄的位置,或者資料頁標記為了“可複用”,但磁碟檔案的大小是不會變的。也就是說,透過 delete 命令是不能回收表空間的。這些可以複用,而沒有被使用的空間,看起來就像是“空洞”。實際上,不止是刪除資料會造成空洞,插入資料也會。

重建表:alter table A engine=InnoDB

在這個過程中,有新的資料要寫入到表 A 的話,就會造成資料丟失。因此,在整個 DDL 過程中,表 A 中不能有更新。也就是說,這個 DDL 不是 Online 的。

6:count(*)具體操作

InnoDB 是索引組織表,主鍵索引樹的葉子節點是資料,而普通索引樹的葉子節點是主鍵值。所以,普通索引樹比主鍵索引樹小很多。對於 count(*) 這樣的操作,遍歷哪個索引樹得到的結果邏輯上都是一樣的。因此,MySQL 最佳化器會找到最小的那棵樹來遍歷。在保證邏輯正確的前提下,儘量減少掃描的資料量,是資料庫系統設計的通用法則之一。

count(*)、count(主鍵 id)、count(欄位) 和 count(1) 等不同用法的效能對比:

count(主鍵 id):InnoDB 引擎會遍歷整張表,把每一行的 id 值都取出來,返回給 server 層。server 層拿到 id 後,判斷是不可能為空的,就按行累加。

count(1) :InnoDB 引擎遍歷整張表,但不取值。server 層對於返回的每一行,放一個數字“1”進去,判斷是不可能為空的,按行累加。單看這兩個用法的差別的話,你能對比出來,count(1) 執行得要比 count(主鍵 id) 快。因為從引擎返回 id 會涉及到解析資料行,以及複製欄位值的操作。

count(欄位):如果這個“欄位”是定義為 not null 的話,一行行地從記錄裡面讀出這個欄位,判斷不能為 null,按行累加;如果這個“欄位”定義允許為 null,那麼執行的時候,判斷到有可能是 null,還要把值取出來再判斷一下,不是 null 才累加。也就是前面的第一條原則,server 層要什麼欄位,InnoDB 就返回什麼欄位。

count(*):並不會把全部欄位取出來,而是專門做了最佳化,不取值。count(*) 肯定不是 null,按行累加。

count(欄位) < count(主鍵 id) < count(1) ≈ count(*),所以我建議你,儘量使用 count(*)。

7:order by

全欄位排序 VS rowid排序

全欄位排序:將所有需要的欄位都放到記憶體中

rowid排序:只需要把主鍵id和排序的欄位存入記憶體中,然後按照主鍵id回表查出需要的欄位

如果 MySQL 實在是擔心排序記憶體太小,會影響排序效率,才會採用 rowid排序演算法,這樣排序過程中一次可以排序更多行,但是需要再回到原表去取資料。如果 MySQL 認為記憶體足夠大,會優先選擇全欄位排序,把需要的欄位都放到 sort_buffer 中,這樣排序後就會直接從記憶體裡面返回查詢結果了,不用再回到原表去取資料。這也就體現了 MySQL 的一個設計思想:如果記憶體夠,就要多利用記憶體,儘量減少磁碟訪問。

如何正確顯示隨機資料?

1:先查出總行數,透過隨機函式得到一個值

2:select * from table limit offset,1

8:字串和數字做比較的話,是將字串轉換成數字。

//tradeid 型別為varchar(40),並建有索引

mysql> select * from tradelog where tradeid=110717; //不會使用索引

mysql> select * from tradelog where tradeid=“110717"; //使用索引

基於字串轉數字的規則,tradeid會被轉換成數字,轉換後的sql為:

mysql> select * from tradelog where CAST(tradid AS signed int) = 110717;

9:保證資料不丟失

mysql會分兩步提交,先寫redo log,在寫binlog,第一步只是呼叫檔案write函式,將資料寫入page cache中,第二步呼叫檔案fsync函式將page cache中的資料寫入磁碟,透過技術手動將redo log和binlog一起寫入檔案,以提高效能,當檔案落盤之後再將事務狀態更新為成功

10:自增主鍵為什麼不是連續的

insert into t values(null, 1, 1);

這個語句的執行流程就是:

1:執行器呼叫 InnoDB 引擎介面寫入一行,傳入的這一行的值是 (0,1,1);

2:InnoDB 發現使用者沒有指定自增 id 的值,獲取表 t 當前的自增值 2;

3:將傳入的行的值改成 (2,1,1);

4:將表的自增值改成 3;

5:繼續執行插入資料操作,由於已經存在 c=1 的記錄,所以報 Duplicate key error,語句返回。

既然已經回滾了,為什麼不把自增的值改回去呢?因為實在是不知道回滾啊!

如有多個事務同時插入資料,有的成功有的失敗,回滾到哪個值

1:唯一鍵衝突是導致自增主鍵 id 不連續的第一種原因,插入的時候指定了主鍵id,且沒有衝突,插入成功後,可能照成空洞

2:事務回滾也會產生類似的現象,這就是第二種原因

11:mysql取資料和發資料給client的流程

1:獲取一行,寫到 net_buffer 中。這塊記憶體的大小是由引數 net_buffer_length 定義的,預設是 16k。

2:重複獲取行,直到 net_buffer 寫滿,呼叫網路介面發出去。

3:如果傳送成功,就清空 net_buffer,然後繼續取下一行,並寫入 net_buffer。

4:如果傳送函式返回 EAGAIN 或 WSAEWOULDBLOCK,就表示本地網路棧(socket send buffer)寫滿了,進入等待。直到網路棧重新可寫,再繼續傳送。

12:字首索引

只要滿足最左字首,就可以利用索引來加速檢索。這個最左字首可以是聯合索引的最左 N 個欄位,也可以是字串索引的最左 M 個字元

13:鎖

1:全域性鎖,2:表鎖(表鎖,元資料鎖meta data lock,MDL),3:行鎖

當對一個表做增刪改查操作的時候,加 MDL 讀鎖;當要對錶做結構變更操作的時候,加 MDL 寫鎖

讀鎖之間不互斥,因此你可以有多個執行緒同時對一張表增刪改查。

讀寫鎖之間、寫鎖之間是互斥的,用來保證變更表結構操作的安全性。因此,如果有兩個執行緒要同時給一個表加欄位,其中一個要等另一個執行完才能開始執行。

14:沒命中索引的情況

1:對索引欄位做函式操作,可能會破壞索引值的有序性,因此最佳化器就決定放棄走樹搜尋功能

2:隱式型別轉換,型別是string傳int查詢,型別是int傳string查詢,型別是int傳string沒問題,型別是string就只能傳string,型別保持一致就不用管這些細節

3:隱式字元編碼轉換

15:for update排他鎖

1:for update 僅適用於InnoDB,並且必須開啟事務,在begin與commit之間才生效。

2:當開啟一個事務進行for update的時候,另一個事務也有for update的時候會一直等著,直到第一個事務結束

3:如果查詢條件中有明確的主鍵查詢就是行鎖,如果沒有明確的主鍵查詢就是表鎖

聚集索引的葉子節點直接儲存了資料,也是資料節點,而非聚集索引的葉子節點沒有儲存實際的資料,主鍵索引的id,需要二次查詢。

聚集索引是指索引的邏輯順序與表記錄的物理儲存順序一致的索引,一般情況下主鍵索引就符合這個定義,所以一般來說主鍵索引也是聚集索引

16
最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • Android相容性最佳化ScrollView滾動監聽適配方案