📄 00000016.htm
字号:
17 11gamma:20:8 <BR>$ cat db4.dat <BR>data1 <BR>Data for beta <BR>Record3 <BR>#include "db.h" <BR>int <BR>main(void) <BR>{ <BR> DB *db; <BR> if ( (db = db_open("db4", O_RDWR | O_CREAT | O_TRUNC, <BR> FILE_MODE)) == NULL) <BR> err_sys("db_open error"); <BR> if (db_store(db, "Alpha", "data1", DB_INSERT) != 0) <BR> err_quit("db_store error for alpha"); <BR> if (db_store(db, "beta", "Data for beta", DB_INSERT) != 0) <BR> err_quit("db_store error for beta"); <BR> if (db_store(db, "gamma", "record3", DB_INSERT) != 0) <BR> err_quit("db_store error for gamma"); <BR> db_close(db); <BR> exit(0); <BR>} <BR>程序16.1 建立一个数据库并向其写三条记录 <BR> 为了使这个例子简单,我们将每个ptr字段的大小定为4个ASCII字符,将Hash <BR>表的大小(Hash链的条数)定为3。由于每一个ptr记录的是一个文件偏移量,所以 <BR>4个ASCII字符限制了一个索引文件或数据文件的大小最多只能为10000字节。在后 <BR>面的16.8节做一些性能方面的测试时,我们将ptr字段的大小设为6(这样文件的大 <BR>小可以达到1000000字节),将Hash表的大小设为100。 <BR> 索引文件的第一行为 <BR> 0 53 35 0 <BR> 分别为空闲链表指针(0,表示空闲链表为空),和三个Hash链表的指针:53 <BR>,35和0。下一行 <BR> 0 10Alpha:0:6 <BR> 显示了一条索引记录的结构。第一个4字符的字段(0)表示这一条记录是此H <BR>ash链的最后一条。下一个4字符的字段(10)为idx len,表示此索引记录剩下部 <BR>分的长度。我们通过两个read操作来读取一条索引记录:第一个read读取这两个固 <BR>定长度的字段(chain ptr和idx len),然后再根据idx len来读取后面的不定长 <BR>部分。剩下的三个字段为:key(主键)、dat off(在数据文件中的偏移量)和d <BR>at len(数据记录的长度)。这三个字段用分隔符隔开,在这里我们使用的是分号 <BR>。由于此三个字段都是不定长的,所以我们需要一个专门的分隔符,而且这个分隔 <BR>符不能出现在主键中。最后用一个回车符结束这一条索引记录。由于在idx len中 <BR>已经有了记录的长度,所以这个回车符并不是必须的,加上回车符是为了把各条索 <BR>引记录分开,这样就可以用标准的Unix工具如cat和more来查看索引文件。key是我 <BR>们将记录加入数据库时选择的主键。数据记录在数据文件中的偏移为0,长度为6。 <BR>从数据文件中我们看到数据记录确实从0开始,长度为6个字节。(与索引文件一样 <BR>,我们在这里自动在每条数据记录的后面加上一个回车符,以便于使用Unix工具。 <BR>在调用db_fetch时,此回车符不作为数据返回。) <BR> 如果在这个例子中跟踪三个Hash链,我们看到第一条Hash链上的第一条记录的 <BR>偏移量是53(gamma)。这条链上的下一条记录的偏移量为17(Alpha),并且是这 <BR>条链上的最后一条记录。第二条Hash链上的第一条记录的偏移量是35(beta),且 <BR>是此链上最后一条记录。第三条Hash链为空。 <BR> 请注意索引文件中索引记录的顺序和数据文件中对应数据记录的顺序与我们在 <BR>程序16.1中调用db_store的顺序一样。由于我们在调用db_open时使用了O_TRUNC标 <BR>志,索引文件和数据文件都被截断,整个数据库相当于从新初始化。在这种情形下 <BR>,db_store将新的索引记录和数据记录添加到对应的文件末尾。在后面我们将看到 <BR>db_store也可以重复使用这两个文件中因删除记录而生成的空间。 <BR> 在这里使用固定大小的Hash表作为索引是一个妥协。当每个Hash链均不太长时 <BR>,这个方法能保证快速的查找。我们的目的是能够快速地查找任意的键,同时又不 <BR>使用太复杂的数据结构,如B树或动态可扩充Hash表。动态可扩充Hash表的优点是 <BR>能保证仅用两次磁盘操作就能找到数据记录(详见Selter和Yigit[1991])。B树能 <BR>够让我们用键的顺序来遍历数据库(采用Hash表的db_nextrec函数就做不到这一点 <BR>)。 <BR>16.5 集中式或非集中式 <BR> 当有多个进程访问数据库时,有两种方法实现库函数。 <BR> 1.集中式 由一个进程作为数据库管理者,所有的数据库访问工作由此进程完 <BR>成。库函数通过IPC机制与此中心进程进行联系。 <BR> 2.非集中式 每个库函数独立的申请并发控制(加锁),然后调用它自己的I/ <BR>O函数。 <BR> 使用这两种技术的数据库都有。在Unix系统中的潮流是使用非集中式方法。如 <BR>果有适当的加锁函数,因为避免使用了IPC,那么非集中式方法一般要快一些。图 <BR>16.2描绘了集中式方法的操作。 <BR> 在图中,我们特意表示出IPC象绝大多数Unix的消息传送一样需要经过操作系 <BR>统内核(在14.9节中的共享内存不需要这种经过内核的拷贝)。我们看到,在集中 <BR>方式下,中心控制进程将记录读出,然后通过IPC机制将数据送给请求进程。注意 <BR>到中心控制进程是唯一的通过I/O操作访问数据库文件的进程。 <BR>图16.2 集中式数据库访问 <BR>图16.3 非集中式数据库访问 <BR> 集中式的优点是能够根据需要来对操作模式进行控制。例如,我们能够通过中 <BR>心进程给不同的进程赋予不同的优先级,而用非集中式方法这是很难做到的。在这 <BR>种情况下我们只能依赖于操作系统核心的磁盘I/O调度策略和加锁策略(如当三个 <BR>进程同时等待一个锁开锁时,哪个进程下一个得到锁)。 <BR> 图16.3描绘了非集中式方法,在本章我们的实现就是采用这种方法。使用库函 <BR>数访问数据库的用户进程是平等的,它们通过使用记录锁机制来实现并发控制。 <BR>16.6 并发 <BR> 由于很多系统的实现都采用两个文件(一个索引文件和一个数据文件)的方法 <BR>,所以我们也使用这种方法,这要求我们能够控制对两个文件的加锁。有很多方法 <BR>可用来对两个文件进行加锁。 <BR>粗锁 <BR> 最简单的加锁方法是将这两个文件中的一个进行作为锁,并要求调用者在对数 <BR>据库进行操作前必须获得这个锁。我们将这称为粗锁。比方说,我们可以认为一个 <BR>进程对索引文件的第一个字节加了读锁后,才能获得读整个数据库的权力。一个进 <BR>程对索引文件的第一个字节加了写锁后,才获得修改整个数据库的权力。我们可以 <BR>使用Unix的记录锁机制来控制每次可以有多个读进程,而只能由一个写进程(见图 <BR>12.2)。db_fetch和db_nextrec函数将获得读锁,而db_delete、db_store以及db <BR>_open则获得写锁。(db_open要获得写锁的原因是如果要创建新文件的话,要在索 <BR>引文件前端建立空闲区链表以及Hash链表。) <BR> 粗锁的问题是限制了最大程度的并发。用粗锁时,当一个进程向一条Hash链中 <BR>加一条记录时,其它进程无法访问在另一条Hash链上的记录。 <BR>细锁 <BR> 下面我们用称为细锁的方法来加强粗锁以提高并发度。我们要求一个读进程或 <BR>写进程在操作一条记录前必须先获得此记录所在的Hash链的读锁或写锁。我们允许 <BR>对一条Hash链同时可以有多个读进程,而只能有一个写进程。另外,一个写进程在 <BR>操作空闲区链表(如db_delete或db_store)前,必须获得空闲区链表的写锁。最 <BR>后,当db_store向索引文件或数据文件加一条新记录时,必须获得对应文件相应区 <BR>域的写锁。 <BR> 我们期望细锁能比粗锁提供更高的并发度。在16.8节我们将给出一些实际的比 <BR>较测试结果。16.7节给出我们的采用细锁的实现,并详细讨论了锁的实现(粗锁是 <BR>我们实现的简化)。 <BR> 在源文件中,我们直接调用read,readv,write和writev。我们没有使用标准 <BR>I/O函数库。虽然使用标准I/O函数库也可以使用记录锁,但是需要非常复杂的缓存 <BR>管理。我们不希望例如fgets返回的数据是10分钟之前读入标准I/O缓存的,而被另 <BR>一个进程在5分钟之前修改了。 <BR> 我们对并发讨论依据的是数据库函数库的简单的需求。商业系统一般有更多的 <BR>需要。关于并发的更多的细节可以参看Data[1982]的第三章。 <BR>16.7 源码 <BR>我们从程序16.2中的头文件db.h开始。所有函数以及调用此函数库的用户进程都包 <BR>含这一头文件。 <BR>#include <sys/types.h> <BR>#include <sys/stat.h> /* open() & db_open() mode */ <BR>#include <fcntl.h> /* open() & db_open() flags */ <BR>#include <stddef.h> /* NULL */ <BR>#include "ourhdr.h" <BR> /* flags for db_store() */ <BR>#define DB_INSERT 1 /* insert new record only */ <BR>#define DB_REPLACE 2 /* replace existing record */ <BR> /* magic numbers */ <BR>#define IDXLEN_SZ 4 /* #ascii chars for length of index record */ <BR>#define IDXLEN_MIN 6 /* key, sep, start, sep, length, newline */ <BR>#define IDXLEN_MAX 1024 /* arbitrary */ <BR>#define SEP ':' /* separator character in index record */ <BR>#define DATLEN_MIN 2 /* data byte, newline */ <BR>#define DATLEN_MAX 1024 /* arbitrary */ <BR> /* following definitions are for hash chains and free list chain <BR> in index file */ <BR>#define PTR_SZ 6 /* size of ptr field in hash chain */ <BR>#define PTR_MAX 999999 /* max offset (file size) = 10**PTR_SZ - 1 */ <BR>#define NHASH_DEF 137 /* default hash table size */ <BR>#define FREE_OFF 0 /* offset of ptr to free list in index file */ <BR>#define HASH_OFF PTR_SZ /* offset of hash table in index file */ <BR>typedef struct { /* our internal structure */ <BR> int idxfd; /* fd for index file */ <BR> int datfd; /* fd for data file */ <BR> int oflag; /* flags for open()/db_open(): O_xxx */ <BR>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -