一、概述
数据库的 DELETE 操作远非简单的数据擦除,其底层实现深刻影响着系统的事务性能、存储效率和并发能力。
二、删除原理
目前有三种主流的实现方式:
原地物理删除(In-Place Deletion) 逻辑标记删除(Logical Delete) 日志结构化删除(Log-Structured Delete)
2.1 原地物理删除
删除表中的某一行数据,大家立马想到就是物理删除。
根据指定的数据行关键字,找到对应的表数据块,将该行数据擦除,同时还需要将当前数据块中后面的数据移动填充空隙,另外如何有索引时,也要一并删除索引项。这一系列操作之后,该行数据完全从表中被擦除了,而且占用的空间也释放了。
原地物理删除是一种常规的删除方式,一次彻底处理,不拖泥带水。
2.2 逻辑标记删除
通过数据行头的元数据中的成员,来标记该行数据“逻辑删除”,此时删除动作就执行完成了,而真正的物理清理,由另外的后台任务异步完成。逻辑标记删除的方式,常常与MVCC结合使用。
逻辑标记删除,其实将删除执行分成了两部分:逻辑删除和物理清理,在DELETE命令执行期只做了前者,而后者由后台进程默默完成。
相比原地物理删除,逻辑标记删除减少了DELETE命令执行期间占用数据块的时间,也就是锁块时间大大减少;同时数据块中存在冗余数据,增加了查询的负担。
2.3 日志结构化删除
基于LSM-Tree存储方式,删除操作并不是直接删除数据,而是通过一种叫“墓碑记录”的特殊数据来标识数据的删除。
当删除数据行时,在日志中追加写入 Tombstone 标记(墓碑记录),依赖后台 Compaction任务物理清理数据。日志结构化删除方式适用于大量写入的场景。
三、DELETE执行器
为DELETE删除命令增加执行器,参数为物理执行计划树。
int ExecDeleteStmt(Node *rootNode){int rowNum = 0;Node *resultList = NULL;QueryStmt *queryPlan = (QueryStmt*)rootNode;do {resultList = ExecNodeProc(queryPlan->plan);if(resultList == NULL)break;rowNum += 1;}while(resultList != NULL);return rowNum;}
在执行器中遍历执行计划节点,递归调用各节点的处理函数,每次删除一行数据,直到所有数据处理完成,返回处理数据的行数统计。
3.1 执行算子
执行计划树的顶层是删除节点,它对应的处理函数来负责从数据表中删除符合条件的数据行。
Node* ExecDeleteTbl(Node *planNode)
删除执行算子的实现分为三部分:
一是从下层节点中获取符合条件的数据行,每次返回一行,当所有数据处理完时,返回值为空。
result = ExecNodeProc(modifyNode->subNode);if (result == NULL){return result;}
二是从结果列表中,找到待删除数据表中的结果行,并记录当前数据行存储的位置。
执行计划树的叶子节点是扫描节点,扫描信息中当前的扫描位置,也就是当前数据行的存储位置。
delTup = GetResultNodeFromList(modifyNode->tblInfo, (List*)result);if(delTup == NULL){return NULL;}scanPos = (ScanNode*)((TableRefInfo*)(delTup->tblRefInfo))->scanPos;
三是根据存储位置,在数据表文件中物理删除本条数据行。
ret = RemoveTupleChain(scanPos->scanInfo->pageNum, scanPos->scanInfo->item_offset - sizeof(ItemData), delTup->rel);
数据行的存储有可能是跨数据块存储,在UPDATE命令中已经实现了清理数据行的函数,此处直接调用即可。
四、总结
本次删除实现采用原理物理删除的方式,本节实现位于exam_72目录下的executor.c文件中。




