MySQL實戰45講 15

来源:https://www.cnblogs.com/ydssx7/archive/2022/07/25/16517601.html
-Advertisement-
Play Games

15 | 答疑文章(一):日誌和索引相關問題 日誌相關 binlog(歸檔日誌)和redo log(重做日誌)配合崩潰恢復,在兩階段提交的不同瞬間,MySQL如果發生異常重啟,是怎麼保證數據完整性的? Q:這個圖不是一個update 語句的執行流程嗎,怎麼還會調用 commit 語句? A: 兩個“ ...


15 | 答疑文章(一):日誌和索引相關問題

日誌相關

binlog(歸檔日誌)和redo log(重做日誌)配合崩潰恢復,在兩階段提交的不同瞬間,MySQL如果發生異常重啟,是怎麼保證數據完整性的?

img

Q:這個圖不是一個update 語句的執行流程嗎,怎麼還會調用 commit 語句?

A:

兩個“commit”的概念

  • “commit 語句”是指 MySQL 語法中,用於提交一個事務的命令。一般跟begin/start transaction 配對使用。

  • 而圖中用到的這個“commit 步驟”,指的是事務提交過程中的一個小步驟,也是最後一步。當這個步驟執行完成後,這個事務就提交完成了。

  • “commit 語句”執行的時候,會包含“commit 步驟”。

而這個例子裡面,沒有顯式地開啟事務,因此這個 update 語句自己就是一個事務,在執行完成後提交事務時,就會用到這個“commit 步驟“

分析在兩階段提交的不同時刻,MySQL 異常重啟會出現什麼現象

如果在圖中時刻 A 的地方,也就是寫入 redo log 處於 prepare 階段之後、寫 binlog 之前,發生了崩潰(crash),由於此時 binlog 還沒寫,redo log 也還沒提交,所以崩潰恢復的時候,這個事務會回滾。這時候,binlog 還沒寫,所以也不會傳到備庫。

在時刻 B,也就是 binlog 寫完,redo log 還沒 commit前發生 crash,那崩潰恢復的時候 MySQL 會怎麼處理?

崩潰恢復時的判斷規則:

  1. 如果 redo log 裡面的事務是完整的,也就是已經有了 commit 標識,則直接提交;
  2. 如果 redo log 裡面的事務只有完整的 prepare,則判斷對應的事務 binlog 是否存在並完整
    a. 如果是,則提交事務;
    b. 否則,回滾事務。

這裡,時刻 B 發生 crash 對應的就是 2(a) 的情況,崩潰恢復過程中事務會被提交。

Q:MySQL 怎麼知道 binlog 是完整的?

A:一個事務的 binlog 是有完整格式的:

  • statement格式的binlog,最後會有COMMIT;
  • row格式的binlog,最後會有一個XID event。

另外,在MySQL 5.6.2版本以後,還引入了binlog-checksum參數,用來驗證binlog內容的正確性。

對於binlog日誌由於磁碟原因,可能會在日誌中間出錯的情況,MySQL可以通過校驗checksum的結果來發現。所以,MySQL還是有辦法驗證事務binlog的完整性的。

Q:redo log 和 binlog 是怎麼關聯起來的?

A:

它們有一個共同的數據欄位,叫 XID。崩潰恢復的時候,會按順序掃描 redo log

  • 如果碰到既有 prepare、又有 commit 的 redo log,就直接提交;
  • 如果碰到只有 parepare、而沒有 commit 的 redo log,就拿著 XID 去 binlog 找對應的事務

Q:處於 prepare 階段的 redo log 加上完整 binlog,重啟就能恢復,MySQL 為什麼要這麼設計?

A:這個問題和數據與備份的一致性有關。在時刻B,也就是 binlog 寫完以後 MySQL 發生崩潰,這時候 binlog 已經寫入了,之後就會被從庫(或者用這個 binlog 恢復出來的庫)使用。所以,在主庫上也要提交這個事務。採用這個策略,主庫和備庫的數據就保證了一致性。

