一、概述
数据行更新,在新数据行覆盖旧数据行时,存在空间多余或者不足的情况。
当旧数据行空间小于新的数据行空间,比如我们写错字后,擦除了一个字,想改为两个字时,这时旧数据的空间不足存放新的数据,此时仍然要保持数据的位置不变,又可以存储新的数据。
在这种情况下,将新数据行在数据表文件中找一个可用空间插入,而在新数据行的位置,只记录新数据行的位置信息。
对于这类需要二次寻址的数据行,新增两种数据行标志,将新数据行和旧数据行分别标记为数据和重定向数据行。
二、数据行类型
数据行的标志新增四种:
「无效」,代表当前数据行是无效数据; 「正常」,当前数据行是普通数据行数据; 「重定向」,当前数据行存储的数据是位置,需要二次寻址; 「数据」,仅存储数据,当前数据行存储的是二次寻址的真正数据行。
typedef enum {ITEM_INVALID = 0x00,ITEM_NORMAL = 0x01,ITEM_REDIRECT = 0x02,ITEM_DATA = 0x04}ITEM_FLAGS;
为了在数据行上记录以上四种标记,在数据行信息头中增加8位的标志字段,将长度字段减少到了24位,这样整个结构的大小不变。
以上四种标志,可以组合形成四种类型数据行:
「无效数据行」,在查找时可以跳过,标志位为ITEM_INVALID; 「有效数据行」,也就是普通的数据行,标志位为ITEM_NORMAL; 「重定向数据行」,它也是带有效标志的,标志位为ITEM_NORMAL | ITEM_REDIRECT; 「只数据的数据行」,同时带有有效标志,标志位为ITEM_NORMAL | ITEM_DATA。
typedef struct ItemData{int offset;int len:24;unsigned int flags:8;}ItemData;
在以数据块为管理单位的存储系统中,每个数据行的最大长度不能超过单个数据块的长度,使得长度字段实际用不到4个字节,这里就可以从它分出一个字节来记录其它信息。
为了方便使用定义了两组宏操作,一组是判断是否有某一标志的宏,它的结果是布尔类型;
#define ITEM_ISNORMAL(item) (((item)->flags & ITEM_NORMAL) != 0)#define ITEM_ISREDIECT(item) (((item)->flags & ITEM_REDIRECT) != 0)#define ITEM_ISDATA(item) (((item)->flags & ITEM_DATA) != 0)
另一组是添加标识的宏,其中只有无效标识是直接赋值,与其它互斥;其它重定向和仅数据标识都需要与有效标识共同使用,所以它们两个是添加操作。
#define ITEM_SETINVALID(item) ((item)->flags &= ITEM_INVALID)#define ITEM_SETREDIECT(item) ((item)->flags |= ITEM_REDIRECT)#define ITEM_SETDATA(item) ((item)->flags |= ITEM_DATA)
三、数据行更新
在这里会有两个版本的数据行,更新前的数据行——旧数据行,更新后的数据行——新数据行,以及旧数据行的在数据文件中的位置,它由数据块号和块内数据行头位 置组成。
在数据行更新时,先根据旧数据行的位置,找到旧数据行,然后拼估空间是否足够存储新数据行,此时分为三种情况:
「空间足够」; 「空间不足时」,当前数据块剩余空间足够存储新数据行; 「空间不足时」,当前数据块剩余空间也不足时。
在这三种情况下,采用不同的方法来处理。
3.1 找到旧数据行
根据旧数据行的位置,找到旧数据行;
int UpdateTuple(int pageNum, int oldItemOffset, RelationInfo *relInfo, TupleHeader *newTupData)page = ReadPageBuffer(relInfo, pageNum);while(page != NULL){oldItem = page->item + itemIndex;if(!ITEM_ISNORMAL(oldItem)){return -1;}if(!ITEM_ISREDIECT(oldItem)){break;}itemIndex = oldItem->len;page = ReadPageBuffer(relInfo, oldItem->offset);}
每个数据行都会有三种类型,要排除无效的数据行,对于重定向类型的数据行递归二次寻址找到真正数据的位置。
3.2 空间足够新数据行
当旧数据行的空间可以存储新数据行时,直接将新数据覆盖到旧数据位置上,并更新数据行头信息中的数据长度,数据块标记为脏数据以便写入磁盘。
if(oldItem->len >= newTupData->size){oldItem->len = newTupData->size;memcpy((char *)page + oldItem->offset, (char *)newTupData + sizeof(TupleHeader), oldItem->len);relInfo->relstorage.isDirt = 1;return 0;}
此时数据行信息头和数据部分的位置都没有发生变化。
3.3 当前块空间足够
当旧数据行的空间不足时,而当前数据块中有足够空间存储新数据行时,就在当前数据块中存储新数据行,并将旧数据行头更新为新数据的位置。
if (HasFreeSpace(page, newTupData->size)){oldItem->len = newTupData->size;page->dataStartOffset -= oldItem->len;memcpy((char *)page + page->dataStartOffset, (char *)newTupData + sizeof(TupleHeader), oldItem->len);oldItem->offset = page->dataStartOffset;relInfo->relstorage.isDirt = 1;return 0;}
此时数据行头信息的位置没有发生变化,而数据部分的位置发生了变化,需要同时更新行头信息中的偏移字段和数据长度字段。
另外,当前数据块中新插入了数据,消耗了数据块剩余空间,数据块的数据偏移字段也需要更新。
3.4 插入新数据行
旧数据行和当前数据块剩余空间都不足时,只能在数据表的所有数据块中查找足够的空间。
page = InsertNewTupForUpdate(relInfo, newTupData, page, &tempItem);newPageNum = page->header.pageNum;if(pageNum != newPageNum){page = ReadPageBuffer(relInfo, pageNum);oldItem = page->item + PAGE_ITEM_INDEX(oldItemOffset);}oldItem->len = PAGE_ITEM_INDEX(tempItem.offset);oldItem->offset = newPageNum;ITEM_SETREDIECT(oldItem);relInfo->relstorage.isDirt = 1;
在当前数据表文件中找到空闲空间块并插入新的数据行,将旧数据行头信息更新为新数据行的位置,此时就会产生二次寻址的数据行,需要将旧数据行头和新数据行的行头信息设置标志,区别普通数据行。
对于二次寻址的数据行头信息,如何记录实际数据行的地址呢?数据行的地址由数据块编号和数据行头的偏移两部分组成。
在数据行头信息中有数据偏移和数据长度两个字段,此时复用这两个字段,数据偏移字段存储数据块编号,数据长度字段存储数据行头的偏移。
当数据行为重定向类型时,数据行头内存储的信息需要解析为数据行的地址。
3.5 重定向数据行
只存储数据的数据行。
PageDataHeader* InsertNewTupForUpdate(RelationInfo *relInfo, TupleHeader *tup, PageDataHeader *page, ItemData *oldItem)if ((page == NULL) || (!HasFreeSpace(page, tup->size))){page = GetFreeSpacePage(relInfo, tup->size, PAGE_NEW);}itemOffset = PutTupleToPage(page, tup);newItem = page->item + PAGE_ITEM_INDEX(itemOffset);ITEM_SETDATA(newItem);relInfo->relstorage.isDirt = 1;if(oldItem != NULL){oldItem->offset = itemOffset;}
在数据表的所有数据块中查找空闲空间,并在新数据行插入数据表中后,它对应的数据行头信息标志要设置为仅数据类型,只有UPDATE命令会产生这样的数据行。
四、总结
本节内容在exam_63 目录的pages.c文件中。
当再次UPDATE相同数据行时,新的数据行优先存储在重定向数据行所在数据块中,这样可以消除重定向的数据行,避够二次读数据。
当然,UPDATE命令产生的数据行的链最多也就两次寻址就可以找到真正的数据,即使多次更新同一行数据,也是如此。
在UPDATE数据行时,为什么旧数据行的位置要保持不变呢?留一个思考题,大家想一想。




