萬字長文深度解讀亞信安慧AntDB-T資料庫鎖——效能和穩定性的保障

亞信AntDB資料庫發表於2024-01-26

前言

亞信安慧AntDB-T資料庫是一款企業級通用分散式關係型資料庫,而併發控制是資料庫系統中最核心的概念之一,其目的是保證多個併發操作能夠正確地讀取和修改資料庫,AntDB-T資料庫實現併發控制的基本方法是使用鎖來控制臨界區互斥訪問。


在多使用者併發訪問資料庫時,如果沒有合適的鎖機制,可能會導致資料不一致等一系列潛在問題。例如,兩個使用者同時修改同一行資料可能會導致資料衝突或被損壞。如果使用鎖,AntDB-T資料庫可以確保一次只有一個使用者可以修改資料,從而避免上述情況發生。為了確保複雜的事務可以安全地同時執行,AntDB-T提供了各種級別的鎖來控制對各種資料物件的併發訪問,使得對資料庫關鍵部分的更改序列化。


在資料庫管理系統(DBMS)中,鎖是維護資料一致性和完整性的重要工具。AntDB-T資料庫同樣依賴於鎖來確保併發操作的資料一致性和完整性。本文主要闡述AntDB-T資料庫的鎖的分類、常規鎖概念、常規鎖的設計以及應用場景。


AntDB-T資料庫鎖的分類

本部分主要介紹AntDB-T資料庫鎖的分類。AntDB-T資料庫中定義了三種鎖,分別是SpinLock、 LWLock和RegularLock。


