【5】自己写数据库函数库 — 删除一条记录

删除一条记录的接口函数为db_delete,代码如下:

/* 根据键值删除一条记录 */
int db_delete(DBHANDLE h, const char *key)
{
	DB *db = h;
	int rc = 0;

	/* 因为可能要修改记录,所以加写锁 */
	if (_db_find_and_lock(db, key, 1) == 0)
	{
		/* 查找成功,执行删除操作 */
		_db_dodelete(db);
		db->cnt_delok++;
	}
	else
	{
		/* 查找失败 */
		rc = -1;
		db->cnt_delerr++;
	}

	/* _db_find_and_lock中加了锁,这里要解锁 */
	if (un_lock(db->idxfd, db->chainoff, SEEK_SET, 1) < 0)
	{
		printf("%d\n", __LINE__);
		exit(-1);
	}

	return rc;
}

整个函数长度并不长,但内部调用的一些其它函数还是比较复杂的。首先用_db_find_and_lock函数查找需要删除的条目,找到则调用_db_dodelete做实际的删除工作,否则返回-1,表示删除失败。在_db_find_and_lock中加了写锁,只锁住了需要删除条目对应的散列链,而_db_dodelete函数中也对索引文件加了写锁,但锁住的是空闲链表,因为我们要把被删除条目添加到空闲链表中去。下面是_db_dodelete函数的代码:

/* 实际的删除记录的函数
 * 可由db_delete或db_store函数调用
 */
static void _db_dodelete(DB *db)
{
	int i;
	char *ptr;
	off_t freeptr, saveptr;

	/* 将数据缓冲填充空格符,后面在向文件中写的时候还会做调整 */
	for (ptr = db->datbuf, i = 0; i < db->datlen - 1; i++)
		*ptr++ = SPACE;
	*ptr = 0;

	/* 将索引记录填充空格符,后面在向文件中写的时候还会做调整
	 * _db_find_and_lock函数调用了_db_readidx函数
	 * db->idxbuf = "key \0 datoff \0 datlen \0"
	 */
	ptr = db->idxbuf;
	while (*ptr)
		*ptr++ = SPACE;
	/* db->idxbuf = "_ _ _ _ \0 datoff \0 datlen \0" */

	/* 锁住空闲链表,防止多个进程同时删除从而影响空闲链表 */
	if (writew_lock(db->idxfd, FREE_OFF, SEEK_SET, 1) < 0)
	{
		printf("%d\n", __LINE__);
		exit(-1);
	}

	/* 向数据文件中写入空白行,_db_readidx中已经设置好了db->datoff
	 * 这里无需加锁,因为db_delete对这条散列链加了写锁
	 * db->datbuf = "_ _ _ _ _ _ '\0'"
	 */
	_db_writedat(db, db->datbuf, db->datoff, SEEK_SET);

	freeptr = _db_readptr(db, FREE_OFF);	/* freeptr = 0 */
	saveptr = db->ptrval;					/* 下一条索引记录的偏移量 */

	/* 修改要删除条目的索引记录,使它的指向下一条记录的指针指向空闲链表的头节点freeptr
	 * 相当于在空闲链表头插入要删除的条目
	 * db->idxbuf = "_ _ _ _ \0 datoff \0 datlen \0"
	 */
	_db_writeidx(db, db->idxbuf, db->idxoff, SEEK_SET, freeptr);

	/* 修改空闲链表,使之指向被删除条目 */
	_db_writeptr(db, FREE_OFF, db->idxoff);

	/* 使上一条记录跳过要删除记录,指向下一条记录 */
	_db_writeptr(db, db->ptroff, saveptr);

	if (un_lock(db->idxfd, FREE_OFF, SEEK_SET, 1) < 0)
	{
		printf("%d\n", __LINE__);
		exit(-1);
	}
}

此函数的主要工作是:

  • 把数据缓冲区清空,然后写入数据文件。
  • 把索引缓冲区的key字段清空,然后写入索引文件。注意,索引记录依然指向原来的数据记录,但此时的数据记录已经全部为空格符了。
  • 把要删除的索引记录插入到空闲链表的开头。
  • 被删除条目对应的散列表中,移除删除条目。
上面的函数中,最主要的就是修改索引记录,然后写数据文件,写索引文件等操作。与读操作类似,写操作也对应了以下三个函数:
  • _db_writedat:写到数据文件。
  • _db_writeidx:写到索引文件。
  • _db_writeptr:修改指针,写入索引文件。
下面来分析上面这三个函数。

_db_writedat函数源码如下:
/* 向数据文件中写入数据,以换行符结尾
 */
