SQLite學習筆記(六)&&共用快取

來源:互聯網
上載者:User

標籤:

介紹

       通常情況下,sqlite中每個串連都會一個獨立的pager對象,pager對象中管理了該串連的緩衝資訊,通過pragma cache_size指令可以設定緩衝大小,預設是2000個page,每個page是1024B。這樣導致了對於同一個資料檔案,多個串連各自維護了自己的一份緩衝,在高並發情況下,可能導致使用大量的記憶體。而sqlite作為一個嵌入式資料庫,通常用於嵌入式裝置,記憶體可能比較有限,為了應對這種問題,sqlite提供了一種方法,通過讓多個串連公用一個pager對象,共用同一份緩衝。

      當開啟這個特性後,多個串連可以共用一個pager對象,這樣在一定程度上減少了記憶體的消耗和檔案的IO次數。一個線程的多個連線物件,以及多個線程的多個連線物件都可以採用這種方式來共用快取。由於sqlite以動態庫方式嵌入在應用程式中,每個應用程式有自己獨立的進程空間,因此該特性不能用於進程之間,同一個進程可以共用一份緩衝,進程之間各自維護自己的緩衝。關於緩衝的實現後面會單獨寫一篇文章。展示了啟用共用快取後的結構圖。


圖1

從圖1中可以看到,Process1中的兩個串連共用BtShared對象,BtShared對象對應一個Pager對象,而緩衝由Pager對象管理,因此整個Process1中的所有串連公用一個緩衝,通過Pager模組操作Page,IO操作對上層模組透明。

實現原理

      調用介面sqlite3_open_v2開啟串連時,通過指定參數SQLITE_OPEN_SHAREDCACHE來聲明串連採用共用快取模式。之前SQLite系列(二):常規效能測試 中的單表主鍵查詢測試章節中提到,開啟共用快取模式下,導致應用的程式的並發效能大大下降,多線程情況下,CPU也只能用一個核。因此大家在實際使用中,要權衡記憶體和並行度,來確定是否開啟共用快取模式。
      我們的測試情境都是唯讀,理論上不應該存在並發衝突,那麼為什麼不能並行?這裡要看共用快取的實現了。從圖1中也可以看到,每個串連有一個btree對象,多個btree對象通過共用BtShared對象來共用快取,BtShared對象通過mutux來維護裡面的成員,包括page cache的管理,table-lock資訊等,因此這個mutex是一個熱點,在高並發情境下,多個線程同時訪問BtShared對象,會由於競爭mutex,無法充分並發,導致並行度差。

table-lock

      預設情況下,sqlite只通過檔案鎖就可以實現讀寫互斥,讀讀並發的效果,關於這點我在SQLite系列(五):SQLITE封鎖機制中已經說明。那麼為什麼要引入table-lock?這個也是拜共用快取所賜。共用快取模式下,多個btree對象對應同一個BtShared對象,在執行更新時,首先會修改pager中cache,此時更新事務加了reserved-lock,與讀事務的shared-lock不互斥。為了避免讀到髒頁,在共用快取模式下,增加了table-lock,避免讀寫事務同時訪問同一個cache,導致髒讀的情況發生。使用者訪問具體某個表的page之前,會首先調用sqlite3BtreeLockTable對該表設定一個READ-LOCK(select 操作)或WRITE-LOCK(DML操作),如果發現衝突,則拋出SQLITE_LOCKED錯誤。通過這種方式,保證了多個線程不會同時對一個表進行讀寫操作。函數sqlite3BtreeLockTable部分實現如下:

sqlite3BtreeEnter(p);//判斷加鎖是否衝突rc = querySharedCacheTableLock(p, iTab, lockType);if( rc==SQLITE_OK ){  //加鎖  rc = setSharedCacheTableLock(p, iTab, lockType);}  sqlite3BtreeLeave(p); 

從上面的代碼可以看到,若加鎖衝突,則直接報SQLITE_LOCKED錯誤;否則加上鎖。注意看到判斷加鎖和加鎖過程通過BtShared對象的mutex來保護(sqlite3BtreeEnter,sqlite3BtreeLeave),因此加鎖過程是串列的,table-lock鏈表不會被多個線程同時操作。

querySharedCacheTableLock代碼邏輯

/* If some other connection is holding an exclusive lock, the** requested lock may not be obtained.*/if( pBt->pWriter!=p && (pBt->btsFlags & BTS_EXCLUSIVE)!=0 ){       sqlite3ConnectionBlocked(p->db, pBt->pWriter->db);     return SQLITE_LOCKED_SHAREDCACHE;}
setSharedCacheTableLock邏輯
for(pIter=pBt->pLock; pIter; pIter=pIter->pNext){    if( pIter->iTable==iTable && pIter->pBtree==p ){       pLock = pIter;    break;  }}/*create a table lock*/if( !pLock ){   pLock = (BtLock *)sqlite3MallocZero(sizeof(BtLock));   if( !pLock ){       return SQLITE_NOMEM;  }  pLock->iTable = iTable;pLock->pBtree = p;pLock->pNext = pBt->pLock;pBt->pLock = pLock;}

遍曆BtShared對象中已有鎖鏈表,比較iTable和對應的pBtree是否與自身相同(自己是否已經加過),若沒有,則申請鎖對象,加入鏈表。

加鎖流程

我們知道sqlite有兩種記錄模式,預設的DELETE模式和WAL模式,下面我會介紹開啟共用快取模式後,更新操作的加鎖流程,主要變化在於table-lock。

普通記錄模式+共用快取模式

  1. 開啟事務:shared-lock[sqlite3BtreeBeginTrans]
  2. DML操作:
  • 檔案鎖,reserved-lock
  • table-lock,
    將對應的表加鎖,同一個表的讀鎖與寫鎖互斥
  • 讀取表對應的page
  1. 提交:
  • 加execlusive-lock [sqlite3PagerCommitPhaseOne,刷日誌]
  • 刪除記錄檔
  • 釋放execlusive-lock
  • 釋放table-lock

 WAL記錄模式+共用快取模式

  1. 開啟事務,shared-lock[sqlite3BtreeBeginTrans]
  2. DML操作
  • 資料檔案鎖,shared-lock
  • wal記錄檔write-lock
  • table-lock
  1. 提交
  • 釋放table-lock
  • 釋放記錄檔write-lock
  • 釋放資料檔案shared-lock

SQLite學習筆記(六)&&共用快取

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.