SpinLock(自旋鎖

SpinLock是最/底層的鎖, 它分為與機器相關的實現方法和與機器不相關的實現方法。如果機器支援TAS (test-and-set)指令集,那麼AntDB-T資料庫就會採用s_lock.h和s_lock.c中定義的SpinLock實現機制;但是如果機器不支援TAS指令集,那麼不依賴於硬體的SpinLock的實現定義在spin.h和spin.c中,它需要用到AntDB-T資料庫定義的訊號量PGSemaphore。


作為一種最/底層的鎖,一般不直接使用SpinLock,而是利用它來實現其他鎖(例如輕量級鎖LWLock)。毫無疑問,依賴於硬體的SpinLock機制肯定比不依賴於硬體的SpinLock機制速度快,因為不依賴於硬體的SpinLock機制需要使用PG訊號量來模擬SpinLock。


SpinLock的特點是:封鎖時間很短、沒有等待佇列和死鎖檢測機制、事務結束時不會自動釋放SpinLock。


LWLock(輕量級鎖)


LWLock (輕量級鎖)主要提供對共享儲存器的資料結構的互斥訪問,它主要是保護這些共享儲存器中的資料結構。LWLock有兩種鎖模式,一種為排他模式,另一種為共享模式。LWLock 不提供死鎖檢測,但LWLock 管理器在elog恢復期間被自動釋放,所以持有LWLock 的期間呼叫elog發出錯誤訊息不會出現LWLock 未釋放的問題。


LWLock 利用SpinLock實現,當沒有鎖的競爭時可以很快獲得或釋放LWLock。當一個程式阻塞在一個LWLock上時,相當於它阻塞在一個訊號量上,所以不會消耗CPU時間,等待的程式將會以先來後到的順序被授予鎖。


簡單來說, LWLock 的特點是:有等待佇列、無死鎖檢測、能自動釋放鎖。


RegularLock(常規鎖)


RegularLock(常規鎖)指的是一般資料庫事務管理中所指的鎖,也簡稱為Lock。RegularLock(常規鎖)它保護的臨界區是資料庫物件的操作,而不是單純的共享記憶體變數或者某一個原子變數。AntDB-T資料庫中的資料庫物件包括表、頁面、元組、事務ID等,RegularLock(常規鎖)在這些物件的保護性質中就像是讀寫鎖。


RegularLock(常規鎖)由LWLock(輕量級鎖)實現,其特點是:有等待佇列,有死鎖檢測,能自動釋放鎖。後文主要為RegularLock(常規鎖)的相關設計。


  

AntDB-T資料庫常規鎖

鎖型別


鎖方法:AntDB-T資料庫包含兩種加RegularLock(常規鎖)的方法:DEFAULT_LOCKMETHOD和USER_LOCKMETHOD。前者是預設鎖方法,後者為使用者鎖方法。AntDB-T資料庫通常使用DEFAULT_LOCKMETHOD作為預設加鎖方法。當然,使用者也可以定義自己的鎖方法,例如建議鎖(Advisory Lock)就是使用者建立的鎖型別之一。


鎖粒度:根據鎖物件的不同,分為:表級鎖、行級鎖、頁級鎖等,具體如下圖1所示,每個鎖物件的含義如下表所示。



圖1:鎖物件型別


表1:鎖的LOCKTAG型別及說明



從上表可以看出,常規鎖不僅可以對錶加鎖,也可以對各類物件加鎖,我們平時說的表鎖(表級鎖)、頁鎖、諮詢鎖等等(行鎖除外),實際上都是常規鎖根據不同鎖定物件劃分的子類。但是這裡說的行鎖除外, 因為AntDB-T資料庫採用元組級常規鎖+xmax結合的方式來實現行鎖,並不是單純用元組級常規鎖來實現的。


常規鎖最常用於給表加鎖,表級鎖:兩個事務在同一時刻不能在同一個表上持有互相沖突的鎖,但是可以同時持有不衝突的鎖。


 表2:常規鎖模式說明(按排他級別從低到高排序)



表3:常規鎖衝突的鎖模式(按排他級別從低到高排序)



AntDB-T資料庫常規鎖設計

基礎資料結構


1、LockMethodData表示鎖方法表,是鎖設計裡面比較基礎的一個結構。定義如下圖2所示:


圖2:LockMethodData資料結構


一個鎖方法表的控制結構定義在 LockMethodData中,它存在於共享記憶體中。其中欄位numLockModes表示在鎖表上定義的鎖模式數量,常規鎖的鎖模式在上圖2中有宏定義,其中NoLock 的宏定義為0,其本身不是一種鎖模式的標誌,這裡可以用來表示沒有獲得鎖,numLockModes欄位的數值必須小於或等於 MAX_LOCKMODES,MAX_LOCKMODES定義值為10。


欄位lockModeNames 表示用於Debug時列印用鎖模式名字用,具體鎖模式名字見上圖2所示,欄位trace_flag 表示指本鎖方法GUC traceflag 的指標。


欄位conlictTab顯示鎖模式衝突的位掩碼陣列,它用於指示持有或請求的鎖模式的集合,鎖模式的取值為1至numLockModes,所以conflictTab[0]未使用。透過不同位來表示的鎖的模式不同,每個位上都對應一個鎖模式,取值1或者0,來代表有無鎖,這就是bit格式的便利,例如加ShareLock鎖,對應的鎖模式為5,則將1左位移5位,透過檢視該欄位要判斷的對應陣列的第五位的值是否為1,來表示是否已經持有ShareLock。


conflictTab欄位還可用來判斷鎖衝突,例如:如果鎖模式i和j衝突,那麼conflictTab[i] 的第j位為1。下圖3是鎖模式衝突表(LockConflicts)的原始碼定義:



圖3: LockConflicts 衝突表定義


其中LOCKBIT_ON 的定位為:#define LOCKBIT_ON(lockmode) (1 << (lockmode)),這個LockConflicts衝突表並沒有直接設定某種型別鎖的衝突bitmask ,而是採用位移運算加或運算,目的是讓我們更直觀的瞭解到,各種型別的鎖之間相互衝突情況。例如:AccessExclusiveLock鎖的衝突情況,代表這個鎖的衝突情況的bitmask值為1111111110,即跟任何一種鎖型別(no lock 除外,表示沒有鎖)的LOCKBIT ON(lock mode)的值取&,結果都會為真,表示都衝突。


2、LOCKTAG表示加鎖物件標識,即鎖物件,一個LOCKTAG的值標識一個可加鎖物件。定義如下圖4所示:


圖4:加鎖物件標識資料結構


在LOCKTAG結構中,欄位locktag_type表示鎖物件的型別,具體列舉值前面圖1有介紹,欄位locktag_lockmethodid表示加鎖的方法,即採用如種方式加鎖的,採用資料庫預設的方式加鎖,還是採用使用者命令的方式加鎖(使用者採用select for update, lock table等一些命令),欄位locktag_field1~ locktag_field4沒有具體指明這些變數的用途,因為針對不同的LockTagType,這些 locktag_field1~ locktag_field4 中儲存的資料不一定相同。


比如當LockTagType 為 LOCKTAG_RELATION時,要求的 locktag_field 包括 DB OID +REL OID,當 LockTagType為LOCKTAG_TRANSACTION,則只需要該事務的 XID。


3、LOCK表示加鎖物件描述體,用於表示已經加鎖的資源或是請求加鎖的可鎖資源,定義如下圖5所示:



圖5:加鎖物件描述體資料結構


其中欄位tag表示為加鎖物件的描述符即鎖物件,欄位grantMask表示當前在該物件上分配的所有鎖模式的掩碼,欄位waitMask表示當前在該物件上等待的所有鎖模式的掩碼,欄位procLocks表示這個物件鎖上的所有程式PROCLOCK物件佇列,欄位waitProcs表示等待該物件上鎖的程式的等待佇列,欄位requested[MAX_LOCKMODES]表示記錄每種模式的鎖請求(持有+等待)該鎖的次數,欄位nRequested表示requested陣列元素的個數,即所有的鎖模式的鎖請求的總的數量,欄位granted[MAX_LOCKMODES]表示每一種鎖模式上的已分配的鎖的數量,欄位nGranted表示granted陣列中元素的個數。


在AntDB-T資料庫中,有很多會話可能會同時訪問一個物件,這些會話請求的鎖模式可能相同,也可能不同,可能相容,也可能不相容。為了方便表示所有會話在這個物件上總共加了哪些模式的鎖,總共還有哪些處於waiting中,所以採用了一個LOCKMASK(bitmask)來表示這兩種情況,其中grantMask、waitMask這2個欄位就是用於判斷已持鎖與請求鎖是否衝突。


初始化RegularLock(常規鎖)


RegularLock(常規鎖)的初始化操作由函式InitLocks(void)實現,該函式初始化鎖管理器的資料結構,主要工作有初始化LockMethodLockHash、LockMethodProcLockHash 和 LockMethodLocalHash 這三個 Hash 表。


LockMethodLockHash:資料庫級別的鎖表,為Lock 資料結構建立的hash表hash key 用 LOCKTAG 透過hash函式生成,整個hash表會儲存到共享記憶體中。


LockMethodProcLockHash:程式級別的鎖表,為 ProcLock 資料結構建立的hash 表,hash key用 PROCLOCKTAG 透過hash函式生成,同樣會儲存到共享記憶體中。


LockMethodLocalHash:本地鎖表,為LocalLock資料結構建立的hash表,LOCALLOCKTAG 透過hash 函式生成hash-key,儲存到本地。


加RegularLock(常規鎖)


RegularLock(常規鎖)的加鎖操作在函式LockAcquire(const LOCKTAG *locktag,LOCKMODE lockmode,bool sessionLock,bool dontWait)中定義。


其中引數含義如下:locktag 表示是被鎖物件的標識;lockmode指示要獲得的鎖模式;sessionLock表示加鎖的模式,如果為TRUE,表示為會話加鎖,如果為FALSE,則為當前事務申請鎖;dontWait表示申請鎖是否允許等待,如果為 TRUE,則在檢查到無法獲得鎖之後不等待,如果為 FALSE,則可以等待。該函式的返回值LockAcquireResult,它是一個列舉值,表示加鎖是否成功等結果資訊。


申請加RegularLock(常規鎖)的流程如下:


1)用locktag(鎖物件)和lockmode(加鎖模式)組成一個具體的加鎖型別作為hash-key,然後在本地鎖表對應的hash表(LockMethodLocalHash)中查詢此加鎖型別的資訊。


