BDB C# API初探
Net framework源代码开放算是微软在开源社区开天辟地了,C#作为其主要载体,承载着微软无限希望。这万里长征的第一步,却引起了开源社区的不少涟漪。作为开源社区首屈一指的数据库,BDB借着东风,乘胜追击,推出了其C# API。在此,笔者抛砖引玉,简述C# API如何使用。
1. 概述
BDB C# API建立在BDB C API的基础上,通过上层C#的接口,调用BDB内核。BDB C# API基于.Net Framework 2.0及以上版本,可运行于32位和64位Windows平台,实例开发环境为Microsoft Visual Studio 2005及以上版本。除了特别说明,下文中提到的编译或运行操作均在Microsoft Visual Studio2005中进行。
BDB C# API打包在BDB源代码里(最新代码可从http://www.oracle.com/technology/software/products/berkeley-db/index.html下载)。在BDB的代码中,BDB C# API的代码主要分布于以下目录:
l <db-4.8.24>\csharp:C# API源代码;
l <db-4.8.24>\libdb_csharp:db.i和db_csharp_wrap.c,C# API和C API方法之间的映射;
l <db-4.8.24>\test\scr037:C# API的测试代码;
l <db-4.8.24>\examples_csharp:使用C# API的实例。
通过编译BDB C# API,在<db-4.8.24>\build_windows\AnyCPU\{release, debug}将得到相应的C#库。
BDB C# API支持绝大多数BDB的功能。此外,BDB C#API接口清晰明了,并提供了C#用户专用的参考的使用手册,源代码,测试及多种实例。因此,使用BDB C# API,可以轻松地实现一个功能完备的数据库应用。
2. 编译
编译BDB C# API本身需要BDB C的动态库。在BDB的源代码中,C# API的解决方案已包含了BDB C工程,因此,编译该解决方案,无须另外编译和指定BDB C动态库。

该解决方案文件包含10个工程,其工程名,内容和相对主目录db\的工程文件路径分别是:
|
工程名 |
内容 |
工程文件路径 |
|
db |
C API |
build_windows\db.vcproj |
|
db_csharp |
SWIG生成的封装文件 |
build_windows\db_csharp.vcproj |
|
db_dotnet |
C# API |
csharp\db_dotnet.csproj |
|
DotNetTest |
C# API测试 |
test\scr037\DotNetTest.csproj |
|
ex_access |
B树存取实例 |
examples_csharp\ex_access\ex_access.csproj |
|
ex_btrec |
使用记录号的b树的实例 |
examples_csharp\ex_btrec\ex_btrec.csproj |
|
ex_env |
环境操作实例 |
examples_csharp\ex_env\ex_env.csproj |
|
ex_repquote |
简单的集群实例 |
examples_csharp\ex_repquote\ex_repquote.csproj |
|
ex_sequence |
序列操作实例 |
examples_csharp\ex_sequence\ex_sequence.csproj |
|
ex_txn |
多线程事务实例 |
examples_csharp\ex_txn\ex_txn.csproj |
上述工程可以在单个工程中编译,亦可在整个BDB C# API的解决方案中编译。
(1) 编译解决方案
BDB C# API解决方案已配置好所有BDB C#动态库所需的库及环境。因此,在配置管理器中选择平台(win32或者x64)和编译模式(debug或者release)后,编译该解决方案,便可生成相应的BDB C动态库,C#动态库以及所有实例的可执行文件。如图2和图3所示,进入配置管理器,选择编译配置、平台及工程项。


(2) 编译单个工程
单独编译表格1中的任一工程,打开其工程文件,编译前,应先编译其依赖项。上述10个工程的工程依赖关系,产生的主要对象及其路径如表格2所示:
|
工程名 |
工程依赖 |
产生对象 |
|
Db |
无 |
build_windows\AnyCPU\{debug, release} \{libdb48.dll, libdb48d.dll}, build_windows\ AnyCPU {debug, release } \{libdb48.lib, libdb48d.lib} |
|
db_csharp |
db |
build_windows\AnyCPU\{debug, release} \{ libdb_csharp48.dll, libdb_csharp48d.dll }, build_windows\AnyCPU\{debug, release } \{ libdb_csharp48.lib, libdb_csharp48d.lib } |
|
db_dotnet |
db, db_csharp |
build_windows\AnyCPU\{debug, release } \ libdb_dotnet48.dll |
|
DotNetTest |
db, db_csharp, db_dotnet |
test\scr037\bin\{debug, release}\DotNetTest.dll |
|
ex_access |
db, db_csharp, db_dotnet |
build_windows\AnyCPU\{debug, release} \ex_access.exe |
|
ex_btrec |
db, db_csharp, db_dotnet |
build_windows\AnyCPU\{debug, release} \ex_btrec.exe |
|
ex_env |
db, db_csharp, db_dotnet |
build_windows\AnyCPU\{debug, release} \ex_env.exe |
|
ex_repquote |
db, db_csharp, db_dotnet |
build_windows\AnyCPU\{debug, release}\ex_repquote.exe |
|
ex_sequence
|
db, db_csharp, db_dotnet |
build_windows\AnyCPU\{debug, release} \ex_sequence.exe |
|
ex_txn
|
db, db_csharp, db_dotnet |
build_windows\AnyCPU\{debug, release} \ex_txn.exe |
3. 运行C# API的单元测试
C# API单元测试基于NUnit框架。NUnit是一个开源的面向所有.Net语言的单元测试框架,提供可视化界面和命令行的支持,可在http://www.nunit.org/index.php下载最新版本。
安装完NUnit后,编译DotNetTest测试工程,在<db-4.8.24>\test\scr037\bin\Debug\得到DotNetTest.dll。在NUnit中运行该测试类库前,需要确定libdb48.dll,libdb_csharp48.dll,libdb_dotnet48.dll都在环境变量或者当前目录下。在NUnit中打开生成的DotNetTest.dll,运行该工程,可得到测试结果。
4. 运行示例程序
BDB C# API解决方案提供了6个实例,它们分别是ex_access,ex_btrec,ex_env,ex_repquote,ex_sequence,ex_txn。在解决方案中编译和运行实例工程,在Visual Studio设置该实例工程为启动工程,若依赖的动态链接库已编译,则编译和运行该实例即可。例如,运行ex_txn,编译C#库得到libdb48.dll,libdb_csharp48.dll,libdb_dotnet48.dll,打开ex_txn工程文件,如图4,编译该工程,运行结果如图5所示。


5. 基于BDB C# API的开发
使用BDB C# API进行基于数据库应用程序的开发,需要libdb48.dll,libdb_csharp48.dll和libdb_dotnet48.dll三个动态链接库。首先,在开发的工程中添加libdb_dotnet48.dll的引用,由于libdb_dotnet48.dll需要调用本地代码得到的libdb48d.dll和libdb_csharp48.dll。因此,libdb48d.dll,libdb_csharp48d.dll应放在本工程路径或者环境变量中,以便运行时加载。开发过程中,在代码中添加BDB C# API的命名空间,即“using BerkeleyDB”,便可使用BDB C# API。本文在此举一例子,打开数据库并插入一条Hello World的记录。代码如下:
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml;
using BerkeleyDB;
class Program {
public static void Main(string[] args) {
BTreeDatabaseConfig btreeDBConfig = new BTreeDatabaseConfig();
btreeDBConfig.Creation = CreatePolicy.ALWAYS;
btreeDBConfig.PageSize = 512;
using (BTreeDatabase btreeDB = BTreeDatabase.Open(
"bdb.db", btreeDBConfig)) {
btreeDB.Put(new DatabaseEntry(BitConverter.GetBytes((int)1)),
new DatabaseEntry(ASCIIEncoding.ASCII.GetBytes("Hello World!")));
KeyValuePair pair = btreeDB.Get(new DatabaseEntry(BitConverter.GetBytes((int)1)));
System.Console.WriteLine(ASCIIEncoding.ASCII.GetString(pair.Value.Data));
}
}
}
运行结果显示如下:
BDB是成熟高效的嵌入式引擎,个人认为它提供的C# API在小到手机等消费终端,大至企业级应用都可以使用上BDB这个成熟高效的数据库引擎。更多的信息请参考
http://www.oracle.com/technology/documentation/berkeley-db/db/programmer_reference/csharp.html,欢迎垂询。
期待更多关于BDB C#方面的文章
Hi 您好:
在使用上述BerkeleyDb的C# Binding时发现个性能问题,当我使用HashDB时插入40W数据,Key为从0到40W,Value为1.5K 的随机字符串,未使用任何参数,数据库大小为1.1G,每次随机读取100条数据,发现读取时间大约为1200毫秒左右,请问速度是否正常,若不常常如何优化?谢谢。
如果改为
如果改为BTreeDatabase 速度大概是800毫秒左右,硬件配置是普通硬盘+2G内存+3.0双核CPU。谢谢。
你好,欢迎关注BDB C# API。从C# API的接口使用来说,请问key和value分别采用何种方式序列化?每次如何随机读取?是否使用了合适的比较方法读取数据?
从数据库性能调优来说,建议在HashDatabaseConfig中调整pagesize和cachesize,具体可参考http://www.oracle.com/technology/documentation/berkeley-db/db/csharp/html/AllMembers_T_BerkeleyDB_HashDatabaseConfig.htm。
感谢您的建议,您给我的文档我正在看,多谢。关于序列化是这样子的:
i:为int32值
s:为String类型
DatabaseEntry key = new DatabaseEntry(BitConverter.GetBytes(i));
DatabaseEntry val = new DatabaseEntry(Encoding.UTF8.GetBytes(s));
但是在查询时只序列化了Key作为查询条件,查询结果并未反序列化,代码如下:
Random ran = new Random(Environment.TickCount);
for (int i = 0; i < 100; i++)
{
int k = ran.Next(400000);
DatabaseEntry key = new DatabaseEntry(BitConverter.GetBytes(k));
KeyValuePair pair = db.Get(key);
}
感谢您的建议,您给我的文档我正在看,多谢。关于序列化是这样子的:
i:为int32值
s:为String类型
DatabaseEntry key = new DatabaseEntry(BitConverter.GetBytes(i));
DatabaseEntry val = new DatabaseEntry(Encoding.UTF8.GetBytes(s));
但是在查询时只序列化了Key作为查询条件,查询结果并未反序列化,代码如下:
Random ran = new Random(Environment.TickCount);
for (int i = 0; i < 100; i++)
{
int k = ran.Next(400000);
DatabaseEntry key = new DatabaseEntry(BitConverter.GetBytes(k));
KeyValuePair pair = db.Get(key);
}
以上,再次感谢。
您好,刚刚按照您说的又做了个测试,把cachesize设为128M,pagesize设为8K,读刚刚没有优化参数创建的库文件速度很慢仍然在1秒钟左右,但是用这两个参数创建了一样的数据库后再读速度相当快,基本上都在16毫秒以下,感谢您的建议,谢谢。不过为什么会发生这种情况,能否告知,谢谢。
@smallcat
读取已经存在的数据库,新设置中pagesize并不起作用,请参考文档http://www.oracle.com/technology/documentation/berkeley-db/db/api_reference/C/dbset_pagesize.html中“If the database already exists when DB->open() is called, the information specified to DB->set_pagesize() will be ignored.”
希望对你有帮助!
您的文档对我很有帮助,谢谢。
已经确认是在插入数据时PageSize设置过小导致OverflowPage所致。所以我针对PageSize与OverflowPage做了个简单的实验:
Key都是Int32类型因此长度均为4bytes
以下是PageSize所能容纳的最大Value长度
PageSize:1024 ValueSize:239
PageSize:2048 ValueSize:495
PageSize:4096 ValueSize:1004
PageSize:8K ValueSize:2020左右
因此好像PageSize所能容纳的数据长度大概是其1/4左右,剩余3/4都被BerkeleyDb所占用?查了些资料也没有详述其间关系。迷惑不解。
不是的,Berkeley DB在每页占用的只有几十个字节。Berkeley DB计算overflow 阀值的方法是:首先,确保每页放得下最小数目的key,默认是2,不可能小于2的了.然后,它假设你的key和data都会很大,所以,都可能overflow,所以这个阀值比较小。
你好
ex_txn 多线程事务实例 examples_csharp\ex_txn\ex_txn.csproj
在这个例子中利用多线程 把PayloadData 以序列化的形式添加进去了
取出来的时候 怎么取出 PayloadData 这个类的对象 利用Get方法
KeyValuePair pair = db.Get(new BerkeleyDB.DatabaseEntry(ASCIIEncoding.ASCII.GetBytes(Key)), txn);
BinaryFormatter formatter = new BinaryFormatter();
MemoryStream ms = new MemoryStream();
ms.Position = 0;
ms.SetLength(pair.Value.Data.Length);
PayloadData put = (PayloadData)formatter.Deserialize(ms);
这样不行 报错 怎么回事呀
已解决了
在put的时候 怎么设置 可以添加重复的key和data
现在我添加重复key 可以 但是 key和data都重复 就报错了 难道不支持吗?
我想实现 添加重复key 和data 覆盖原先来值 怎么实现呀
若数据库有重复记录,应设置其为可重复的,例如在btree打开前,设置其BTreeDatabaseConfig中Duplicates为UNSORTED或者SORTED,可参考http://www.oracle.com/technology/documentation/berkeley-db/db/csharp/html/F_BerkeleyDB_BTreeDatabaseConfig_Duplicates.htm。
若需要在put的时候限制插入记录的重复性,可使用BTreeDatabase.Put(), BTreeDatabase.PutNoDuplicate(), BTreeDatabase.PutNoOverwrite()达到不同的要求,可参考http://www.oracle.com/technology/documentation/berkeley-db/db/csharp/html/Methods_T_BerkeleyDB_BTreeDatabase.htm。
多谢
请问下 我机器32位 程序写好都正常 布置到服务器上报错
The type initializer for ‘BerkeleyDB.Internal.libdb_csharpPINVOKE’ threw an exception
怎么回事呀 环境变量已经配置了 服务器是64位 我按照图2和图3所示 进行编译成64位 放到服务器上的
我按照图2和图3所示 设置成64位 进行编译 项目跳过 什么原因?
@zhu
请问你提到的异常在什么情况下抛出的?
@zhu
按照图2和图3设置成64位后,请检查configuration manager中的build一栏,是否所有需要的项目都选定被编译。在编译时,应右键solution中的rebuild all进行重编译。
你好
我生成一个数组 每个数组里面有6个字段 每个字段的只要2个字母的一个值
生成一百万的数据 数据库文件大小有500M 有什么方式可以设置文件大小的
我设置 PageSize 的大小 好像作用不大 PageSize的值越小 文件反而越大
char[] tempc = new char[] { ‘a’, ‘b’, ‘c’, ‘d’, ‘a’, ‘b’, ‘c’, ‘d’, ‘e’, ‘f’, ‘a’, ‘b’, ‘c’, ‘d’, ‘e’, ‘f’ }
BinaryFormatter formatter = new BinaryFormatter();
MemoryStream ms = new MemoryStream();
formatter.Serialize(ms, o);
Byte[] bytes = ms.GetBuffer();
dbt.Data = bytes;
存10W条 大小还是50M
@乌鸦精
你好,在已有的BDB方法中,文件大小均会根据运行时bdb的操作(例如insert, delete, compact等等)改变,即目前没有方法设置文件大小。
设置pagesize不能直接设置文件大小。一个数据库包括很多页,每一页除了存储一定数量的数据外,还有一些头结构。设置较小的pagesize,数据的有效空间则相对较小,需要的页较多,数据库文件大小不会减小。具体如何设置合适的pagesize可以参考http://www.oracle.com/technology/documentation/berkeley-db/db/programmer_reference/general_am_conf.html#am_conf_pagesize。
影响文件大小的因素有很多。如上段提到的pagesize。如不同的access method页面及记录的插入方式不同,从而占用的空间不同。具体如何选择合适的access method可以参考http://www.oracle.com/technology/documentation/berkeley-db/db/programmer_reference/am_conf_select.html。又如compact操作可以对数据库进行压缩,适当地减小数据库文件,具体可以参考http://www.oracle.com/technology/documentation/berkeley-db/db/api_reference/C/dbcompact.html。
总之,合理地选择access method,设置符合你应用程序特点的pagesize,以及正确的使用API。
ex_txn 多线程事务实例 在这个例子中 inMem 这个变量设置成true 第一次运行没问题 当第一次运行完 这个数据库文件已有数据 在启动运行 db = BTreeDatabase.Open(dbName, dbCfg); 这个时候打开数据库 就报错了 郁闷 照理说 应该加载上次添加过数据的 文件
为什么打不开呢 例子我其他地方都没动 就运行 把inMem设置true 报错 设置false 没事
另外我看 4.8版本支持批量插入和批量删除
C# API 里面 我怎么没找到呢 有这方面的例子吗?
还有 database间外键的约束 我看过以前版本的例子 C# 4.8 API里面我也没找到关于这方面的东西 跪求例子 多谢
@zhu
你方便告知你在什么应用中用到BDB 4.8 C#吗?我给你发过Email,没有得到你的答复…
@chaohuang
给你回复了 mail 查收下 多谢
..
@Emily Fu
5. 批量插入和批量删除
众所周知,通过BDB API读取记录时,可以每次读一条也可以批量读取。在4.8里, 我们又提供了批量插入和删除的功能,从而来提高效率。批量查询可以通过设置DB_MULTIPLE or DB_MULTIPLE_KEY in DBC->get 来实现;而在DB->put() & DB->del() 中加上 DB_MULTIPLE 和 DB_MULTIPLE_KEY 标志来实现批量插入和删除。具体性能提升指 标取决于应用场景和平台。
我仔细看过c# API Database 批量插入和批量删除 不支持?
@Emily Fu
// public byte[][] Data;
/* No Public Constructor */
//internal MultipleDatabaseEntry(DatabaseEntry dbt) {
// byte[] dat = dbt.UserData;
// List tmp = new List();
// uint pos = dbt.ulen – 4;
// int off = BitConverter.ToInt32(dat, (int)pos);
// for (int i = 0; off > 0; off = BitConverter.ToInt32(dat, (int)pos), i++) {
// pos -= 4;
// int sz = BitConverter.ToInt32(dat, (int)pos);
// tmp.Add(new byte[sz]);
// Array.Copy(dat, off, tmp[i], 0, sz);
// pos -= 4;
// }
// Data = tmp.ToArray();
//}
难道这就是要实现 批量的代码?
有了这段代码 既然你们不实现那我就给实现了把
@zhu
日志用于恢复数据库,维护数据的持久性。设置日志为内存存储方式(inmem = true),日志不会存储在磁盘上,数据库不保持其持久性。因此,在你的场景中, 第二次运行时,该环境不能再次被使用。具体可参考文档http: //www.oracle.com/technology/documentation/berkeley-db/db/api_reference/C/envlog_set_config.html 中DB_LOG_IN_MEMORY的说明。
若仍需要内存日志数据库中的数据文件,请在关闭环境前调用DB_ENV->lsn_reset (),则数据文件仍可使用,具体可参考文档http: //www.oracle.com/technology/documentation/berkeley-db/db/api_reference/C/envlsn_reset.html。
@Leasing
批量查询弄出来了?
@Emily Fu
多谢!
4.8 C# API中 简单的集群实例 我做了测试 两天机器同步 性能还算可以插入数据在20毫秒左右 取数据也很快 几乎不用时间
但是 我用三台机器测试 性能就慢下来了 插入数据 需要2000毫秒左右 取数据性能不变 还行
是否里面有参数设置问题? 还是正常?
@Emily Fu
ex_repquote 简单的集群实例 在这个例子中 我启动一个主机 两个客户端 任意重新启动一个客户端 重新启动这个客户端的数据 怎么就没了?可不可以保存下来呢?
如果我数据有几十个G,甚至更多 运行期间一个客户机维护停止,在启动起来 需要从零去同步数据,这样不行吧?
你好
如果我设置了可以写入重复值 那读取的时候怎么操作
希望能给一个写入与读取 重复记录的例子 简单点都可以
我的 mail 是xuyong619@21cn.com
你好 重复读取写入弄明白 现在又遇到一个问题 我在写入数据的时候大概是120W 文件大小是1G 如果是在一个循环体里写入 大概写到50W的 500多M的时候写入就很慢了 如果我换个方式写入 每次都只写入8W左右 已追加的方式写入到一个文件里 速度就很快 每次速度很平均 请问这是怎么回事 我该如何调整
@乌鸦精
你好,如果数据库允许重复值或者有序重复值,可以使用DB句柄或者DBcursor句柄读取这些重复值。具体根据你的需求,使用其相应的flags。请参考http://www.oracle.com/technology/documentation/berkeley-db/db/api_reference/C/dbget.html的DB->get()和http://www.oracle.com/technology/documentation/berkeley-db/db/api_reference/C/dbcget.html的DBcursor->get()。
如果你在使用C# API,请根据你使用何种数据库(BTreeDatabase, HashDatabase, QueueDatabase, RecnoDatabase),选择其相应的成员函数。
你好:
我这样操作获得多条记录 (数据集比较大)为什么全部操作完了内存不释放
mBtreeDB.GetMultiple(keyT, 10240)
mBtreeDB.Close();
mBtreeDB.Dispose();
mBtreeDB = null;
mBtreeConfig = null;
GC.Collect();
也都释放不了 他占据的(十多G)内存
其他的查询唯一 和游标都没此问题
请问是怎么回事
我现在处理几千万数据效率还是不错的 比之前使用SQL2005要快不少
注明下
我是开启的线程完成的
在任务管理器里面也都看不到那占用的内存
除了关闭程序 可以释放完 没有其他的办法
@乌鸦精
你好,请问除了GetMultiple以外,你的进程还进行了什么操作?
请问c# Berkeley DB API 什么时候能支持批量插入
我在一个线程里有 一个 Cursor 另外还开启了3个查询(两个查询唯一的 一个查询多行的)还有就是写入lucene 因为我的数据量比较大 打开一个连接就没关闭 到最后数据操作完了 才选择关闭
最多可以开启16个线程
还有一个比较严重的问题是 会造成内存写入错误导致服务器重启
重启也不是每次发生 有时候会有时候不会 我做了很多测试 觉得问题有可能就出在 mBtreeDB.GetMultiple(keyT, 10240) 其实我每次查询的数据操作完成以后 根本不需要放在内存里 不会重复使用的 怎么才能做到使用完了就释放掉内存里的数据
我现在做了个操作就是读取了10W条数据后 关闭数据库重新打开连接(其实是没有效果的) 就算所有操作都完成了关闭所有连接都不会释放内存 我只能在程序里做到所有操作完成后 程序自动关闭 然后另外写一个监控程序 判断进程里的这个程序不存在 再开启 如果不导致服务器意外重启 这个项目基本上可以应用了 比以前操作SQLSERVER效率上提高了百分之80以上
服务器的是64位的 重启的时候物理内存还有10G左右 BDB大概占了10G CPU是16核的 使用了大概百分之40
还有就是在数据写入的时候会造成数据文件损坏 有什么补救的方式吗
如果可能 我把代码发给你
谢谢了
CPU使用百分之40是开启16个线程 最高峰的时候 一般是20到30左右
你好,我们会做进一步的测试,确定你所描述的场景。
谢谢!
您好,我在使用JoinCursor 的时候,调用Move,或MoveNextItem 会抛出异常
page 0: illegal page type or format
PANIC: Invalid argument
不知道为什么,示例完全是参照 测试代码中的JoinCursorTest.TestMoveJoinCursor,没有做任何改动。使用SecondaryCursor没问题,能行告诉我为什么吗?
//这里抛出异常,不能遍历
foreach (KeyValuePair pair in joinCursor)
{
/*
* Confirm that the key got by join cursor has 0 at
* its highest byte and 1 at its lowest byte.
*/
Assert.AreEqual(0, pair.Key.Data[pair.Key.Data.Length - 1]);
Assert.AreEqual(1, pair.Key.Data[0]);
}
能否提供更多的使用场景和代码以便分析。
强烈建议 :提供更多的代码使用例子
我的邮箱是:weis@yahoo.cn
有些问题想咨询您?期待回音!
我的邮箱是:weis2007@yahoo.cn
有些问题想咨询您?期待回音!
问题:Berkeley DB中有很多条数据,其中KEY为“001”的有三条,它们分别对应不同的VALUE值;请问怎么能从数据库中只读取KEY为001的数据,如果用游标的话,应该怎么读?谢谢!
@zhu
在x64上编译了吗?后来如何解决的?能不能共享一下。
我手头没有x64的环境啊。
官方如果直接有下载就好了…