一、入門代碼
LMDB的全稱是Lightning Memory-Mapped Database(快如閃電的內存映射數據庫),它的文件結構簡單,包含一個數據文件和一個鎖文件:
LMDB文件可以同時由多個進程打開,具有極高的數據存取速度,訪問簡單,不需要運行單獨的數據庫管理進程,只要在訪問數據的代碼里引用LMDB庫,訪問時給文件路徑即可。
讓系統訪問大量小文件的開銷很大,而LMDB使用內存映射的方式訪問文件,使得文件內尋址的開銷非常小,使用指針運算就能實現。數據庫單文件還能減少數據集復制/傳輸過程的開銷。
在python中使用lmdb: linux中,可以使用指令‘pip install lmdb' 安裝lmdb包。
1. 生成一個空的lmdb數據庫文件
1
2
3
4
5
6
7
|
# -*- coding: utf-8 -*-
import
lmdb
# 如果train文件夾下沒有data.mbd或lock.mdb文件,則會生成一個空的,如果有,不會覆蓋
# map_size定義最大儲存容量,單位是kb,以下定義1TB容量
env
=
lmdb.
open
(
"./train"
,map_size
=
1099511627776
)
env.close()
|
2. LMDB數據的添加、修改、刪除
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
# -*- coding: utf-8 -*-
import
lmdb
# map_size定義最大儲存容量,單位是kb,以下定義1TB容量
env
=
lmdb.
open
(
"./train"
, map_size
=
1099511627776
)
txn
=
env.begin(write
=
True
)
# 添加數據和鍵值
txn.put(key
=
'1'
, value
=
'aaa'
)
txn.put(key
=
'2'
, value
=
'bbb'
)
txn.put(key
=
'3'
, value
=
'ccc'
)
# 通過鍵值刪除數據
txn.delete(key
=
'1'
)
# 修改數據
txn.put(key
=
'3'
, value
=
'ddd'
)
# 通過commit()函數提交更改
txn.commit()
env.close()
|
3. 查詢lmdb數據庫內容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
# -*- coding: utf-8 -*-
import
lmdb
env
=
lmdb.
open
(
"./train"
)
# 參數write設置為True才可以寫入
txn
=
env.begin(write
=
True
)
############################################添加、修改、刪除數據
# 添加數據和鍵值
txn.put(key
=
'1'
, value
=
'aaa'
)
txn.put(key
=
'2'
, value
=
'bbb'
)
txn.put(key
=
'3'
, value
=
'ccc'
)
# 通過鍵值刪除數據
txn.delete(key
=
'1'
)
# 修改數據
txn.put(key
=
'3'
, value
=
'ddd'
)
# 通過commit()函數提交更改
txn.commit()
############################################查詢lmdb數據
txn
=
env.begin()
# get函數通過鍵值查詢數據
print
txn.get(
str
(
2
))
# 通過cursor()遍歷所有數據和鍵值
for
key, value
in
txn.cursor():
print
(key, value)
############################################
env.close()
|
4. 讀取已有.mdb文件內容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
# -*- coding: utf-8 -*-
import
lmdb
env_db
=
lmdb.Environment(
'trainC'
)
# env_db = lmdb.open("./trainC")
txn
=
env_db.begin()
# get函數通過鍵值查詢數據,如果要查詢的鍵值沒有對應數據,則輸出None
print
txn.get(
str
(
200
))
for
key, value
in
txn.cursor():
#遍歷
print
(key, value)
env_db.close()
|
二、進階
LMDB 介紹
LMDB 全稱為 Lightning Memory-Mapped Database,就是非常快的內存映射型數據庫,LMDB使用內存映射文件,可以提供更好的輸入/輸出性能,對於用於神經網絡的大型數據集( 比如 ImageNet ),可以將其存儲在 LMDB 中。
因為最開始 Caffe 就是使用的這個數據庫,所以網上的大多數關於 LMDB 的教程都通過 Caffe 實現的,對於不了解 Caffe 的同學很不友好,所以本篇文章只講解 LMDB。
LMDB屬於key-value數據庫,而不是關系型數據庫( 比如 MySQL ),LMDB提供 key-value 存儲,其中每個鍵值對都是我們數據集中的一個樣本。LMDB的主要作用是提供數據管理,可以將各種各樣的原始數據轉換為統一的key-value存儲。
LMDB效率高的一個關鍵原因是它是基於內存映射的,這意味着它返回指向鍵和值的內存地址的指針,而不需要像大多數其他數據庫那樣復制內存中的任何內容。
LMDB不僅可以用來存放訓練和測試用的數據集,還可以存放神經網絡提取出的特征數據。如果數據的結構很簡單,就是大量的矩陣和向量,而且數據之間沒有什么關聯,數據內沒有復雜的對象結構,那么就可以選擇LMDB這個簡單的數據庫來存放數據。
LMDB的文件結構很簡單,一個文件夾,里面是一個數據文件和一個鎖文件。數據隨意復制,隨意傳輸。它的訪問簡單,不需要單獨的數據管理進程。只要在訪問代碼里引用LMDB庫,訪問時給文件路徑即可。
用LMDB數據庫來存放圖像數據,而不是直接讀取原始圖像數據的原因:
- 數據類型多種多樣,比如:二進制文件、文本文件、編碼后的圖像文件jpeg、png等,不可能用一套代碼實現所有類型的輸入數據讀取,因此通過LMDB數據庫,轉換為統一數據格式可以簡化數據讀取層的實現。
- lmdb具有極高的存取速度,大大減少了系統訪問大量小文件時的磁盤IO的時間開銷。LMDB將整個數據集都放在一個文件里,避免了文件系統尋址的開銷,你的存儲介質有多快,就能訪問多快,不會因為文件多而導致時間長。LMDB使用了內存映射的方式訪問文件,這使得文件內尋址的開銷大幅度降低。
LMDB 的基本函數
env = lmdb.open()
:創建 lmdb 環境txn = env.begin()
:建立事務txn.put(key, value)
:進行插入和修改txn.delete(key)
:進行刪除txn.get(key)
:進行查詢txn.cursor()
:進行遍歷txn.commit()
:提交更改
創建一個 lmdb 環境:
# 安裝:pip install lmdb import lmdb env = lmdb.open(lmdb_path, map_size=1099511627776)
lmdb_path
指定存放生成的lmdb數據庫的文件夾路徑,如果沒有該文件夾則自動創建。
map_size
指定創建的新數據庫所需磁盤空間的最小值,1099511627776B=1T。可以在這里進行 存儲單位換算。
會在指定路徑下創建 data.mdb
和 lock.mdb
兩個文件,一是個數據文件,一個是鎖文件。
修改數據庫內容:
txn = env.begin(write=True) # insert/modify txn.put(str(1).encode(), "Alice".encode()) txn.put(str(2).encode(), "Bob".encode()) # delete txn.delete(str(1).encode()) txn.commit()
先創建一個事務(transaction) 對象 txn
,所有的操作都必須經過這個事務對象。因為我們要對數據庫進行寫入操作,所以將 write
參數置為 True
,默認其為 False
。
使用 .put(key, value)
對數據庫進行插入和修改操作,傳入的參數為鍵值對。
值得注意的是,需要在鍵值字符串后加 .encode()
改變其編碼格式,將 str
轉換為 bytes
格式,否則會報該錯誤:TypeError: Won't implicitly convert Unicode to bytes; use .encode()
。在后面使用 .decode()
對其進行解碼得到原數據。
使用 .delete(key)
刪除指定鍵值對。
對LMDB的讀寫操作在事務中執行,需要使用 commit
方法提交待處理的事務。
查詢數據庫內容:
txn = env.begin() print(txn.get(str(2).encode())) for key, value in txn.cursor(): print(key, value) env.close()
每次 commit()
之后都要用 env.begin()
更新 txn(得到最新的lmdb數據庫)。
使用 .get(key)
查詢數據庫中的單條記錄。
使用 .cursor()
遍歷數據庫中的所有記錄,其返回一個可迭代對象,相當於關系數據庫中的游標,每讀取一次,游標下移一位。
也可以想文件一樣使用 with
語法:
with env.begin() as txn: print(txn.get(str(2).encode())) for key, value in txn.cursor(): print(key, value)
完整的demo如下:
import lmdb import os, sys def initialize(): env = lmdb.open("lmdb_dir") return env def insert(env, sid, name): txn = env.begin(write=True) txn.put(str(sid).encode(), name.encode()) txn.commit() def delete(env, sid): txn = env.begin(write=True) txn.delete(str(sid).encode()) txn.commit() def update(env, sid, name): txn = env.begin(write=True) txn.put(str(sid).encode(), name.encode()) txn.commit() def search(env, sid): txn = env.begin() name = txn.get(str(sid).encode()) return name def display(env): txn = env.begin() cur = txn.cursor() for key, value in cur: print(key, value) env = initialize() print("Insert 3 records.") insert(env, 1, "Alice") insert(env, 2, "Bob") insert(env, 3, "Peter") display(env) print("Delete the record where sid = 1.") delete(env, 1) display(env) print("Update the record where sid = 3.") update(env, 3