2)如果沒有找到此加鎖型別的資訊,則構造一條插入本地鎖表中;否則找到此加鎖型別的資訊,分配空間以記錄鎖擁有者的資訊。


3)如果當前事務已經持有過此型別的鎖(locallock->nLocks >0),在本地表的計數器上加1並更新鎖資源擁有者裡面的計算器也加1,然後直接退出。


4)如果鎖模式是AccessExclusiveLock且鎖物件是 Relation(表),則會嘗試分配一個transactionid(事務id) 來在後續加鎖成功之後寫一條 WAL record。


5)滿足快速鎖模式時,加鎖並返回加鎖成功。加快速鎖前提是當前會話之前沒有新增過強鎖且還有充足的儲存弱鎖的位置。


6)如果申請的是強鎖,則會先將當前程式持有的強鎖的計數自增,並將快速鎖資訊從會話本地轉移到主鎖表中(共享記憶體中)。


7)在全域性的鎖表(LockMethodLockHash)中查詢這個鎖,如果在“Lock Hash”中找不到,則在"Lock Hash”中插入一個新元素。然後在 ProcLock-Hash (LockMethodProcLockHash)表裡也查詢對應的 ProcLock,如果在ProcLock Hash表找不到,則插入該ProcLock。


8)檢查新增的鎖模式會不會與已加的鎖模式發生衝突,如果不會,則加鎖;否則,根據函式引數決定等待還是退出。如果退出,還需清除鎖表中相應的元素以保持一致性。


