Perl KV数据库模块-DBM::Deep
DBM::Deep是用纯Perl实现的支持事务、多层级的哈希/数组数据库管理模块这是一个独特的单数据K/V文件数据库模块,用纯perl从头开发。真正的多级hash/数组支持(不像MLDBM,它是伪造的)、混合运行OO/tie()调用、跨平台的支持FTP文件、ACID事务,且速度非常快。可以处理数以百万计的键值对和无限的水平层级且没有显的缓速,不是基于C的DBM的包装。兼容Unix、Mac OS X和Windows平台。
版本间的差异
2.0000在文件地层引入了对Unicode支持,这就需要改变文件格式。但是仍然支持版本1.0003格式,因此我们添加了一个db_version()方法。如果使用的是旧格式的数据库,则必须对其进行升级才能获得Unicode支持。
1.0020引入了由不同类型的文件存储以支持的不同引擎,有原始存储(称为“文件”)和数据库存储(称为“DBI”)。可通过“插件”章节了解更多信息。
1.0000与以前的版本有很大的文件格式差异。可通过执行utils/upgrade_db.pl来取得一个向后兼容层。由1.0000或更高版本创建的文件与使用早期版本不兼容。
设置
可使用OO(面向对象)或Tie(绑定)两种方式来对其进行使用。
OO方式
调用new()方法
my $db = DBM::Deep->new( "foo.db" );
#支持锁定与自动将缓冲区的内容刷入硬盘
my $db = DBM::Deep->new(file => "foo.db",locking => 1, autoflush => 1);
#构建一个数组类型的KV库,而非默认的哈希结构(TYPE_HASH)
my $db = DBM::Deep->new(file => "foo.db",type => DBM::Deep->TYPE_ARRAY);
Tie方式
这是Perl独有的一种方式,因此将一些函数如lock()与unlock()做为一种隐式调用。
my %hash;
my $db = tie %hash, "DBM::Deep", "foo.db";
my @array;
my $db = tie @array, "DBM::Deep", "bar.db";
或与OO方式结合起来使用
tie %hash, "DBM::Deep", {file => "foo.db",locking => 1,autoflush => 1};
额外的选项
type
就只支持两种,当然Perl也就这两种原生的数据类型,上文有述。
DBM::Deep->TYPE_HASH
DBM::Deep->TYPE_ARRAY
autoflush
指令字,指定是否自动将数据刷向硬盘中,默认为1,很明显这会降低性能,在多进程使用的情形下要考虑使用锁来保证事务。
filter_*
可参考 "过滤器" 章节。
num_txns
同时运行时支持的事务数量,默认为1,最大可为255;事务量设定的越大,文件增长的更快更大。该值一般取决于有多少进程同时操作数据库,例如,有5个进程同时在调用begin_work方法时,则该值至少应该被设定为6。
max_buckets
桶的最大值,用于在重新索引前决定其中的条目数量,设定的越大将会导致文件也越大,但可得到不错的性能;默认为16,最大为256,但不建议超过64。
data_sector_size
看上去似乎与磁盘的扇区大小相关联的参数?与性能相关,设定过大会浪费空间而过小会导致一些性能问题。最小为64最大为256字节,默认为64字节。
pack_size
实为指定数据库文件的大小,上文已经说过,它是一个单文件的数据库。因此在硬盘上有文件大小相关的指定(此参数很可能与32或64位的操作系统相关),目前有3个可指定的值:
1)small:使用了2字节的偏移量,文件最大不能超过65KB,不知道这样设定有何意义?
2)medium(默认):使用了4字节的偏移量,文件最大不能超过4GB,能适用大部分场景。一旦超过脚本会报错。
3)large:使用了8字节的偏移量,文件最大不能超过16XB,仅能用在64位的系统与Perl下。
external_refs
暂不明白该参数的意义。
TIE接口
Hashes
熟悉Perl的方式对这种方法会非常容易上手:
my $db = DBM::Deep->new( "foo.db" );
$db->{mykey} = "myvalue";
$db->{myhash} = {};
$db->{myhash}->{subkey} = "subvalue";
say $db->{myhash}->{subkey};
foreach my $key (keys %$db){
print "$key: " . $db->{$key} . "\n";
}
如果数据库很大,这种方式就变得不那么友好了,会对内存造成使用上压力同时影响速度。因此使用while与each才是比较合理的方式:
while (my ($key, $value) = each %$db){
print "$key: $value\n";
}
但不能这样使用:
while (my ($key, $value) = each %{$db->{foo}}){...}
Arrays
很明显,这是将数据库当作数组来使用,因此push(), pop(), shift(), unshift(), splice()这些作用在数组上的函数都能用上了。
my $db = DBM::Deep->new(file => "foo-array.db",type => DBM::Deep->TYPE_ARRAY);
$db->[0] = "foo";
push @$db, "bar", "baz";
unshift @$db, "bah";
my $last_elem = pop @$db; # baz
my $first_elem = shift @$db; # bah
my $second_elem = $db->[1]; # bar
my $num_elements = scalar @$db;
OO接口
可供使用的函数有:put(), get(), exists(), delete() 与 clear()。fetch() 与 store() 其实是 put() 与 get() 的别名。
new()/clone():结构体复制函数
put()/store():存入键值对到数据库中
get()/fetch():从数据库取得键值对,使用方式如下:
my $value = $db->get("foo"); # for hashes
my $value = $db->get(1); # for arrays
exists():从数据库中查询指定键或数组索引是否存在,使用方式如下:
if($db->exists("foo")){ print "yay!\n"; } # for hashes
if($db->exists(1)){ print "yay!\n"; } # for arrays
delete():从数据库中删除指定键或对数组指定索引进行删除,同样该操作函数作用于数组时会有性能上问题,数组内容越大越明显
clear():清空哈希或数组,直接调用,无返回值
lock()/unlock()/lock_exclusive()/lock_shared():参考"LOCKING"章节
optimize():对空间进行优化,重新利用空间与内部数据整理,压缩等,在1.0003版本之后支持
import():该函数可能与字面意思有些出入,更类似于put函数
export():在内存中导出完整的数据结构
begin_work()/commit()/rollback():事务相关的函数,参考"TRANSACTIONS"章节
supports($option):向DBM系统中查询其是否支持相关的功能,目前有两个参数可选:
transactions
unicode
db_version():返回当前使用的数据库版本
Hashes
DBM提供了两个函数:first_key()与next_key()来处理哈希数组。
first_key():返回形式上第一个键
my $key = $db->first_key();
next_key():返回下一个键,如果已经取完则返回undef
$key = $db->next_key($key);
my $db = DBM::Deep->new( "foo.db" );
$db->put("foo", "bar");
print "foo: " . $db->get("foo") . "\n";
$db->put("baz", {}); # new child hash ref
$db->get("baz")->put("buz", "biz");
print "buz: " . $db->get("baz")->get("buz") . "\n";
my $key = $db->first_key();
while ($key){
print "$key: " . $db->get($key) . "\n";
$key = $db->next_key($key);
}
if ($db->exists("foo")){ $db->delete("foo"); }
Arrays
可供使用的函数有:length(), push(), pop(), shift(), unshift(), splice()
这些函数跟作用在数组上时相同。
my $db = DBM::Deep->new(file => "foo.db",type => DBM::Deep->TYPE_ARRAY);
$db->push("bar", "baz");
$db->unshift("foo");
$db->put(3, "buz");
my $len = $db->length();
print "length: $len\n"; # 4
for (my $k=0; $k<$len; $k++) {
print "$k: " . $db->get($k) . "\n";
}
$db->splice(1, 2, "biz", "baf");
while (my $elem = shift @$db) {
print "shifted: $elem\n";
}
锁
my $db = DBM::Deep->new(file => "foo.db",locking => 1);
与文件所在的文件系统相关,更多信息可参考操作系统中的文件系统锁。
显式锁定
通过调用lock_exclusive()方法(用于写入时)或lock_shared()方法(用于读取时)完成的。这对于诸如计数器之类的东西特别有用,在计数器中,当前值需要先获取,递增之后再次存储。
$db->lock_exclusive();
my $counter = $db->get("counter");
$counter++;
$db->put("counter", $counter);
$db->unlock();
# or...
$db->lock_exclusive();
$db->{counter}++;
$db->unlock();
导入/导出
导入:将外部的数据类型结构引入到数据库中,支持数组与哈希两种方式
my $struct = {
key1 => "value1",
key2 => "value2",
array1 => [ "elem0", "elem1", "elem2" ],
hash1 => {
subkey1 => "subvalue1",
subkey2 => "subvalue2"
}
};
my $db = DBM::Deep->new( "foo.db" );
$db->import( $struct );
print $db->{key1} . "\n"; # prints "value1"
导出:将数据库中的内容导出到hashref中,这在内存中完成,当数据库过大时对内存是一种考验,这是深度复制,clone()函数亦是如此。
my $copy = $db->clone();
my $db = DBM::Deep->new( "foo.db" );
$db->{key1} = "value1";
$db->{key2} = "value2";
$db->{hash1} = {};
$db->{hash1}->{subkey1} = "subvalue1";
$db->{hash1}->{subkey2} = "subvalue2";
my $struct = $db->export();
print $struct->{key1} . "\n"; # prints "value1"
过滤器
DBM::Deep有许多钩子函数,可以在其中自定义函数来对传入或传出的数据执行过滤处理。这是扩展引擎、实现实时压缩或加密等功能的完美方法。筛选应用于基本DB级别和所有子哈希/数组。可以在首次构造DBM::Deep对象时指定过滤器挂钩,也可以随时调用set_filter()方法。有四个可用的过滤器挂钩函数。
set_filter()
此方法采用两个参数--过滤器类型和过滤器子引用。这四种类型是:
filter_store_key
每当存储哈希键时,都会调用此过滤器。它被传递给传入的键,并期望返回一个已转换的键。
filter_store_value
每当存储哈希键或数组元素时,都会调用此过滤器。将传入值传递给它,并期望返回转换后的值。
filter_fetch_key
每当获取哈希键时(即通过first_key()或next_key()),都会调用此过滤器。它被传递转换后的密钥,并返回期望的普通密钥。
filter_fetch_value
每当获取哈希键或数组元素时,都会调用此过滤器。它被传递转换后的值,并返回期望的普通值。
有两种设置的过滤器:
my $db = DBM::Deep->new(
file => "foo.db",
filter_store_value => \&my_filter_store,
filter_fetch_value => \&my_filter_fetch
);
# or...
$db->set_filter( "store_value", \&my_filter_store );
$db->set_filter( "fetch_value", \&my_filter_fetch );
如果要删除过滤器,请将函数引用设置为undef:
$db->set_filter( "store_value", undef );
错误处理
建议使用eval来进行错误处理。
低级访问函数
目前有两个函数可用,得到其文件操作句柄
my $fh = $db->_fh();
再次修改所创建对象的一些属性,如锁定与解锁
my $file_obj = $db->_storage();
循环引用
Deep完全支持循环引用。这意味着可以有一个指向父对象的嵌套哈希键或数组元素,此关系存储在DB文件中,并在会话之间保留。
my $db = DBM::Deep->new( "foo.db" );
$db->{foo} = "bar";
$db->{circle} = $db; # ref to self
print $db->{foo} . "\n"; # prints "bar"
print $db->{circle}->{foo} . "\n"; # prints "bar" again
这也可以与数组和哈希引用一起工作。因此以下工作如预期:
$db->{foo} = [ 1 .. 3 ];
$db->{bar} = $db->{foo};
push @{$db->{foo}}, 42;
is( $db->{bar}[-1], 42 ); # Passes
事务
从1.00开始,DBM::Deep就支持ACID事务,每个DBM::Deep对象都是完全事务就绪的,不必显示启用它。必须指定可以同时运行多少个事务(q.v. "num_txns")。
增加了三种新方法来支持它们,它们是:
begin_work()
这将启动一个事务。
commit()
这会将事务中所做的更改应用于主线并结束事务。
rollback()
这将丢弃事务中对主线所做的更改,并结束事务。
DBM::Deep中的事务是使用MVCC方法的变体完成的,与MySQL中InnoDB引擎使用的方法相同。
迁移
截至1.0000,文件格式已更改。为了帮助升级,在CPAN发行版中提供了一个迁移脚本:utils/upgrade_udb.pl。
注意:此脚本未安装到系统上,因为它包含当前版本之前的每个版本的副本。
从版本2.0000开始,可以读取由旧版本返回到1.0003的数据库,但除非先升级数据库,否则新功能可能不可用。
关于大数组
当心对大型数组使用shift()、unshift()或splice()。这些函数会导致数组中的每个元素移动,这可能在DBM::Deep上发生,因为每个元素都必须从磁盘中提取,然后再次存储在不同的位置。这将在未来的版本中来解决。官方都不建议数组中使用这些函数,确实在笔者的实测中效率不是令人满意。
最新版本:2.0016
项目主页:https://metacpan.org/release/DBM-Deep