Q:如果這樣的話,為什麼還要兩階段提交呢?乾脆先 redo log 寫完,再寫 binlog。崩潰恢復的時候,必須得兩個日誌都完整才可以。是不是一樣的邏輯?

A:

回答:其實,兩階段提交是經典的分散式系統問題,並不是MySQL獨有的。

如果必須要舉一個場景,來說明這麼做的必要性的話,那就是事務的持久性問題

對於InnoDB引擎來說,如果redo log提交完成了,事務就不能回滾(如果這還允許回滾,就可能覆蓋掉別的事務的更新)。而如果redo log直接提交,然後binlog寫入的時候失敗,InnoDB又回滾不了,數據和binlog日誌又不一致了

兩階段提交就是為了給所有人一個機會,當每個人都說“我ok”的時候,再一起提交

Q :不引入兩個日誌,也就沒有兩階段提交的必要了。只用 binlog 來支持崩潰恢復,又能支持歸檔,不就可以了?

A:意思是只保留 binlog,然後可以把提交流程改成這樣:… -> “數據更新到記憶體” -> “寫 binlog” -> “提交事務”,是不是也可以提供崩潰恢復的能力?

不可以。

歷史原因: InnoDB 並不是 MySQL 的原生存儲引擎。MySQL 的原生引擎是 MyISAM,設計之初就有沒有支持崩潰恢復。InnoDB 在作為 MySQL 的插件加入 MySQL 引擎家族之前,就已經是一個提供了崩潰恢復和事務支持的引擎了。InnoDB 接入了 MySQL 後,發現既然 binlog 沒有崩潰恢復的能力,那就用 InnoDB 原有的 redo log 好了。

實現上的原因:

假設只用binlog

img

這樣的流程下,binlog 還是不能支持崩潰恢復的。binlog 沒有能力恢復“數據頁”

如果在圖中標的位置,也就是 binlog2 寫完了,但是整個事務還沒有 commit 的時候,MySQL 發生了 crash

重啟後,引擎內部事務 2 會回滾,然後應用 binlog2 可以補回來;但是對於事務 1 來說,系統已經認為提交完成了,不會再應用一次 binlog1

但是,InnoDB 引擎使用的是 WAL 技術,執行事務的時候,寫完記憶體和日誌,事務就算完成了如果之後崩潰,要依賴於日誌來恢複數據頁。

也就是說在圖中這個位置發生崩潰的話,事務 1 也是可能丟失了的,而且是數據頁級的丟失。此時,binlog 裡面並沒有記錄數據頁的更新細節,是補不回來的。

如果要說,優化一下 binlog 的內容,讓它來記錄數據頁的更改可以嗎?但,這其實就是又做了一個 redo log 出來。所以,至少現在的 binlog 能力,還不能支持崩潰恢復。

Q:那能不能反過來,只用 redo log,不要 binlog?

A:如果只從崩潰恢復的角度來講是可以的。你可以把 binlog 關掉,這樣就沒有兩階段提交了,但系統依然是 crash-safe 的。

但 binlog 有著 redo log 無法替代的功能。

一個是歸檔。redo log 是迴圈寫,寫到末尾是要回到開頭繼續寫的。這樣歷史日誌沒法保留,redo log 也就起不到歸檔的作用。

一個就是 MySQL 系統依賴於 binlog。binlog 作為 MySQL 一開始就有的功能,被用在了很多地方。其中,MySQL 系統高可用的基礎,就是 binlog 複製。

還有很多公司有異構系統(比如一些數據分析系統),這些系統就靠消費 MySQL 的binlog 來更新自己的數據。關掉 binlog 的話,這些下游系統就沒法輸入了

Q:redo log 一般設置多大?

A:redo log 太小的話,會導致很快就被寫滿,然後不得不強行刷 redo log,這樣WAL 機制的能力就發揮不出來了。直接將 redo log 設置為4 個文件、每個文件 1GB 。