9)如果是AccessExclusiveLock鎖模式,則需要記錄一條WAL record。


釋放RegularLock(常規鎖)


與加RegularLock(常規鎖)相對應的操作是解RegularLock(常規鎖),RegularLock 的解鎖操作定義在函式LockRelease(const LOCKTAG *locktag, LOCKMODE lockmode, bool sessionLock)中定義。


其中引數含義如下:locktag 表示是被鎖物件的標識;lockmode指示要獲得的鎖模式;sessionLock表示加鎖的模式,如果為TRUE,表示為會話加鎖,如果為FALSE,則為當前事務申請鎖。該函式在本地鎖表(LockMethodLocalHash)中查詢鎖標記為LockTag的鎖,並釋放該鎖;如果SessionLock為TRUE,則釋放一個會話鎖(SessionLock),否則,釋放一個常規的事務鎖。如果發現任何等待程式現在是可以被喚醒的,將請求的鎖賦予它們並將其喚醒。


申請解RegularLock(常規鎖)的流程如下:


1)用locktag(鎖物件)和lockmode(加鎖模式)組成一個具體的加鎖型別作為hash-key,然後在本地鎖表對應的hash表(LockMethodLocalHash)中查詢此加鎖型別的資訊。


2)找到此型別鎖的擁有者,在它持有鎖的計數器上減 1。如果它已經不再持有此鎖,則刪除這個擁有者的資訊。


3)如果這個型別的鎖並沒有真正釋放,只是計數器減1,直接退出。


4)如果是快速鎖則清除快速鎖的加鎖標記,如果解鎖成功就返回。


5)如果不是快速鎖,則在全域性的“Lock Hash”(LockMethodLockHash)和"ProcLock Hash”(LockMethodProcLockHash)表裡查詢此鎖對應的 Lock 和 ProcLock,呼叫UnGrantLock修改其資訊。喚醒可以被喚醒的程式,並從“Local Hash"( LockMethodLocalHash)裡移除該型別鎖。


清理RegularLock(常規鎖)


