轉(zhuǎn)載:邏輯日志與物理日志
日志主要分為邏輯日志和物理日志:
- 邏輯日志 Logic log[1];
- 物理日志 Physical log[2];
1. 物理日志與邏輯日志的存儲(chǔ)內(nèi)容
1.1 Physical Logging
物理日志和邏輯日志在存儲(chǔ)內(nèi)容上有很大區(qū)別,存儲(chǔ)內(nèi)容是區(qū)分它們的最重要手段。
物理日志:
- 存儲(chǔ)內(nèi)容:存儲(chǔ)數(shù)據(jù)庫中特定記錄的變更,通常是 page oriented,即描述具體某一個(gè) page 的修改操作;
- 例子:一條更新請(qǐng)求對(duì)應(yīng)的初始值(original value)以及更新值(after value);
邏輯日志:
- 存儲(chǔ)內(nèi)容:存儲(chǔ)事務(wù)中的一個(gè)操作;
- 例子:事務(wù)中的 UPDATE、DELETE 以及 INSERT 操作。
下圖是一種典型的物理日志:

figure1.典型的物理日志
我們可以看到,更新操作作用于 Page42,將字段 “Kemera” 修改為 “camera”。更新操作對(duì)應(yīng)的日志為:
"Page 42:image at 367,2; before:'ke';after:'ca'"
其中:
- Page 42 用于說明更新操作作用的 page;
- 367:用于說明更新操作相對(duì)于 page 的 offset;
- 2:用于說明更新操作的作用長(zhǎng)度,即 length,2 代表僅僅修改了兩個(gè)字符;
- before:‘Ke’:這里表示 undo information,也可以稱為 undo log;
- after:‘ca’:這里表示 redo log information,也可以稱為 redo log;
當(dāng)然,一條物理日志可以有多個(gè)字段的修改,下面是一個(gè)抽象版本:
(Page ID,Record Offset,length,(Filed 1, Value 1) … (Filed i, Value i) … )
注意事項(xiàng):
- 物理日志實(shí)際上以字節(jié)編碼落盤,而不是字符編碼,因此通常肉眼不可見;
- image 的含義通常指代鏡像,但這里不是說對(duì) page 做整個(gè)鏡像,而是對(duì)更改或增量(change/delta)操作做鏡像,before image 代表寫操作作用之前的字段副本,after image 代表寫操作作用之后的字段副本;
可見,physical log 中的一條記錄對(duì)應(yīng)于狀態(tài)機(jī)(state mechine)上某一個(gè) page 上的某些字段做了什么改動(dòng)的落盤。
1.2 Logical Logging
邏輯日志又被稱為 high-level logging,這是相對(duì)于物理日志而言的。
下圖是一個(gè)典型的邏輯日志:

Figure2.典型的邏輯日志
在上圖中,有一張 CameraLingo 表,我們?cè)噲D糾正 itermID 為 0 的拼寫錯(cuò)誤,即將 “Kemera” 修改為 “Cemera”。邏輯日志的格式如下:
CameraLingo:update(0,'Kermera'=>'camera')
邏輯日志被稱為 high level 的原因是其更抽象,其不需要指明更新操作具體作用于哪一塊 page,因此也對(duì)底層少了一些限制。如果利用物理日志進(jìn)行宕機(jī)后的數(shù)據(jù)恢復(fù),那么需要確保 page 不能夠改變,但利用邏輯日志并不在乎底層 page 是否改變。
熟悉 MySQL 的同學(xué)可以發(fā)現(xiàn)邏輯日志與一條 SQL 語句非常類似,事實(shí)上確實(shí)如此,邏輯日志的本質(zhì)就是對(duì)更新語句(update query)本身的落盤。在本節(jié)的例子中,只需要指明在哪一張表上的哪一行,對(duì)哪一些字段進(jìn)行什么修改即可。邏輯日志不用物理上的 page,而用邏輯上的表。
另一方面,得益于 high level 的抽象,一個(gè)邏輯日志可以對(duì)應(yīng)多條物理日志,下面舉一個(gè)例子:
CameraLingo:capitaLetter(term)
上述邏輯日志的語義是對(duì) CameraLingo 表中所有 term 字段對(duì)應(yīng)的屬性首字母大寫,如果表足夠大,此次邏輯日志涉及多個(gè) page 上的修改,因而需要多個(gè)物理日志。
需要指出的是 high level 的數(shù)據(jù)結(jié)構(gòu)抽象不僅僅局限于 table,例如 key-value 也是一個(gè)典型的 high level 數(shù)據(jù)結(jié)構(gòu)。
1.3 Physiological Logging
除了物理日志以及邏輯日志,還有一種日志被稱為 Physiological Logging[3],其試圖同時(shí)獲得物理日志與邏輯日志的優(yōu)勢(shì)。
下圖是一個(gè)典型的 Physiological Logging:

Figure3.典型的 Physiological Logging
Physiological Logging 的格式也可以如下表示:
(Page ID,Record Offset,(Filed 1, Value 1) … (Filed i, Value i) … )
其中,Page ID、Record Offset 的設(shè)計(jì)源于物理日志。(Field1,Value1) 的設(shè)計(jì)來源于邏輯日志。
Physiological Logging 的特點(diǎn)是:
- 與物理日志相同,更新操作相對(duì)于 page 進(jìn)行,每一條日志僅僅涉及一個(gè) page 的修改;
- 與邏輯日志相同:日志內(nèi)容為更新語句(update query)本身,而不是狀態(tài)機(jī)某些字段更新前后的狀態(tài)。
2. 物理日志與邏輯日志的比較
2.1 事務(wù)并發(fā)控制
什么是事務(wù)并發(fā)控制,為什么需要事務(wù)并發(fā)控制?
我們需要使用事務(wù)并發(fā)控制的原因基于以下事實(shí)(以 MySQL 為語境解釋):
- 事務(wù)由 SQL 語句構(gòu)成,每一個(gè) SQL 語句可分解為多個(gè)不可分隔的讀/寫操作;
- 事務(wù)的執(zhí)行實(shí)際上是一連串不可分割讀寫操作的執(zhí)行;
- 事務(wù)調(diào)度器負(fù)責(zé)調(diào)度不可分割讀寫操作的執(zhí)行順序,它們可能來自于不同事務(wù);
- 事務(wù)并發(fā)控制的一個(gè)目標(biāo)就是實(shí)現(xiàn)并行化事務(wù);
邏輯日志很難實(shí)現(xiàn)一致的事務(wù)并發(fā)控制。由于邏輯日志難以攜帶并發(fā)執(zhí)行順序的信息,當(dāng)同時(shí)有多個(gè)事務(wù)產(chǎn)生更新操作時(shí),數(shù)據(jù)庫內(nèi)部會(huì)將這些操作調(diào)度為串行化序列執(zhí)行,需要機(jī)制來保障每次回放操作的執(zhí)行順序與調(diào)度產(chǎn)生的順序一致。
另一方面,物理日志本身就是存儲(chǔ)就是基于不可分隔的更新操作,因此其存儲(chǔ)先后順序就代表了執(zhí)行器的調(diào)度順序。而且由于很容易判斷兩個(gè) page 是否是同一個(gè) page,如果不是,完全可以安全并行地并行執(zhí)行。
為了實(shí)現(xiàn)宕機(jī)前后事務(wù)并發(fā)控制的一致性,數(shù)據(jù)庫選擇使用 Physical Logging 作為其 Redo Log。
2.2 冪等性
冪等性在日志上的語義是:無論日志回放多少次,最終得到的結(jié)果保持一致。
物理日志能夠做的冪等性,因?yàn)槠浔举|(zhì)是對(duì)狀態(tài)機(jī)某一個(gè)字段在更新前后狀態(tài)的記錄,無論執(zhí)行多少次,最終得到的狀態(tài)總是相同的。下面是一個(gè)例子:
"Page 42:image at 367,2; before:'ke';after:'ca'"
邏輯日志并不能夠提供冪等性的語義,因?yàn)槟骋粋€(gè)更新操作本身不具備冪等性。例如:
CameraLingo:update(0,age=>age+1)
如果 age 的原值為 0,如果執(zhí)行一次,那么 age 更新為 1。如果執(zhí)行兩次,那么 age 更新為 2。
當(dāng)然,如果更新操作本身是冪等的,邏輯日志也可以是冪等的,例如:
CameraLingo:capitaLetter(term)
上述邏輯日志無論回放多少次(至少一次),最終得到的結(jié)果也就是將首字母大寫。
2.3 數(shù)據(jù)量大小
邏輯也不是一無是處,其在日志數(shù)據(jù)量上占優(yōu)。
來自客戶端的一條更新語句可能會(huì)對(duì)應(yīng)多個(gè) page 上的更新,因此邏輯日志與物理日志在日志數(shù)量上有巨大的區(qū)別。
#查詢語句
CameraLingo:capitaLetter(term)
#物理日志
"Page 42:image at 3,1; before:'k';after:'K'"
"Page 42:image at 22,1; before:'a';after:'A'"
....
"Page 42:image at 442,1; before:'b';after:'B'"
#邏輯日志
CameraLingo:capitaLetter(term)
別小看!日志數(shù)據(jù)量大小是特別重要的特性,其對(duì)以下過程都有影響:
- 磁盤 I/O 吞吐量;
- 落盤文件大小;
- 網(wǎng)絡(luò)帶寬;
這里的重點(diǎn)是網(wǎng)絡(luò)帶寬。分布式系統(tǒng)會(huì)通過 primary 副本向 secondary 副本發(fā)送日志的方式來進(jìn)行分布式事務(wù)的維護(hù),因此使用物理日志進(jìn)行傳播就不合適。例如,MySQL 就選擇邏輯日志進(jìn)行維護(hù)分布式事務(wù)。
2.4 日志重放效率
邏輯日志比物理日志在重放時(shí)有著更低的效率,這主要有兩個(gè)方面的原因:
- 額外的解釋步驟:邏輯日志需要額外地解釋更新語句、額外查找實(shí)際 page 位置;
- 物理日志可以并發(fā)進(jìn)行:當(dāng)系統(tǒng)判斷兩個(gè)物理日志作用于不同的 page 時(shí),就可以進(jìn)行完全的并行處理,而邏輯日志通常只能串行執(zhí)行。
2.5 磁盤/內(nèi)存式日志系統(tǒng)的 trade-off
其次,基于磁盤的存儲(chǔ)系統(tǒng)與基于內(nèi)存的存儲(chǔ)系統(tǒng)對(duì)邏輯日志以及物理日志有著不同的 trade-off(權(quán)衡),如下面兩張圖所示。