Q:正常運行中的實例,數據寫入後的最終落盤,是從 redo log 更新過來的還是從 buffer pool 更新過來的呢?

A:

實際上,redo log 並沒有記錄數據頁的完整數據,所以它並沒有能力自己去更新磁碟數據頁,也就不存在“數據最終落盤,是由 redo log 更新過去”的情況。

  1. 如果是正常運行的實例的話,數據頁被修改以後,跟磁碟的數據頁不一致,稱為臟頁。最終數據落盤,就是把記憶體中的數據頁寫盤。這個過程,甚至與 redo log 毫無關係。(指落盤的過程)

  2. 在崩潰恢復場景中,InnoDB 如果判斷到一個數據頁可能在崩潰恢復的時候丟失了更新,就會將它讀到記憶體,然後讓 redo log 更新記憶體內容。更新完成後,記憶體頁變成臟頁,就回到了第一種情況的狀態。

Q:redo log buffer 是什麼?是先修改記憶體,還是先寫 redo log文件?

A:

在一個事務的更新過程中,日誌是要寫多次的。比如下麵這個事務:

begin;
insert into t1 ...
insert into t2 ...
commit;

這個事務要往兩個表中插入記錄,插入數據的過程中,生成的日誌都得先保存起來但又不能在還沒 commit 的時候就直接寫到 redo log 文件里

所以,redo log buffer 就是一塊記憶體,用來先存 redo 日誌的。也就是說,在執行第一個insert 的時候,數據的記憶體被修改了,redo log buffer 也寫入了日誌。但是,真正把日誌寫到 redo log 文件(文件名是 ib_logfile+ 數字),是在執行 commit語句的時候做的。

這裡說的是事務執行過程中不會“主動去刷盤”,以減少不必要的 IO 消耗。但是可能會出現“被動寫入磁碟”,比如記憶體不夠、其他事務提交等情況。

業務設計問題

業務上有這樣的需求,A、B 兩個用戶,如果互相關註,則成為好友

設計上是有兩張表,一個是 like 表,一個是 friend 表,like 表有 user_id、liker_id 兩個欄位,我設置為複合唯一索引即 uk_user_id_liker_id。語句執行邏輯是這樣的:

以 A 關註 B 為例:
第一步,先查詢對方有沒有關註自己(B 有沒有關註 A)
select * from like where user_id = B and liker_id = A;

如果有,則成為好友
insert into friend;

沒有,則只是單向關註關係
insert into like;

但是如果 A、B 同時關註對方,會出現不會成為好友的情況因為上面第 1步,雙方都沒關註對方。第 1 步即使使用了排他鎖也不行,因為記錄不存在,行鎖無法生效。請問這種情況,在 MySQL 鎖層面有沒有辦法處理?

即:在併發場景下,同時有兩個人,設置為關註對方,就可能導致無法成功加為朋友關係

模擬出表:

PS:業務根本就是保證“我一定會插入重覆數據,資料庫一定要要有唯一性約束”,這時直接建唯一索引。不用考慮在“業務開發保證不會插入重覆記錄”的情況下,著重要解決性能問題的時候,才建議儘量使用普通索引

CREATE TABLE `like` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NOT NULL,
  `liker_id` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_user_id_liker_id` (`user_id`,`liker_id`)
) ENGINE=InnoDB;

CREATE TABLE `friend` (
  id` int(11) NOT NULL AUTO_INCREMENT,
  `friend_1_id` int(11) NOT NULL,
  `firned_2_id` int(11) NOT NULL,
  UNIQUE KEY `uk_friend` (`friend_1_id`,`firned_2_id`)
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;

img

由於一開始 A 和 B 之間沒有關註關係,所以兩個事務裡面的 select 語句查出來的結果都是空。

因此,session 1 的邏輯就是“既然 B 沒有關註 A,那就只插入一個單向關註關係”。session 2 也同樣是這個邏輯。

