SQlite資料庫的C編程介面(七) 資料庫鎖定(Database Locking) ——《Using SQlite》讀書筆記

來源:互聯網
上載者:User

SQlite資料庫的C編程介面(七)  資料庫鎖定(Database Locking) 
by斜風細雨QQ:253786989    2012-02-09

  對於《Using SQLite》的這一節內容,理解的不是很清楚。有時間要仔細看看SQLite的文檔:http://www.sqlite.org/lockingv3.html(File Locking And Concurrency In SQLite Version 3

  SQLite使用一些不同的鎖來保護資料庫,以允許多個資料庫連接同時訪問一個相同的資料庫檔案,而不會出現資料庫損壞。不管是在“自動認可事務(autocommit transaction)”模式,還是“顯示事務(explicit transaction)”模式,這些鎖都工作良好。

  SQLite鎖系統(locking system)涉及幾個不同層次的鎖,用來減少競爭、避免死結等等。以使SQlite允許多個資料庫連接並行讀取同一個資料庫檔案,不過任何寫操作都需要完整的,整個資料庫檔案的獨佔訪問。

  大部分時間鎖系統(locking system)工作良好,允許不同的應用程式之間方便和安全的分享同一個資料庫檔案。如果編碼得當,大部分寫操作僅僅需要幾分之一秒。然而,如果多個資料庫連接試圖在同一時間訪問同一個資料庫檔案,那這些操作遲早會相遇。通常情況下,如果一個資料庫操作需要一個暫時無法得到的鎖,那麼SQLite會返回SQLITE_BUSY,或者在更極端的情況下,返回SQLITE_IOERR(或者擴充碼SQLITE_IOERR_BLOCKED)。函數sqlite3_prepare_xxx、sqlite3_step、sqlite3_reset、sqlite3_finalize會返回SQLITE_BUSY。函數sqlite3_backup_step、sqlite3_blob_open也會返回SQLITE_BUSY,因為在這兩個函數的內部都是通過調用sqlite3_prepare_xxx、sqlite3_step函數來完成工作的。如果調用sqlite3_close函數時,所串連的資料庫存在沒有銷毀(unfinalize)的語句,則該函數也會返回SQLITE_BUSY。

  如果想訪問某個鎖,就需要等待其所有者完成並釋放對它的使用,通常不會等待太長時間。等待(waiting)狀態可以由應用程式處理,比如接收到SQLITE_BUSY應答,則再次嘗試處理該語句。或者也可以由一個忙處理常式(busy handler)來處理。