RegularLock(常規鎖)的清理在函式 CleanUpLock 中,在釋放鎖之後執行,主要是清理LockMethodProcLockHash、LockMethodLockHash佇列,以及喚醒可以被喚醒的程式。


AntDB-T資料庫常規鎖使用場景


前面部分介紹了鎖相關的理論部分,下面舉個常規鎖的例子來體會下鎖。


1、資料準備:建表,構造資料,具體SQL 如下圖6所示:


圖6:資料準備


2、會話一執行開始事務和UPDATE語句如下圖7所示,此時會話表上的鎖模式為RowExclusiveLock 。


圖7:開始事務和更新表


3、會話二執行ALTER TABLE語句,這時看到修改表語句處於等待狀態,如下圖8所示:此時會話表上需要加鎖的模式是AccessExclusiveLock,該模式與會話一上持有的鎖模式衝突,所以此SQL語句會一直等在那裡。


圖8:修改表


4、會話三查詢鎖等待的程式號以及鎖資訊,如下圖9所示:


圖9:查詢鎖等待的程式資訊


這裡可以看到等待的鎖時一個表鎖,鎖模式是AccessExclusiveLock,pid為541258。


5、會話三根據鎖等待的程式pid找到當前持有鎖的程式,並檢視持有鎖程式的詳細情況,比如對應的應用、SQL、等待事件等,如下圖10所示:


圖10:查詢鎖持有的程式資訊


6、驗證等待鎖、持有鎖的程式pid是否正確,用ps檢視過濾下想要的程式, 如下圖11所示:


圖11:驗證等待鎖持有鎖的程式pid


從上圖11中可以看出,等待鎖的程式pid 為541258 在執行ALTER TABLE操作,與圖9查出的程式pid一致,持有鎖的程式pid為541046,處在空閒事務狀態,與圖10查出的程式pid一致。會話二語句目前一直等在那裡,要等到會話一結束,才能執行成功。如果會話一忘記COMMIT/ROLLBACK了,也不要太擔心,AntDB-T資料庫提供了一些超時引數,當超過配置的引數時,就會自動結束會話。


引數idle_in_transaction_session_timeout:在一個空閒的事務中,空閒時間超過這個值,將視為超時,0表示禁用,一般預設都是禁用。獲取一個表,索引,行上的鎖超過這個時間,直接報錯,不等待,0為禁用.


引數lock_timeout:獲取一個表,索引,行上的鎖超過這個時間,直接報錯,不等待,0為禁用。


引數statement_timeout:當SQL語句的執行時間超過這個設定時間,終止執行SQL,0為禁用。


引數deadlock_timeout:死鎖時間超過這個值將直接報錯,不會等待,預設設定為1s。


總結與展望

本文主要講述了AntDB-T資料庫鎖的分類、常規鎖的基本概念、常規鎖的設計以及常規鎖具體使用的場景。限於篇幅原因,與常規鎖相關的死鎖檢測、行鎖等相關內容沒有涉及,以後有機會將會進行分享。


在未來,隨著雲端計算、大資料和人工智慧等技術的持續發展,資料的應用場景和規模也將不斷擴大。為了滿足不同的資料應用需求,AntDB-T資料庫的鎖機制也將不斷升級和最佳化。透過與其他資料庫技術相結合,更好地滿足企業和使用者的需求,成為企業資料應用的重要保障和支援,為資料的安全和可靠性保駕護航。



關於亞信安慧AntDB資料庫


AntDB資料庫始於2008年,在運營商的核心繫統上,服務國內24個省市自治區的數億使用者,具備高效能、彈性擴充套件、高可靠等產品特性,峰值每秒可處理百萬筆通訊核心交易,保障系統持續穩定執行超十年,並在通訊、金融、交通、能源、物聯網等行業成功商用落地。



來自 “ ITPUB部落格 ” ,連結:https://blog.itpub.net/70026696/viewspace-3005246/,如需轉載,請註明出處,否則將追究法律責任。

相關文章