這個結果對業務來說就是 bug 了。因為在業務設定裡面,這兩個邏輯都執行完成以後,是應該在 friend 表裡面插入一行記錄的。

如提問裡面說的,“第 1 步即使使用了排他鎖也不行,因為記錄不存在,行鎖無法生效”。不過,使用另外一個方法,來解決這個問題。

首先,要給“like”表增加一個欄位,比如叫作 relation_ship,並設為整型,取值 1、2、3。

值是 1 的時候,表示 user_id 關註 liker_id;
值是 2 的時候,表示 liker_id 關註 user_id;
值是 3 的時候,表示互相關註

然後,當 A 關註 B 的時候,邏輯改成如下所示的樣子:

應用代碼裡面,比較 A 和 B 的大小,如果 A<B,就執行下麵的邏輯:

begin; /* 啟動事務 */
insert into `like`(user_id, liker_id, relation_ship) values(A, B, 1) on duplicate key update relation_ship=relation_ship | 1;
select relation_ship from `like` where user_id=A and liker_id=B;
/* 代碼中判斷返回的 relation_ship,
  如果是 1,事務結束,執行 commit
  如果是 3,則執行下麵這兩個語句:
  */
insert ignore into friend(friend_1_id, friend_2_id) values(A,B);
commit;

如果 A>B,則執行下麵的邏輯

begin; /* 啟動事務 */
insert into `like`(user_id, liker_id, relation_ship) values(B, A, 2) on duplicate key update relation_ship=relation_ship | 2;
select relation_ship from `like` where user_id=B and liker_id=A;
/* 代碼中判斷返回的 relation_ship,
  如果是 2,事務結束,執行 commit
  如果是 3,則執行下麵這兩個語句:
*/
insert ignore into friend(friend_1_id, friend_2_id) values(B,A);
commit;

這個設計里,讓“like”表裡的數據保證 user_id < liker_id,這樣不論是 A 關註 B,還是B 關註 A,在操作“like”表的時候,如果反向的關係已經存在,就會出現行鎖衝突

然後,insert … on duplicate 語句,確保了在事務內部,執行了這個 SQL 語句後,就強行占住了這個行鎖,之後的 select 判斷 relation_ship 這個邏輯時就確保了是在行鎖保護下的讀操作。

操作符 “|” 是按位或,連同最後一句 insert 語句里的 ignore,是為了保證重覆調用時的冪等性。???

這樣,即使在雙方“同時”執行關註操作,最終資料庫里的結果,也是 like 表裡面有一條關於 A 和 B 的記錄,而且 relation_ship 的值是 3, 並且 friend 表裡面也有了 A 和 B 的這條記錄。

Q:???

我們創建了一個簡單的表 t,並插入一行,然後對這一行做修改。

CREATE TABLE `t` (
`id` int(11) NOT NULL primary key auto_increment,
`a` int(11) DEFAULT NULL
) ENGINE=InnoDB;
insert into t values(1,2);

這時候,表 t 里有唯一的一行數據 (1,2)。假設,現在要執行:

update t set a=2 where id=1;

結果:

img

結果顯示,匹配(rows matched)了一行修改(Changed)了0行

僅從現象上看,MySQL內部在處理這個命令的時候,可以有以下三種選擇:

  1. 更新都是先讀後寫的,MySQL 讀出數據,發現 a 的值本來就是 2,不更新,直接返回,執行結束;
  2. MySQL 調用了 InnoDB 引擎提供的“修改為 (1,2)”這個介面,但是引擎發現值與原來相同,不更新,直接返回;
  3. InnoDB 認真執行了“把這個值修改成 (1,2)"這個操作,該加鎖的加鎖,該更新的更新。

你覺得實際情況會是以上哪種呢?你可否用構造實驗的方式,來證明你的結論?進一步地,可以思考一下,MySQL 為什麼要選擇這種策略呢?

A:

答案應該是選項 3,即:InnoDB 認真執行了“把這個值修改成 (1,2)"這個操作,該加鎖的加鎖,該更新的更新。

第一個選項是,MySQL 讀出數據,發現值與原來相同,不更新,直接返回,執行結束。這裡可以用一個鎖實驗來確認。

假設,當前表 t 里的值是 (1,2)。

session B 的 update 語句被 blocked 了,加鎖這個動作是 InnoDB 才能做的,所以排除選項 1。

第二個選項是,MySQL 調用了 InnoDB 引擎提供的介面,但是引擎發現值與原來相同,不更新,直接返回。用一個可見性實驗來確認。

假設當前表裡的值是 (1,2)。

session A 的第二個 select 語句是一致性讀(快照讀),它是不能看見 session B 的更新的。

現在它返回的是 (1,3),表示它看見了某個新的版本,這個版本只能是 session A 自己的 update 語句做更新的時候生成。


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

-Advertisement-
Play Games
更多相關文章
  • 期待已久的 Audition 2022 Mac版已經更新,au用於創建、混合、編輯和恢複音頻內容的多軌、波形和頻譜顯示,最新創建、混合、編輯和恢複音頻內容的多軌、波形和頻譜顯示,這款功能強大的音頻工作站旨在加速視頻製作工作流程和音頻整理,並提供具有原始聲音的優美混音等。 詳情: Audition 2 ...
  • 各位讀者好,新版本的系統平臺低功耗藍牙開發介面庫已發佈,提供BLE設備的枚舉、掃描、連接和檢測,藍牙服務和特征的枚舉、讀寫、訂閱,控制器版本查詢等API函數。支持Windows、Android等系統。鏈接:WCHBleLib_MultiOS.ZIP - 南京沁恆微電子股份有限公司 前言 上一篇文章中 ...
  • 鏡像下載、功能變數名稱解析、時間同步請點擊 阿裡雲開源鏡像站 一、Git LFS Git Large File Storage (LFS) 使用 Git 內部的文本指針替換音頻樣本、視頻、數據集和圖形等大文件,同時將文件內容存儲在 GitHub.com 或 GitHub Enterprise 等遠程伺服器上 ...
  • 一、TTY介紹 當前主要有三種類型tty設備及驅動程式:控制台、pty和串口。其中控制台和pty可看作為虛擬tty設備,物理tty設備包含串口、USB轉串口、SPI轉串口等。 二、TTY設備及驅動信息查看 確定tty設備及驅動程式類型可查閱/proc/tty/drivers文件。如下所示: 文件從左 ...
  • 在嵌入式應用領域中,串口是最為常見的一種硬體通信介面。因為其具備協議簡單,硬體電路精簡等優勢使得串口基本成為MCU、電腦或嵌入式產品的標配介面。本文僅介紹在Linux系統下串口編程需要使用的API和一些應用技巧,關於串口的背景知識介紹,以及Windows系統下串口編程讀者可以移步至其他文章。 Li ...
  • Things3 for Mac是Mac平臺上一款非常優秀的任務管理軟體,嚴格按照GTD流程來規劃人們的任務安排,設計方式也和很多其他的應用程式有很大差別。things3 mac版是一個功能強大,易於使用的任務管理應用程式,可幫助您輸入,組織和處理待辦事項列表中的項目。基於流行的Getting Thi ...
  • 17 | 如何正確地顯示隨機消息? 場景:從一個單詞表中隨機選出三個單詞。 表的建表語句和初始數據的命令如下,在這個表裡面插入了 10000 行記錄: CREATE TABLE `words` ( `id` int(11) NOT NULL AUTO_INCREMENT, `word` varcha ...
  • 16 | “order by”是怎麼工作的? 以市民表為例,假設要查詢城市是“杭州”的所有人名字,並且按照姓名排序返回前 1000 個人的姓名、年齡。 這個表的部分定義: CREATE TABLE `t` ( `id` int(11) NOT NULL, `city` varchar(16) NOT ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...