pg事务篇(一)—— 事务与多版本并发控制MVCC
一、 MVCC常用實(shí)現(xiàn)方法
一般MVCC有2種實(shí)現(xiàn)方法:
- 寫新數(shù)據(jù)時(shí),把舊數(shù)據(jù)快照存入其他位置(如oracle的回滾段、sqlserver的tempdb)。當(dāng)讀數(shù)據(jù)時(shí),讀的是快照的舊數(shù)據(jù)。
- 寫新數(shù)據(jù)時(shí),舊數(shù)據(jù)不刪除,直接插入新數(shù)據(jù)。PostgreSQL就是使用的這種實(shí)現(xiàn)方法。
1. PostgreSQL的MVCC實(shí)現(xiàn)方式優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
- 無(wú)論事務(wù)進(jìn)行了多少操作,事務(wù)回滾可以立即完成
- 數(shù)據(jù)可以進(jìn)行很多更新,不必像Oracle和MySQL的Innodb引擎需要保證回滾段不會(huì)被用完,也不會(huì)經(jīng)常遇到“ORA-1555”錯(cuò)誤的困擾
缺點(diǎn)
- 舊版本的數(shù)據(jù)需要清理。當(dāng)然,PostgreSQL 9.x版本中已經(jīng)增加了自動(dòng)清理的輔助進(jìn)程來(lái)定期清理
- 舊版本的數(shù)據(jù)可能會(huì)導(dǎo)致查詢需要掃描的數(shù)據(jù)塊增多,從而導(dǎo)致查詢變慢
2. PostgreSQL中MVCC的實(shí)現(xiàn)思路
為了實(shí)現(xiàn)MVCC機(jī)制,必須要:
- 定義多版本的數(shù)據(jù)——使用元組頭部信息的字段來(lái)標(biāo)示元組的版本號(hào)
- 定義數(shù)據(jù)的有效性、可見性、可更新性——通過(guò)當(dāng)前的事務(wù)快照和對(duì)應(yīng)元組的版本號(hào)判斷
- 實(shí)現(xiàn)不同的數(shù)據(jù)庫(kù)隔離級(jí)別——通過(guò)在不同時(shí)機(jī)獲取快照實(shí)現(xiàn)
下面我們分別來(lái)看,首先了解一些基本概念。
二、 基本概念
1. 事務(wù)標(biāo)識(shí)
當(dāng)事務(wù)開始(執(zhí)行begin第一條命令時(shí)),事務(wù)管理器會(huì)為該事務(wù)分配一個(gè)txid(transaction id)作為唯一標(biāo)識(shí)符。txid是一個(gè)32位無(wú)符號(hào)整數(shù),取值空間大小約42億(2^32-1)。
txid可通過(guò)txid_current()函數(shù)獲取
testdb=# BEGIN; BEGIN testdb=# SELECT txid_current();txid_current --------------100 (1 row)三個(gè)特殊的txid
- 0:InvalidTransactionId,表示無(wú)效的事務(wù)ID
- 1:BootstrapTransactionId,表示系統(tǒng)表初始化時(shí)的事務(wù)ID,比任何普通的事務(wù)ID都舊。
- 2:FrozenTransactionId,凍結(jié)的事務(wù)ID,比任何普通的事務(wù)ID都舊。
- 大于2的事務(wù)ID都是普通的事務(wù)ID。
事務(wù)間的可見性
txid間可以相互比較大小,任何事務(wù)只可見txid<其自身txid的事務(wù)修改結(jié)果。但txid并不是無(wú)限的,當(dāng)42億數(shù)據(jù)用盡之后又應(yīng)該如何判斷可見性?這個(gè)問(wèn)題我們下篇再討論。
2. 元組結(jié)構(gòu)
pg中元組由三部分組成——元組頭結(jié)點(diǎn)、空值位圖、用戶數(shù)據(jù)。
官方文檔中解釋如下?PostgreSQL: Documentation: 9.6: Database Page Layout
| t_xmin | TransactionId | 4 bytes | insert XID stamp |
| t_xmax | TransactionId | 4 bytes | delete XID stamp |
| t_cid | CommandId | 4 bytes | insert and/or delete CID stamp (overlays with t_xvac) |
| t_xvac | TransactionId | 4 bytes | XID for VACUUM operation moving a row version |
| t_ctid | ItemPointerData | 6 bytes | current TID of this or newer row version |
| t_infomask2 | uint16 | 2 bytes | number of attributes, plus various flag bits |
| t_infomask | uint16 | 2 bytes | various flag bits |
| t_hoff | uint8 | 1 byte | offset to user data |
其中與MVCC相關(guān)的重要信息為:
- t_xmin:保存插入該元組的事務(wù)txid(該元組由哪個(gè)事務(wù)插入)
- t_xmax:保存更新或刪除該元組的事務(wù)txid。若該元組尚未被刪除或更新,則t_xmax=0,即invalid
- t_cid:保存命令標(biāo)識(shí)(command id,cid),指在該事務(wù)中,執(zhí)行當(dāng)前命令之前還執(zhí)行過(guò)幾條sql命令(從0開始計(jì)算)
- t_ctid:一個(gè)指針,保存指向自身或新元組的元組的標(biāo)識(shí)符(tid)。
當(dāng)更新該元組時(shí),t_ctid會(huì)指向新版本元組。若元組被更新多次,則該元組會(huì)存在多個(gè)版本,各版本通過(guò)t_cid串聯(lián),形成一個(gè)版本鏈。通過(guò)這個(gè)版本鏈,可以找到最新的版本。t_ctid是一個(gè)二元組(頁(yè)號(hào),頁(yè)內(nèi)偏移量),其中頁(yè)號(hào)從0開始,頁(yè)內(nèi)偏移量從1開始。
pg提供了pageinspect插件,可查看指定表對(duì)應(yīng)的page header內(nèi)容
testdb=# CREATE EXTENSION pageinspect; CREATE EXTENSION testdb=# CREATE TABLE tbl (data text); CREATE TABLE testdb=# INSERT INTO tbl VALUES('A'); INSERT 0 1 testdb=# SELECT lp as tuple, t_xmin, t_xmax, t_field3 as t_cid, t_ctid FROM heap_page_items(get_raw_page('tbl', 0));tuple | t_xmin | t_xmax | t_cid | t_ctid -------+--------+--------+-------+--------1 | 99 | 0 | 0 | (0,1) (1 row)三、 元組的增、刪、改
1. 插入
插入操作最簡(jiǎn)單,直接將新元組插入目標(biāo)表中頁(yè)面即可
插入操作的過(guò)程和結(jié)果分析:
- t_xmin 被設(shè)置為99,表示插入該元組的txid
- t_xmax 被設(shè)置為0,因?yàn)樵撛M還未被更新或刪除過(guò)
- t_cid 被設(shè)置為0,因?yàn)檫@是該事務(wù)的第一條命令
- t_ctid 指向自身,被設(shè)置為(0,1),表示該元組位于0號(hào)page的第1個(gè)位置上
2. 刪除
pg的刪除只是將目標(biāo)元組在邏輯上標(biāo)為刪除(將t_xmax設(shè)為執(zhí)行delete命令的事務(wù)txid),實(shí)際該元組依然存在于數(shù)據(jù)庫(kù)的存儲(chǔ)頁(yè)面,直至該元組被清理進(jìn)程清理掉。
刪除操作的過(guò)程和結(jié)果分析:
- t_xmin 不變,表示插入該元組的txid
- t_xmax 被設(shè)置為111,即刪除該元組的txid
- t_cid 被設(shè)置為0,因?yàn)檫@是該事務(wù)的第一條命令
- t_ctid 指向自身,被設(shè)置為(0,1),表示該元組位于0號(hào)page的第1個(gè)位置上
當(dāng)txid=111的事務(wù)提交時(shí),tuple_1就不再需要了,稱為dead tuple。但是這個(gè)tuple依然殘留在頁(yè)面上, 隨著數(shù)據(jù)庫(kù)的運(yùn)行,這種死元組越來(lái)越多,它們會(huì)在VACUUM時(shí)最終被清理掉。
3. 更新
pg不會(huì)直接修改數(shù)據(jù),而是將目標(biāo)元組標(biāo)記為刪除,并插入一條新元組,同時(shí)修改t_ctid執(zhí)行新版本元組。
更新操作的過(guò)程和結(jié)果分析
首先看第一條update:
Tuple_1
- t_xmin 不變,表示插入該元組的txid
- t_xmax 被設(shè)置為100,即刪除該元組的txid
- t_cid 被設(shè)置為0,因?yàn)檫@是該事務(wù)的第一條命令
- t_ctid 指向新版本元組,被設(shè)置為(0,2),表示新元組位于0號(hào)page的第2個(gè)位置上
Tuple_2
- t_xmin 被設(shè)置為100,表示插入該元組的txid
- t_xmax 被設(shè)置為0,因?yàn)樵撛M還未被更新或刪除過(guò)
- t_cid 被設(shè)置為0,因?yàn)檫@是該事務(wù)的第一條命令(雖然又刪又增,實(shí)際都是一條update操作的)
- t_ctid 指向自身,被設(shè)置為(0,2),表示該元組位于0號(hào)page的第2個(gè)位置上
再看第二條update:
Tuple_2
- t_xmin 不變,表示插入該元組的txid
- t_xmax 被設(shè)置為100,即刪除該元組的txid
- t_cid 被設(shè)置為1,因?yàn)檫@是該事務(wù)的第二條命令
- t_ctid 指向新版本元組,被設(shè)置為(0,3),表示新元組位于0號(hào)page的第3個(gè)位置上
Tuple_3
- t_xmin 被設(shè)置為100,表示插入該元組的txid
- t_xmax 被設(shè)置為0,因?yàn)樵撛M還未被更新或刪除過(guò)
- t_cid 被設(shè)置為1,因?yàn)檫@是該事務(wù)的第二條命令
- t_ctid 指向自身,被設(shè)置為(0,3),表示該元組位于0號(hào)page的第3個(gè)位置上
四、 提交日志
pg在提交日志(commit log,CLOG)中保存事務(wù)的狀態(tài)。
1. 事務(wù)狀態(tài)
pg定義了四種事務(wù)狀態(tài)——IN_PROGRESS, COMMITTED, ABORTED和SUB_COMMITTED,其中SUB_COMMITTED狀態(tài)用于子事務(wù),此處不討論。
#define TRANSACTION_STATUS_IN_PROGRESS 0x00 #define TRANSACTION_STATUS_COMMITTED 0x01 #define TRANSACTION_STATUS_ABORTED 0x02 #define TRANSACTION_STATUS_SUB_COMMITTED 0x03四種事務(wù)狀態(tài)僅需兩個(gè)bit即可記錄。以一個(gè)塊8KB為例,可以存儲(chǔ)8KB*8/2 = 32K個(gè)事務(wù)的狀態(tài)。內(nèi)存中緩存CLOG的buffer 大小為Min(128,Max(4,NBuffers/512))。
2. 工作原理
CLOG在邏輯上是一個(gè)數(shù)組,由共享內(nèi)存中一系列8K頁(yè)面組成。數(shù)組下標(biāo)對(duì)應(yīng)事務(wù)txid,數(shù)組內(nèi)容則為事務(wù)狀態(tài):
- T1時(shí)刻:txid=200事務(wù)提交,對(duì)應(yīng)狀態(tài)從IN_PROGRESS改為COMMITED
- T2時(shí)刻:txid=201事務(wù)回滾,對(duì)應(yīng)狀態(tài)從IN_PROGRESS改為ABORTED
當(dāng)需要獲取事務(wù)狀態(tài)時(shí),pg調(diào)用內(nèi)部函數(shù)讀取CLOG返回所請(qǐng)求事務(wù)狀態(tài),詳情參考下篇——提示位。
3. CLOG的維護(hù)
當(dāng)shutdown pg或Checkpoint運(yùn)行時(shí),CLOG數(shù)據(jù)會(huì)由內(nèi)存寫入pg_clog(pg 10后叫pg_xact)目錄中的文件。這些文件被命名為0000,0001,最大256KB。當(dāng)pg啟動(dòng)時(shí),會(huì)加載這些文件用于初始化CLOG。
CLOG數(shù)據(jù)會(huì)不斷增長(zhǎng),但并非所有數(shù)據(jù)都是必要的,清理過(guò)程也會(huì)定期清理掉不再需要的CLOG頁(yè)面和文件。
參考
《postgresql實(shí)戰(zhàn)》
The Internals of PostgreSQL : Chapter 5 Concurrency Control
PgSQL· 引擎特性 · 多版本并發(fā)控制介紹及實(shí)例分析
PgSQL · 特性分析 · MVCC機(jī)制淺析
PgSQL · 引擎特性 · PostgreSQL Hint Bits 簡(jiǎn)介
PostgreSQL: Documentation: 10: 67.6.?Database Page Layout
總結(jié)
以上是生活随笔為你收集整理的pg事务篇(一)—— 事务与多版本并发控制MVCC的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: harbor管理 helm-charts
- 下一篇: 拼多多API接口的实践案例