static void _db_writedat(DB *db, const char *data, off_t offset, int whence)
{
	struct iovec iov[2];
	static char newline = NEWLINE;

	if (whence == SEEK_END)	/* 追加,则锁住整个数据文件 */
		if (writew_lock(db->datfd, 0, SEEK_SET, 0) < 0)
		{
			printf("%d\n", __LINE__);
			exit(-1);
		}

	if ((db->datoff = lseek(db->datfd, offset, whence)) == -1)	/* 在数据文件中定位 */
	{
		printf("%d\n", __LINE__);
		exit(-1);
	}

	db->datlen = strlen(data) + 1;	/* +1容纳换行符 */

	/* 聚集写
	 * 写入到文件中的数据,要以换行符结尾
	 */
	iov[0].iov_base = (char *)data;
	iov[0].iov_len  = db->datlen - 1;
	iov[1].iov_base = &newline;
	iov[1].iov_len  = 1;
	if (writev(db->datfd, &iov[0], 2) != db->datlen)
	{
		printf("%d\n", __LINE__);
		exit(-1);
	}

	if (whence == SEEK_END)
		if (un_lock(db->datfd, 0, SEEK_SET, 0) < 0)
		{
			printf("%d\n", __LINE__);
			exit(-1);
		}
}

向数据文件中写使用了writev函数,也就是聚集写,把多个缓冲区的数据按序写入数据文件。

接下来是_db_writeidx函数:
/* 向索引文件写一条索引记录 */
static void _db_writeidx(DB *db, const char *key, off_t offset, int whence, off_t ptrval)
{
	struct iovec iov[2];
	char asciiptrlen[PTR_SZ + IDXLEN_SZ + 1];
	int len;
	char *fmt;

	/* ptrval是下一条记录的偏移量 */
	if ((db->ptrval = ptrval) < 0 || ptrval > PTR_MAX)
	{
		printf("%d\n", __LINE__);
		exit(-1);
	}

	if (sizeof(off_t) == sizeof(long long))
		fmt = "%s%c%lld%c%d\n";
	else
		fmt = "%s%c%ld%c%d\n";

	/* datoff和datlen已经在_db_find_and_lock函数中被设置好了
	 * 实际上,被删除的索引记录依然指向原来的数据,但key部分已经变为空了
	 */
	sprintf(db->idxbuf, fmt, key, SEP, db->datoff, SEP, db->datlen);	/* 索引记录后半部分 */
	if ((len = strlen(db->idxbuf)) < IDXLEN_MIN || len > IDXLEN_MAX)
	{
		printf("%d\n", __LINE__);
		exit(-1);
	}

	/* ptrval为下一条记录的偏移量 */
	sprintf(asciiptrlen, "%*ld%*d", PTR_SZ, ptrval, IDXLEN_SZ, len);	/* 索引记录前半部分 */

	if (whence == SEEK_END)	/* 追加,则需要加写锁 */
		/* 跳过空闲链表和散列表,锁定下一行至结尾 */
		if (writew_lock(db->idxfd, ((db->nhash + 1) * PTR_SZ) + 1, SEEK_SET, 0) < 0)
		{
			printf("%d\n", __LINE__);
			exit(-1);
		}

	if ((db->idxoff = lseek(db->idxfd, offset, whence)) == -1)
	{
		printf("%d\n", __LINE__);
		exit(-1);
	}

	/* 聚集写 */
	iov[0].iov_base = asciiptrlen;
	iov[0].iov_len  = PTR_SZ + IDXLEN_SZ;
	iov[1].iov_base = db->idxbuf;
	iov[1].iov_len  = len;
	if (writev(db->idxfd, &iov[0], 2) != PTR_SZ + IDXLEN_SZ + len)
	{
		printf("%d\n", __LINE__);
		exit(-1);
	}

	if (whence == SEEK_END)
		if (un_lock(db->idxfd, ((db->nhash + 1) * PTR_SZ) + 1, SEEK_SET, 0) < 0)
		{
			printf("%d\n", __LINE__);
			exit(-1);
		}
}

此函数稍微有些复杂。主要任务还是配置索引记录中的各个字段,然后写入索引文件,同样使用了聚集写。

最后是_db_writeptr函数,这个函数相对较为简单,只需要把某个偏移量写入到索引文件中,代码如下:
/* 将一个链表指针写入索引文件 */
static void _db_writeptr(DB *db, off_t offset, off_t ptrval)
{
	char asciiptr[PTR_SZ + 1];

	if (ptrval < 0 || ptrval > PTR_MAX)
	{
		printf("%d\n", __LINE__);
		exit(-1);
	}

	/* 数值型转换成字符型写入缓冲区 */
	sprintf(asciiptr, "%*ld", PTR_SZ, ptrval);

	/* 写之前的定位 */
	if (lseek(db->idxfd, offset, SEEK_SET) == -1)
	{
		printf("%d\n", __LINE__);
		exit(-1);
	}
	if (write(db->idxfd, asciiptr, PTR_SZ) != PTR_SZ)
	{
		printf("%d\n", __LINE__);
		exit(-1);
	}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值