golang中 boltdb的學習和實踐
golang boltdb的學習和實踐
1. 安裝
go get github.com/boltdb/bolt
2.建立和啟動資料庫
db, err := bolt.Open("my.db", 0600, nil)
其中open
的第一個引數為路徑,如果資料庫不存在則會建立名為my.db的資料庫, 第二個為檔案操作,第三個引數是可選引數, 內部可以配置只讀和超時時間等,
特別需要注意的地方就是因為boltdb是檔案操作型別的資料庫,所以只能單點寫入和讀取,如果多個同時操作的話後者會被掛起直到前者關閉操作為止, boltdb一次只允許一個讀寫事務,但一次允許多個只讀事務。所以資料具有較強的一致性。
因此單個事務和從它們建立的所有物件(例如桶、鍵)都不是執行緒安全的。與資料在多個概念你必須為每一個或使用鎖機制來保證只有一個goroutine裡操作改變資料。
只讀事務和讀寫事物通常不應該在同一個goroutine裡同時開啟。由於讀寫事務需要週期性地重新對映資料檔案,這可能導致死鎖。
3.讀寫事務
boltdb的讀寫事務操作我們可以使用DB.Update()
來完成形如:
err := db.Update(func(tx *bolt.Tx) error { ... return nil })
在閉包fun中,在結束時返回nil來提交事務。您還可以通過返回一個錯誤在任何點回滾事務。所有資料庫操作都允許在讀寫事務中進行。
始終要關注err返回,因為它將報告導致您的事務不能完成的所有磁碟故障。
4.批量讀寫事物
每一次新的事物都需要等待上一次事物的結束,這種開銷我們可以通過DB.Batch()
批處理來完成
err := db.Batch(func(tx *bolt.Tx) error { ... return nil })
在批處理過程中如果某個事務失敗了,批處理會多次呼叫這個函式函式返回成功則成功。如果中途失敗了,則整個事務會回滾。
5.只讀事務
只讀事務可以使用DB.View()
來完成
err := db.View(func(tx *bolt.Tx) error { ... return nil })
不改變資料的操作都可以通過只讀事務來完成, 您只能檢索桶、檢索值,或在只讀事務中複製資料庫。
6.啟動事務
DB.Begin()
啟動函式包含在db.update和db.batch中,該函式啟動事務開始執行事務並返回結果關閉事務,這是boltdb推薦的方式,有時候你可能需要手動啟動事物你可以使用Tx.Begin()
來開始,切記不要忘記關閉事務。
// Start a writable transaction. tx, err := db.Begin(true) if err != nil { return err } defer tx.Rollback() // Use the transaction... _, err := tx.CreateBucket([]byte("MyBucket")) if err != nil { return err } // Commit the transaction and check for error. if err := tx.Commit(); err != nil { return err }
7.使用桶
桶是資料庫中鍵/值對的集合。桶中的所有鍵必須是唯一的。您可以使用DB.CreateBucket()
建立一個桶:
db.Update(func(tx *bolt.Tx) error { b, err := tx.CreateBucket([]byte("MyBucket")) if err != nil { return fmt.Errorf("create bucket: %s", err) } return nil })
你也可以是實用Tx.CreateBucketIfNotExists()
來建立桶,該函式會先判斷是否已經存在該桶不存在即建立, 刪除桶可以使用Tx.DeleteBucket()
來完成
8.使用k-v對
儲存鍵值對到桶裡可以使用Bucket.Put()
來完成:
db.Update(func(tx *bolt.Tx) error { b := tx.Bucket([]byte("MyFriendsBucket")) err := b.Put([]byte("one"), []byte("zhangsan")) return err })
獲取鍵值Bucket.Get()
:
db.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte("MyFriendsBucket")) v := b.Get([]byte("one")) fmt.Printf("The answer is: %s\n", v) return nil })
get()
函式不返回一個錯誤,因為它的執行是保證工作(除非有某種系統故障)。如果鍵存在,那麼它將返回它的值。如果它不存在,那麼它將返回nil。
還需要注意的是當事務開啟都get返回的值時唯一有效的,如果你需要將該值用於其他事務,你可以通過copy
拷貝到其他的byte slice中
9.桶的自增
利用nextsequence()
功能,你可以讓boltdb生成序列作為你鍵值對的唯一標識。見下面的示例。
func (s *Store) CreateUser(u *User) error { return s.db.Update(func(tx *bolt.Tx) error { // 建立users桶 b := tx.Bucket([]byte("users")) // 生成自增序列 id, _ = b.NextSequence() u.ID = int(id) // Marshal user data into bytes. buf, err := json.Marshal(u) if err != nil { return err } // Persist bytes to users bucket. return b.Put(itob(u.ID), buf) }) } // itob returns an 8-byte big endian representation of v. func itob(v int) []byte { b := make([]byte, 8) binary.BigEndian.PutUint64(b, uint64(v)) return b } type User struct { ID int ... }
10. 迭代鍵
boltdb以桶中的位元組排序順序儲存鍵。這使得在這些鍵上的順序迭代非常快。要遍歷鍵,我們將使用遊標Cursor()
:
db.View(func(tx *bolt.Tx) error { // Assume bucket exists and has keys b := tx.Bucket([]byte("MyBucket")) c := b.Cursor() for k, v := c.First(); k != nil; k, v = c.Next() { fmt.Printf("key=%s, value=%s\n", k, v) } return nil })
遊標Cursor()
允許您移動到鍵列表中的特定點,並一次一個地通過操作鍵前進或後退。
游標上有以下函式:
First()移動到第一個健. Last()移動到最後一個健. Seek()移動到特定的一個健. Next()移動到下一個健. Prev()移動到上一個健.
這些函式中的每一個都返回一個包含(key []byte, value []byte)的簽名。當你有游標迭代結束,next()將返回一個nil。在呼叫next()或prev()之前,你必須尋求一個位置使用first(),last(),或seek()。如果您不尋求位置,則這些函式將返回一個nil鍵。
在迭代過程中,如果鍵為非零,但值為0,則意味著鍵指向一個桶而不是一個值。用桶.bucket()訪問子桶。
11.字首掃描
遍歷一個key的字首,你可以結合seek()
和bytes.hasprefix()
:
db.View(func(tx *bolt.Tx) error { // Assume bucket exists and has keys c := tx.Bucket([]byte("MyBucket")).Cursor() prefix := []byte("1234") for k, v := c.Seek(prefix); bytes.HasPrefix(k, prefix); k, v = c.Next() { fmt.Printf("key=%s, value=%s\n", k, v) } return nil })
12.範圍掃描
另一個常見的用例是掃描範圍,例如時間範圍。如果你使用一個合適的時間編碼,如rfc3339然後可以查詢特定日期範圍的資料:
db.View(func(tx *bolt.Tx) error { // Assume our events bucket exists and has RFC3339 encoded time keys. c := tx.Bucket([]byte("Events")).Cursor() // Our time range spans the 90's decade. min := []byte("1990-01-01T00:00:00Z") max := []byte("2000-01-01T00:00:00Z") // Iterate over the 90's. for k, v := c.Seek(min); k != nil && bytes.Compare(k, max) <= 0; k, v = c.Next() { fmt.Printf("%s: %s\n", k, v) } return nil })
13.迴圈遍歷每一個
如果你知道所在桶中擁有鍵,你也可以使用ForEach()
來迭代:
db.View(func(tx *bolt.Tx) error { // Assume bucket exists and has keys b := tx.Bucket([]byte("MyBucket")) b.ForEach(func(k, v []byte) error { fmt.Printf("key=%s, value=%s\n", k, v) return nil }) return nil })
14.巢狀桶
還可以在一個鍵中儲存一個桶,以建立巢狀的桶:
func (*Bucket) CreateBucket(key []byte) (*Bucket, error) func (*Bucket) CreateBucketIfNotExists(key []byte) (*Bucket, error) func (*Bucket) DeleteBucket(key []byte) error
15.資料庫備份
boltdb是一個單一的檔案,所以很容易備份。你可以使用TX.writeto()
函式寫一致的資料庫。如果從只讀事務呼叫這個函式,它將執行熱備份,而不會阻塞其他資料庫的讀寫操作。
預設情況下,它將使用一個常規檔案控制代碼,該控制代碼將利用作業系統的頁面快取。有關優化大於RAM資料集的資訊,請參見Tx
文件。
一個常見的用例是在HTTP上進行備份,這樣您就可以使用像cURL
這樣的工具來進行資料庫備份:
func BackupHandleFunc(w http.ResponseWriter, req *http.Request) { err := db.View(func(tx *bolt.Tx) error { w.Header().Set("Content-Type", "application/octet-stream") w.Header().Set("Content-Disposition", `attachment; filename="my.db"`) w.Header().Set("Content-Length", strconv.Itoa(int(tx.Size()))) _, err := tx.WriteTo(w) return err }) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } }
然後您可以使用此命令進行備份:
$ curl http://localhost/backup > my.db
或者你可以開啟你的瀏覽器以ofollow,noindex" target="_blank">http://localhost/backup ,它會自動下載。
如果你想備份到另一個檔案,你可以使用TX.copyfile()
輔助功能。
16.統計
資料庫對執行的許多內部操作保持一個執行計數,這樣您就可以更好地瞭解發生了什麼。通過捕捉這些資料的快照,我們可以看到在這個時間範圍內執行了哪些操作。
例如,我們可以開始一個goroutine裡記錄統計每10秒:
go func() { // Grab the initial stats. prev := db.Stats() for { // Wait for 10s. time.Sleep(10 * time.Second) // Grab the current stats and diff them. stats := db.Stats() diff := stats.Sub(&prev) // Encode stats to JSON and print to STDERR. json.NewEncoder(os.Stderr).Encode(diff) // Save stats for the next loop. prev = stats }
17.只讀模式
有時建立一個共享的只讀boltdb資料庫是有用的。對此,設定options.readonly國旗開啟資料庫時。只讀模式使用共享鎖允許多個程序從資料庫中讀取,但它將阻塞任何以讀寫方式開啟資料庫的程序。
db, err := bolt.Open("my.db", 0666, &bolt.Options{ReadOnly: true}) if err != nil { log.Fatal(err) }
18.移動端支援(ios/android)
boltdb能夠執行在移動裝置上利用的工具結合特徵GoMobile。建立一個結構體,包含您的資料庫邏輯和參考一個bolt.db與初始化contstructor需要在檔案路徑,資料庫檔案將儲存。使用這種方法,Android和iOS都不需要額外的許可權或清理。
func NewBoltDB(filepath string) *BoltDB { db, err := bolt.Open(filepath+"/demo.db", 0600, nil) if err != nil { log.Fatal(err) } return &BoltDB{db} } type BoltDB struct { db *bolt.DB ... } func (b *BoltDB) Path() string { return b.db.Path() } func (b *BoltDB) Close() { b.db.Close() }
資料庫邏輯應定義為此包裝器結構中的方法。
要從本機語言初始化此結構(兩個平臺現在都將本地儲存與雲同步)。這些片段禁用資料庫檔案的功能):
Android
String path; if (android.os.Build.VERSION.SDK_INT >=android.os.Build.VERSION_CODES.LOLLIPOP){ path = getNoBackupFilesDir().getAbsolutePath(); } else{ path = getFilesDir().getAbsolutePath(); } Boltmobiledemo.BoltDB boltDB = Boltmobiledemo.NewBoltDB(path)
IOS
- (void)demo { NSString* path = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0]; GoBoltmobiledemoBoltDB * demo = GoBoltmobiledemoNewBoltDB(path); [self addSkipBackupAttributeToItemAtPath:demo.path]; //Some DB Logic would go here [demo close]; } - (BOOL)addSkipBackupAttributeToItemAtPath:(NSString *) filePathString { NSURL* URL= [NSURL fileURLWithPath: filePathString]; assert([[NSFileManager defaultManager] fileExistsAtPath: [URL path]]); NSError *error = nil; BOOL success = [URL setResourceValue: [NSNumber numberWithBool: YES] forKey: NSURLIsExcludedFromBackupKey error: &error]; if(!success){ NSLog(@"Error excluding %@ from backup %@", [URL lastPathComponent], error); } return success; }
19.檢視工具
1.下載工具
go get github.com/boltdb/boltd
然後編譯cmd下的main檔案生成可執行檔案改名為boltd
拷貝boltd到 *.db同級目錄,執行如下:
然後開啟網站:
2.命令列工具
https://github.com/hasit/bolter