忙處理常式(Busy handlers

  busy handler(忙處理常式)是一個回呼函數,當SQLite library無法擷取一個鎖時調用。在busy handler中,可以繼續嘗試擷取鎖的操作,或者放棄並返回SQLITE_BUSY錯誤碼。

  SQLite有一個內建的基於定時器的busy handler,可以給這個busy handler設定一個以毫秒為單位的逾時時間。在逾時時間範圍內,busy handler將繼續重複嘗試擷取鎖的操作。

int sqlite3_busy_timeout(sqlite3*, int ms);

  用來設定內建busy handler的逾時時間,以毫秒為單位。如果給ms參數傳遞0或者負值,則內建的busy handler被清除。

  程式員也可以自己寫busy handler,然後通過aqlite3_busy_handler函數進行設定。

int sqlite3_busy_handler(sqlite3*, int(*)(void*,int), void*);

  給指定的資料庫連接設定自訂的busy handler,把自己寫的busy handler函數傳遞給該函數的第2個參數。如果給第2個參數傳遞NULL,則移除自訂的busy handler。第3個參數是傳遞給busy handler回呼函數的使用者資料指標。

  自訂的busy handler回呼函數原型如下:

int user_defined_busy_handler_callback( void *udp, int incr )

  第1個參數是通過sqlite3_busy_handler函數傳遞過來的使用者資料指標。第2個參數是一個計數器,每次busy handler被調用時該計數器的計數遞增。如果該回呼函數返回0,則SQLite將放棄擷取鎖的操作,並返回SQLITE_BUSY。如果該函數返回一個非0值,則SQLite將繼續嘗試擷取鎖的操作。

  需要注意的是:一個資料庫連接僅僅可以擁有一個busy handler,不能同時設定自訂的busy handler並配置內建的基於定時器的busy handler。每次設定其中一種busy handler都將刪除另一個busy handler。

死結(Deadlocks

  為一個資料庫連接設定busy handler不能夠解決所有問題。有的時候在多個資料庫連接之間會出現死結現象。比如兩個資料庫連接各自持有一些鎖,但是它們現在都在等待對方持有的鎖被釋放,這樣就會造成死結現象。唯一的解決辦法,就是其中一個資料庫連接放棄擷取鎖的操作,並釋放自己所持有的鎖。

  如果SQLite檢測到了一個潛在的死結情況,它將跳過busy handler,並將有一個資料庫連接立刻返回SQLITE_BUSY。這樣做是為了鼓勵應用程式釋放他們自己的鎖,以打破死結現象。

避免SQLITE_BUSY(Avoiding SQLITE_BUSY

  如果我們在開發某個項目時需要充分考慮資料庫的並發效能,最簡單的方法是使用sqlite3_busy_timeout設定內建的busy handler,並且逾時時間在250-2000毫秒範圍內進行調整,這樣可以減少SQLITE_BUSY的產生。

  如果想完全避免SQLITE_BUSY,唯一的辦法就是確保對於同一個資料庫某一個時刻僅僅存在一個資料庫連接。這需要設定PRAGMA locking-mode為EXCLUSIVE(獨佔的)。

  另外,應用程式可以使用獨佔模式(EXCLUSIVE)的事務,這樣對於SQLITE_BUSY傳回值的處理就更容易些。可以通過BEGIN EXCLUSIVE TRANSACTION命令開啟一個獨佔式的事務,如果成功則在事務的執行過程中不會返回SQLITE_BUSY。不過BEGIN命令本身可能會執行失敗並返回SQLITE_BUSY,但這種情況處理起來很容易,應用程式可以通過sqlite3_reset函數重設BEGIN語句,然後重試。BEGIN EXCLUSIVE的缺點是只有目前沒有其他的資料庫連接(包括唯讀事務)正在訪問該資料庫,它才會執行成功。而且一旦獨佔式的事務開始執行,它同樣會鎖住資料庫,使其他的資料庫連接(包括唯讀事務)無法訪問該資料庫。

  為了允許更多的並發訪問,還有一種類型的事務——IMMEDIATE TRANSACTION(即時事務)。可以通過BEGIN IMMEDIATE TRANSACTION命令啟動一個即時事務,如果成功則在事務執行的過程中通常只有執行到COMMIT語句時才會返回SQLITE_BUSY。不管是事務中的哪些命令(包括COMMIT),一旦遇到了SQLITE_BUSY,應用程式可以簡單的重設語句然後等待並重試。BEGIN IMMEDIATE語句本身也有可能會遭遇SQLITE_BUSY,這時應用程式也是可以簡單的重設BEGIN語句然後重試。與EXCLUSIVE TRANSACTION不同的是,如果存在其它的資料庫連接正在讀取(非寫)資料庫,這時候IMMEDIATE事務是可以啟動的。一旦IMMEDIATE事務成功啟動,則不允許其它資料庫連接進行寫入操作,但唯讀資料庫連接仍然可以訪問資料庫,除非IMMEDIATE事務正在強制修改資料庫檔案(通常是事務正在執行COMMIT操作)。使用IMMEDIATE事務不會發生死結現象,所有的SQLITE_BUSY可以通過重試操作進行處理。

避免死結(Avoiding deadlocks

  避免死結的原則比較簡單,不過遵循這些原則會使應用程式變得複雜一些。

  首先,sqlite3_prepare_xxx、sqlite3_backup_step、sqlite3_blob_open函數調用不會產生死結現象。任何時候如果這些函數返回SQLITE_BUSY,簡單的等待一下然後重試即可。

  如果在一個(deferred)事務中,sqlite3_step、sqlite3_reset、sqlite3_finalize函數返回SQLITE_BUSY,則應用程式必須回退然後重試。如果這些語句不在一個顯示的(explicit)事務中,prepared語句可以簡單的重設然後重新執行。如果這些語句在一個顯示的(explicit)事務中,那麼整個事務就必須回退,然後從頭開始執行。致使回退的整個原因就是其他一些資料庫連接需要修改資料庫。另外需要注意的是,如果應用程式完成了一些讀取操作並準備進行寫操作,那麼在新的事務中最好重新讀取這些資訊以確定這些資料仍然有效。

  不管你做什麼,不要忽視SQLITE_BUSY。它可能很少發生,但如果處理不當它就可能成為大麻煩的源頭。

當“繁忙”轉換為“被阻塞”(When BUSY becomes BLOCKED)

  當一個串連需要修改資料庫中的資料,資料庫就要被加鎖使其對於其他串連處於唯讀狀態。事實上,事務中對於資料庫的某個修改並沒有馬上寫入資料庫檔案,而是儲存在資料庫的頁緩衝中。因為如果將這個修改直接寫入資料庫,那麼這個修改對於其他的讀串連就可見了,這就打破了事務的隔離性原則。

  當所有需要的修改操作全部做完,事務開始準備提交。這時資料庫檔案會被進一步鎖定,不允許新的唯讀事務啟動。允許已經存在的讀操作(reader)完成,並釋放他們持有的資料庫鎖。當所有的讀操作完成,寫操作(writer)就要獨佔式的訪問資料庫,最終將頁緩衝中的修改重新整理到資料庫中。

  這一過程允許寫事務正在執行的同時,唯讀事務依然可以繼續運行。當寫事務確實在提交資料時,讀事務被鎖定。然而,一個關鍵的假設是在這個過程中,所有修改被放入頁緩衝中,直到事務提交的時候才被寫入資料庫。如果緩衝(cache)中裝滿了(包含處於懸掛狀態的修改的)資料頁,那麼寫事務沒有其它選擇,只能給資料庫加一個獨佔鎖,並且在提交(commit)階段之前重新整理緩衝。該事務仍然可以在任何時候復原,但是寫入操作必須獲得獨佔鎖的即時訪問,以重新整理緩衝。

  如果這個鎖不是立即可用,寫入操作就會被強制終止整個事務。寫入事務將回退,然後返回擴充碼SQLITE_IOERR_BLOCKED。因為事務是自動回退的,所以應用程式沒有更多選擇,只能重新啟動事務。

  為了避免這種情況,最好是用顯示的BEGIN EXCLUSIVE語句啟動一個可以修改多行資料的大事務。BEGIN EXCLUSIVE也許會失敗,並返回SQLITE_BUSY,但是應用程式可以簡單的重新嘗試直到成功。一旦一個獨佔式的事務成功啟動,它就可以完整的訪問資料庫,消除SQLITE_IOERR_BLOCKED的出現,甚至事務在提交之前就造成了緩衝區溢位(增加資料庫緩衝會有所協助)。

SQlite資料庫的C編程介面(七)  資料庫鎖定(Database Locking) 
by斜風細雨QQ:253786989    2012-02-09

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.