深入理解mysql之BDB系列(1)---BDB相关基础知识(摘自老杨)

时间:2022-03-15 06:35:46
一:BDB体系结构 1.1.BDB体系结构 BDB整体的体系结构如图1.1所示,包含五个子系统(见图1.1中相关数)。1)数据存取子系统,2)事务子系统,3)锁子系统,4)内存池管理子系统,5)日志子系统。 在一个应用程序中,并不一定需要完全具备这5大子系统。 如果程序只使用了数据存取子系统,它的体系结构如图1.2。在图1.2中,我们只使了两个子系统:数据存取以及内存池子系统。(备注:另外三个子系统在BDB系统中隐式调用)

深入理解mysql之BDB系列(1)---BDB相关基础知识(摘自老杨)

 深入理解mysql之BDB系列(1)---BDB相关基础知识(摘自老杨)

1.2.子系统的介绍 1)数据存取子系统    数据存取子系统提供多种存取数据库文件的方法:1)B树、2)HASH文件、3)定长记录以及4)变长记录。    对于这四种不同的存取类型,BDB系统提供几乎一致的接口。比如,要打开一个数据库,都使用DB->open。    有些情况下,为更快的存取效率而不使用事务子系统,数据存取子系统可以单独使用,从而提供更快的存取速度(见图1.2)。   2)事务子系统    事务子系统允许将一系统的操作看作为一个原子操作,或者全部完成,或者全部取消。可以根据应用的具体情况,设置事务的隔离级别。    事务子系统可以单独使用,适用于需要符合事务特性的应用。   3)锁子系统    锁子系统为BDB提供锁机制,为系统提供读、写控制。可以允许多用户同时进行读操作,只允许单用户进行排它的修改操作。通过子系统,申请读、写权限,一旦申请成功,便可进行相关操作。事务子系统利用锁机制来实现多个事务的并发控制。    锁子系统可以被应用程序单独使用,为应用提供一个灵活的、快速的、可设置的锁管理。   4)内存池管理子系统    该子系统为多进程(或多线程)共享存取数据提供可能。需要页数据时,如果数据已经缓冲在内存池中,则直接使用,否则将新的页读到内存池*用户使用。在必要的时候,将修改的页数据进行刷盘操作。    该子系统可以被应用程序单独使用。内存池管理子系统为应用提供灵活的、面向页、数据缓冲管理。   5)日志子系统    该子系统采用先写日志先写数据的策略,用于支持事务子系统进行数据恢复,保证数据一致性。该子系统不能被应用程单独使用,只能做为事务子系统的调用模块。       二:BDB环境(Environment) 2.1.概述     BDB环境是对一个或多个数据库、日志文件以及区域文件的一个封装。区域文件可以理解为共享内存池,用来保存类似缓冲页的BDB环境信息。只有数据库文件是平*立的,可以在不同的byte-order机器上进行移动。日志文件可以在同byte-order的机器上进行移动。但是,区域文件对特定的机器是唯一的,不能与其它类型机器进行日志文件的移动,甚至对于不同版本的操作系统,虽然机器类型相同却也不能移动。     管理BDB环境最简单的方法就是创建一个home目录,在该目录中存储需要共享环境的文件。如果需要使用home目录(比如目录myhome),必须在应用程序运行之前创建好该目录(比如在工作目录下创建一个命令为myhome的文件夹),因为BDB本身不会为用户创建home目录。BDB环境通过目录名进行标识。    一个环境可以被多进程以及多线程共享。一个环境可以引用系统其它目录的资源,应用程序常常选择将资源分布到不同的目录或者磁盘,以此来提升程序性能。尽管如此,在默认情况下,数据库文件、共享区域(包括锁、日志缓冲区、内存池、事务共享内存区域)以及日志文件存储在一个同层次目录中(home目录中,可以包括其它的比如,比如在其中创建一个mydata目录用来存储用户的数据库文件)。    共享同一个环境的所有应用,它们彼此之间是互相信任的。它们能互相访问对方的数据,因为这些数据都是放在共享区域中的。它们共享类似于buffer以及锁之类的资源。与此同时,如果多个应用想要使用同一个数据库,并且需要它们之间保持一致性,则必须共享同一个环境。   2.2.创建BDB环境    BDB环境是通过db_env_create以及DB_ENV->open接口来创建和描述的。在需要定制的地方,比如将日志文件存储到一个单独的磁盘,或者选择一个特定的值做为cache的大小,应用通过环境配置文件或者将参数传递给DB_ENV处理函数来实现定制。  一旦一个环境被创建,被指定相关路径的数据库文件,都将相对环境的home目录来创建。用相对路径允许整个环境的轻易的移动。简化了在不同目录和不同系统中重建和恢复的步骤。 一旦应用程序首先通过db_env_create方法获得一个环境句柄,然后调用DB_ENV->open来创建或加入数据库环境。这里有很多选项你可以在调用DB_ENV->open时来定制你的环境。这些选项大致可以分为四类: 1)子系统初始化选项: 这些标志指明哪些bdb子系统将因为环境被初始化,和哪些操作将自动发生当数据库在环境中被访问的时候。这些标志包括DB_INIT_CDB,DB_INIT_LOCK,DB_INIT_LOG,DB_INIT_MPOOL,以及DB_INIT_TXN。DB_INIT_CDB标志为bdb并发数据存储做初始化工作。其它标志初始化单个子系统。比如,当DB_INIT_LOCK标志被指定,应用程序读写在这个环境中打开的数据库时,将使用locking子系统以确保它们不覆盖对方的对数据的改动。    2)恢复选项: 这些标志包括DB_RECOVER和DB_RECOVER_FATAL,它们表明在环境被打开且在正常使用之前,恢复将要进行。 3)命名选项: 这些标志包括DB_USE_ENVIRON和DB_USE_ENVIRON_ROOT,修改如何在环境给文件命名。 4)混杂选项: 除非前面的三类之外,还有一些混杂选项,比如,DB_CREATE选项使底数据库文件被创建时是必需的。        对于大多数应用,或者仅仅指定DB_INIT_MPOOL,或者指定所有的四个子系统的初始化标志(DB_INIT_MPOOL,DB_INIT_LOCK,DB_INIT_LOG以及DB_INIT_TXN)。     前者只是想简单用一些基本的访问方法以及一个共享缓冲池,没有考虑应用程序或系统出现故障时的可恢复性。后者是一些需要提供可恢复性的应用。在一些罕见的情况下,也有可能是其它的初始化标志的组合。   DB_RECOVER标志在当应用启动运行时做一些必需的数据库恢复的时候被指定。也就是说,在上一次运行过程中如果系统或应用程序出现故障,打算在再次运行前将数据恢复到一致性状态。不过,在没有任何数据需要恢复的情况下,指定这个标志也不为错。  DB_RECOVER_FATAL标志有更特殊的用途。它执行灾难性的数据库恢复,通常需要做一些初始化的安排;也就是说归档日志文件被带回到文件系统。应用程序通常不指定该标志。做为替代,在这种罕见的情况,应该使用db_recover函数。    下面是一个简单的为事务程序打开一个数据库环境的例子:   DB_ENV * db_setup(home, data_dir, errfp, progname)        char *home, *data_dir, *progname;        FILE *errfp; {        DB_ENV *dbenv;        int ret;        /*         * Create an environment and initialize it for additional error         * reporting.         */        if ((ret = db_env_create(&dbenv, 0)) != 0) {               fprintf(errfp, "%s: %s/n", progname, db_strerror(ret));               return (NULL);        }        dbenv->set_errfile(dbenv, errfp);        dbenv->set_errpfx(dbenv, progname);          /*         * Specify the shared memory buffer pool cachesize: 5MB.         * Databases are in a subdirectory of the environment home.         */        if ((ret = dbenv->set_cachesize(dbenv, 0, 5 * 1024 * 1024, 0)) != 0) {               dbenv->err(dbenv, ret, "set_cachesize");               goto err;        }        if ((ret = dbenv->set_data_dir(dbenv, data_dir)) != 0) {               dbenv->err(dbenv, ret, "set_data_dir: %s", data_dir);               goto err;        }          /* Open the environment with full transactional support. */        if ((ret = dbenv->open(dbenv, home, DB_CREATE |            DB_INIT_LOG | DB_INIT_LOCK | DB_INIT_MPOOL | DB_INIT_TXN, 0)) != 0) {               dbenv->err(dbenv, ret, "environment open: %s", home);               goto err;        }          return (dbenv);   err: (void)dbenv->close(dbenv, 0);        return (NULL); }


      2.3.在环境中打开数据库    一旦环境被创建,数据句柄可能在这个环境中打开。这由db_create函数通过指定特定的环境作为参数来实现。    文件命名、数据库操作以及错误处理都将因为这个指定的环境而被做。例如,如果DB_INIT_LOCK或DB_INIT_CDB标志被指定,在环境被创建(create)或被合并(join)时,数据库操作将为应用程序自动地执行所有必要的锁操作。    下面是个简单的例子,在一个环境中打开两个数据库:          DB_ENV *dbenv;        DB *dbp1, *dbp2;        int ret;          dbenv = NULL;        dbp1 = dbp2 = NULL;          /*         * Create an environment and initialize it for additional error         * reporting.         */        if ((ret = db_env_create(&dbenv, 0)) != 0) {               fprintf(errfp, "%s: %s/n", progname, db_strerror(ret));               return (ret);        }          dbenv->set_errfile(dbenv, errfp);        dbenv->set_errpfx(dbenv, progname);          /* Open an environment with just a memory pool. */        if ((ret =            dbenv->open(dbenv, home, DB_CREATE | DB_INIT_MPOOL, 0)) != 0) {               dbenv->err(dbenv, ret, "environment open: %s", home);               goto err;        }          /* Open database #1. */        if ((ret = db_create(&dbp1, dbenv, 0)) != 0) {               dbenv->err(dbenv, ret, "database create");               goto err;        }        if ((ret = dbp1->open(dbp1,            NULL, DATABASE1, NULL, DB_BTREE, DB_CREATE, 0664)) != 0) {               dbenv->err(dbenv, ret, "DB->open: %s", DATABASE1);               goto err;        }          /* Open database #2. */        if ((ret = db_create(&dbp2, dbenv, 0)) != 0) {               dbenv->err(dbenv, ret, "database create");               goto err;        }        if ((ret = dbp2->open(dbp2,            NULL, DATABASE2, NULL, DB_HASH, DB_CREATE, 0664)) != 0) {               dbenv->err(dbenv, ret, "DB->open: %s", DATABASE2);               goto err;        }          return (0);   err: if (dbp2 != NULL)               (void)dbp2->close(dbp2, 0);        if (dbp1 != NULL)               (void)dbp2->close(dbp1, 0);        (void)dbenv->close(dbenv, 0);        return (1); }       2.4.出错处理    db_strerror函数能根据一个bdb的错误返回值返回一个指向错误信息的指针。它可以处理系统的错误也能处理bdb特有的返回值。    例如:   int ret; if ((ret = dbenv->set_cachesize(dbenv, 0, 32 * 1024, 1)) != 0) {        fprintf(stderr, "set_cachesize failed: %s/n", db_strerror(ret));        return (1); }      这里也有两个附加的错误处理函数: DB_ENV->err和DB_ENV->errx。   DB_ENV->err函数追加标准错误字符串到已构造好的消息,而DB_ENV->errx不那样。    错误信息可以通过DB_ENV->set_errpfx被配置成总包含一些固定的前缀(例如,应用程序名称)。    例如:   int ret; dbenv->set_errpfx(dbenv, program_name); if ((ret = dbenv->open(dbenv, home,     DB_CREATE | DB_INIT_LOG | DB_INIT_TXN | DB_USE_ENVIRON, 0))     != 0) {        dbenv->err(dbenv, ret, "open: %s", home);        dbenv->errx(dbenv,            "contact your system administrator: session ID was %d",            session_id);        return (1); }      假设,应用程序名为”my_app”,它企图打开环境目录”/tmp/home”且open函数返回权限错误,出错的信息如下:   my_app: open: /tmp/home: Permission denied. my_app: contact your system administrator: session ID was 2       2.5.DB_CONFIG配置文件    几乎所有可以指定给DB_ENV那些方法的培植信息,也可以通过一个配置文件来指定。如果一命名为DB_CONFIG的文件存在数据库home目录下,它将会按照name value的格式被一行行地读入。    NAME与VALUE之间是通过一个或者多个空格来分割,拖尾的空格将被丢弃。空行或以#开始的行,将被忽略。每一行必须同时指定NAME以及相对应的VALUE。NAME VALUE是通过手动方式进行编辑的。   DB_CONFIG配置文件的目的是允许管理员定制不依赖于应用程序的环境。例如,可以移动log文件以及数据文件到不同的位置,而不用重新编译应用程序。另外,DB_CONFIG文件是当数据库环境被打开时读取的,它可以用来覆盖在那以前配置的规则。例如,可以定义一个更合理的cache大小,来覆盖以前已经编译到程序中的值。     2.6.文件命名    下面介绍几种可能的为bdb指定命名信息的方法: 1) db_home:为DB->open的db_home参数指定一个非NULL值,它的值将会用来作为数据库的home,以后的文件命名都是相对这个路径。 2) DB_HOME:为环境变量DB_HOME指定值,DB_ENV->被调用时,读取这个值,把它作为数据库home,以后的文件命名都是相对这个路径。 3) DB_ENV方法:这里有三个函数可以影响文件命名,DB_ENV->set_data_dir可以为数据库文件指定一个目录。DB_ENV->set_lg_dir可以为log文件指定目录。DB_ENV->set_tmp_dir为创建的临时文件指定目录。例如,一个应用程序可以将数据文件、日志文件分别放到不同的目录中。 4) DB_CONFIG文件:相同的指定给DB_ENV方法的信息,也可以用DB_CONFIG配置文件来指定。       指定路径的优先级从高到底是这样的:DB_ENV,DB_CONFIG,db_home,DB_HOME,default。如果以上的路径为绝对路径,那么home就是那个绝对路径。如果以上的路径为相对路径,那么将根据当前的工作目录计算出home路径。如果什么都没指定,那么默认的将现在的工作目录作为home。   例如: 情况一:将所有的文件都放在目录/a/database下: dbenv->open(dbenv, “/a/database”, flags, mode);   情况二:将临时文件存储到目录/b/temporary,其他文件放在/a/database目录下: dbenv->set_temp_dir(dbenv, “/b/temporary”); dbenv->open(dbenv, “/a/database”, flags, mode);   情况三:将数据文件放在目录/a/database/datadir下,日志文件放在目录/a/database/logdir目录下,其它文件放在目录/a/database下。 dbenv->set_lg_dir(dbenv, ”logdir”); dbenv->set_data_dir(dbenv, “datadir”); dbenv->open(dbenv, “/a/database”, flags, mode);    情况四:将数据文件放在/a/database/data1和/b/data2目录下,其它的文件放在目录/a/database下。任何数据文件都将被创建在/b/data2目录下,因为它是第一个被指定的数据目录。 dbenv->set_data_dir(dbenv, “/b/data2”); dbenv->set_data_dir(dbenv, “data1”); dbenv->open(dbenv, “/a/database”, flags, mode);           2.7.共享内存区域(shared memory regions)    环境中的每个子系统都被一个或多个区域(regions),或大块的内存来描述。区域包括所有的每进程和每线程共享信息(包括mutexs),这些组成了bdb环境。这些区域将在三种类型中的一种中被创建,这取决于指定DB_ENV->open方法的标志:  1)DB_PRIVATE:如果这标志被指定,区域将在每进程(per-process)内存中被创建;也就是说由malloc()返回的内存。 如果有不止一个进程访问环境,这个标志最好不要指定。因为它很有可能导致数据库腐烂(corruption)和一些不可预期的行为。例如,当server应用程序和bdb公共程序(例如,db_archive,db_checkpoint或者db_stat)都期望访问这个环境的时候,最好别指定DB_PRIVATE标志。 2)DB_SYSTEM_MEM:如果这个标志被指定,共享区域将在系统内存中创建而不是在文件中。这是一个可选的机制,为了在多个进程和一个进程中的多个线程共享bdb环境。    Bdb所使用的系统内存潜在地很有用,伴侣任何特殊的进程度过生存周期。因此,附加的清除操作是必要当应用程序出现故障后,因为bdb没办法确认,支撑内存区的系统资源是不是还给了系统。    系统内存的使用是根据计算机体系结构而定的。例如,在一个支持X/Open样式共享内存的系统上,比如UNIX系统,shmget(2)和相似的系统V IPC接口被调用。另外,在VxWork系统中使用系统内存。在这些情况下,一个初始的段ID必须在应用程序中被指定,以确保应用程序不互相覆盖对方的数据库环境,所以段创建的数量部是无限制的增长。可以参考DB_ENV->set_shm_key方法获得更多的信息。    在Windows平台上,使用DB_SYSTEM_MEM标志是有很多问题的。因为该操作系统使用引用计数来自动地清除页共享对象。另外,默认的共享对象的访问权限控制,不同于文件,如果是不同用户使用的多进程,这将导致问题。    3)default:如果没有内存相关的标志指定给DB_ENV->open,被文件系统支撑的内存(虚拟内存)将用来存储这些区域(regions)。在UNIX系统上,bdb库将使用POSIX mmap接口。如果mmap不可用,那么unix shmget接口可能被使用,如果它可用的话。     任何文件系统中创建的用来支撑区域的文件,将在环境的home目录(DB_ENV->open指定)中被创建。这些文件命名为为__db.###(例如,__db.001,__db.002等等)。当区域文件是被文件系统支撑的时候,每一个区域对应一个文件将被创建。当区域文件被系统内存来支撑的时候,只有一个文件将仍然被创建,因此这里必须有一熟知的名字在文件系统中,以便多进程能定位到环境所使用的系统共享内存。  统计在环境中的共享内存区域,可以使用db_stat的-e选项来显示。       2.8.安全(security) 下面是当你写bdb应用程序的时候需要考虑的安全问题。 1) 数据库环境许可(Database environment permissions) 被bdb数据库环境使用的目录,应该有它自己的许可设置,以确保那些没有适当权限的用户不能访问环境里的文件。应用程序添加用户的访问许可时(例如,unix的setuid或segid程序),应该仔细的检查,不允许违法的使用这些许可,例如访问环境中的文件。 2)环境变量(Environment variables) 设置DB_USE_ENVIRON和DB_USE_ENVIRON_ROOT标志,和允许在文件命名时使用环境变量都是危险的。设置这些标志,并使用附加的权限(例如,UNIX setuid或segid程序),将潜在地允许那些正常情况下没有权限的用户读写数据库。 3)文件许可(File permission)    默认地,bdb总是创建所有者和所在组可读写的文件(也就是,S_IRUSR,S_IWUSR,S_IRGRP和S_IWGRP;或者在历史性的UNIX系统上使用八进制模块0660)。创建文件的组的所有权限,是基于系统和目录的,不被bdb进一步指定。 4) 临时支撑文件(Tempprary backing files 如果一个没有命名的数据库被创建,而cache太小以至于在内存中保持这个数据库bdb将创建一个临时的物理文件,必要时使它能把数据库的cache页放到磁盘上。在这种情况下,环境变量,比如TMPDIR可能被用来定位那个临时文件。尽管临时支撑文件被创建成只有所有者可读写(S_IRUSR和S_IWUSR,或八进制模式0600在历史性的UNIX系统上),一些文件系统可能不能充分保护被创建在随机目录中的临时文件不被非法访问。为了绝对安全,应用程序存储敏感数据在未命名的数据库红,应该用DB_ENV->set_tmp_dir方法用已知的许可(known perimissions)指定一个临时目录。