3. 工業(yè)實(shí)踐與典型案例
3.1 MySQL
MysQL 對(duì)存儲(chǔ)引擎層以及 sever 提供了不同的日志方案。以 InnoDB 存儲(chǔ)引擎為例。
(1)server 層的 bin log
MySQL 的 server 層使用 binlog,其屬于邏輯日志[4]。bin log 分為兩種類型:
- 基于語句的日志記錄(Statement-based logging):主要記錄了該 MySQL 執(zhí)行語句(包括 inserts, updates, deletes);
- 基于行的日志記錄(Row-based logging):主要記錄了對(duì)單個(gè)行的修改,其在 MySQL 5.1.5 后引入;
無論是 SQL 更新語句還是行上具體某個(gè)修改,都是邏輯日志,因?yàn)槎紱]有涉及在具體哪一個(gè) page 上進(jìn)行修改。
為此,MySQL 提供了三個(gè)模式進(jìn)行配置:
- Statement:基于語句的 binlog 日志記錄(statement-based replication-SBR);
- Row:基于行的 binlog 日志記錄(row-based replication-RBR);
- Mixed:混合模式,通?;谡Z句,有必要的情況下基于行實(shí)現(xiàn)(mixed-based replication-MBR);
MySQL 從 V5.1.8 開始提供 Mixed 模式,V5.7.7 之前的版本默認(rèn)是Statement 模式,之后默認(rèn)使用 Row 模式, 但是在 8.0 以上版本已經(jīng)默認(rèn)使用 Mixed 模式了。
MySQL 在 [5] 中指出了上述兩種邏輯日志的優(yōu)缺點(diǎn)。
基于語句的 binlog:
- 優(yōu)點(diǎn):
- 技術(shù)成熟;
- 數(shù)據(jù)更少,即使一個(gè)更新操作會(huì)影響非常多的行。
- 包括任何更新語句,因此可以用于數(shù)據(jù)庫維護(hù)人員維護(hù)數(shù)據(jù)庫;
- 缺點(diǎn):并非所有 SQL 語句的執(zhí)行效果都支持基于語句的復(fù)制。例如調(diào)動(dòng)一個(gè)函數(shù) now() 來獲取系統(tǒng)時(shí)間,在不同的機(jī)器、時(shí)間上重放日志將得到不同的結(jié)果;
基于行的 binlog:
-
優(yōu)點(diǎn):
- 所有更改都可以復(fù)制,包括上面提到的 now() 函數(shù);
- 以下類型的語句所需行鎖更少,可以實(shí)現(xiàn)更好的并發(fā)性:
INSERT ... SELECT-
INSERTstatements withAUTO_INCREMENT -
UPDATEorDELETEstatements withWHEREclauses that do not use keys or do not change most of the examined rows.
-
缺點(diǎn):
- 數(shù)據(jù)量相比基于語句的 binlog 要大很多,在備份與恢復(fù)上性能較差;
- 丟失了原有 SQL 更新語句,不利于復(fù)盤;
(2)數(shù)據(jù)引擎層的 redo log/undo log
MySQL 的 InnoDB 存儲(chǔ)引擎使用 redo log 以及 undo log,它們屬于 Physiological Logging,雖然很多人認(rèn)為其屬于物理日志。
MySQL InnoDB 的 redo log 可以分為三種類型:作用于Page,作用于 Space 以及提供額外信息的 Logic 類型。
這里以作用于 Page 的 redo log 的格式為例:

可見,MySQL 的 redo log 屬于 Physiological Logging:
- 物理上,其使用 Page Number + Record Offset 來指定具體作用于哪一個(gè) page 上的一條記錄。
- 邏輯上,使用 Field Number(Field編號(hào))來說明更新操作作用于哪些字段;
3.2 Redis
Redis 是一個(gè)內(nèi)存型 key-value NoSQL 數(shù)據(jù)庫的典型代表。
Redis 基于 AOF(Append Only File)提供持久化機(jī)制[7]。
AOF 中的每一條日志代表 Redis 節(jié)點(diǎn)接收到的一條寫操作,其格式為 Redis 命令格式,因此 AOF 屬于邏輯日志。
Redis 基于內(nèi)存快照(RDB)+check point + AOF 的方式實(shí)現(xiàn)持久化。
3.3 Kafka
在關(guān)系型數(shù)據(jù)庫看來,日志不是數(shù)據(jù)本身,例如對(duì)于 MySQL 的 InnoDB 存儲(chǔ)引擎來說,數(shù)據(jù)本身是存儲(chǔ)于磁盤上的 B+Tree 樹,日志是用于確保單機(jī)事務(wù)以及分布式事務(wù)的一種手段。換言之,關(guān)系型數(shù)據(jù)庫提供的讀/查詢 API 不是直接讀日志,而是直接讀 B+Tree。
但是對(duì)于 Kakfa 而言,日志本身就是數(shù)據(jù)本身。因此就沒有必要將這類數(shù)據(jù)庫分為邏輯日志與物理日志。Kakfa 的日志一方面服務(wù)于單機(jī)事務(wù)與分布式事務(wù),另一方面服務(wù)于消息的讀/寫 API。
根據(jù)[8],我們可知,Kakfa 消息包括如下重要字段:

可見,Kafka 在 message-logging 中的一條消息并不是用于描述更新,其就是數(shù)據(jù)本身。
總之,當(dāng)日志本身就是數(shù)據(jù)而不是描述更新操作時(shí),不需要將日志區(qū)分為邏輯日志與物理日志。
4. 總結(jié)
4.1 概述
(1)物理日志
格式:記錄在某個(gè)頁面的某個(gè)偏移量初修改了幾個(gè)字節(jié)的值,具體修改的內(nèi)容
優(yōu)點(diǎn):
- 日志是冪等的,重復(fù)執(zhí)行改日志不會(huì)導(dǎo)致數(shù)據(jù)發(fā)生不一致的問題;
- 可用于實(shí)現(xiàn)宕機(jī)前后事務(wù)并發(fā)控制的一致性。物理日志本身就是存儲(chǔ)就是基于不可分隔的更新操作,因此其存儲(chǔ)先后順序就代表了執(zhí)行器的調(diào)度順序。而且由于很容易判斷兩個(gè) page 是否是同一個(gè) page,如果不是,完全可以安全并行地并行執(zhí)行。
- 日志重放效率高
缺點(diǎn):日志量大;
(2)邏輯日志
格式:記錄對(duì)于表的操作,類似update語句
優(yōu)點(diǎn):日志量??;
缺點(diǎn):
- 恢復(fù)時(shí)無法保證冪等性;
- 難以保證宕機(jī)前后事務(wù)并發(fā)控制的一致性。,邏輯日志很難實(shí)現(xiàn)一致的事務(wù)并發(fā)控制。由于邏輯日志難以攜帶并發(fā)執(zhí)行順序的信息,當(dāng)同時(shí)有多個(gè)事務(wù)產(chǎn)生更新操作時(shí),數(shù)據(jù)庫內(nèi)部會(huì)將這些操作調(diào)度為串行化序列執(zhí)行,需要機(jī)制來保障每次回放操作的執(zhí)行順序與調(diào)度產(chǎn)生的順序一致。
- 日志重放效率低;
(3)物理邏輯日志
格式:對(duì)應(yīng)頁是物理的,頁內(nèi)部操作是邏輯的。即根據(jù)物理頁進(jìn)行日志記錄,根據(jù)不同的邏輯操作進(jìn)行日志寫入。
需要注意的:日志不是冪等性;
4.2 舉例
來源地址:頁斷裂(partial write)與doublewrite技術(shù)
比如:innodb表T(c1,c2, key key_c1(c1)),插入記錄row1(1,’abc’)
邏輯日志:
<insert OP, T, 1,’abc’>
邏輯物理日志:
因?yàn)楸鞹含有索引key_c1, 一次插入操作至少涉及兩次B樹操作(我感覺是二級(jí)索引也維護(hù)了一個(gè)B+),二次B樹必然涉及至少兩個(gè)物理頁面,因此至少有兩條日志:
<insert OP, page_no_1, log_body>
<insert OP, page_no_2, log_body>
物理日志:
由于一次INSERT操作,物理上來說要修改頁頭信息(如,頁內(nèi)的記錄數(shù)要加1),要修改相鄰記錄里的鏈表指針,要修改Slot屬性等,因此對(duì)應(yīng)邏輯物理日志的每一條日志,都會(huì)有N條物理日志產(chǎn)生。
< group_id,file_id,page_no,offset1, value1>
< group_id,file_id,page_no,offset2, value2>
……
< group_id,file_id,page_no,offsetN, valueN>
因此對(duì)于上述一個(gè)INSERT操作,會(huì)產(chǎn)生一條邏輯日志,二條邏輯物理日志,2*N條物理日志。從上面簡(jiǎn)單的分析可以看出,邏輯日志的日志量最小,而物理日志的日志量最大;物理日志是純物理的;而邏輯物理日志則頁間物理,頁內(nèi)邏輯,所謂physical-to-a-page, logical-within-a-page。
REFERENCE
- [1] What is the logical log?
- [2] Physical logging, checkpoints, and fast recovery
- [3] What to log, Physical, Logical, and Physiological Logging, Trade-Offs…
- [4]MySQL :: MySQL Internals Manual :: 20.1 Binary Log Overview
- [5]MySQL :: MySQL 8.0 Reference Manual :: 17.2.1.1 Advantages and Disadvantages of Statement-Based and Row-Based Replication
- [6]庖丁解InnoDB之REDO LOG
- [7]Redis Persistence – Redis
- [8]A Guide To The Kafka Protocol