知识回顾
postgres Drop_table源码解析-1
postgres Drop_table源码解析-2
通过之前的分析可知,删除的表在事务执行的过程中并不删除,而是等事务提交的才将物理文件中磁盘中移除,相应的调用接口函数为smgrDoPendingDeletes:
数据结构
/*
* We keep a list of all relations (represented as RelFileNode values)
* that have been created or deleted in the current transaction. When
* a relation is created, we create the physical file immediately, but
* remember it so that we can delete the file again if the current
* transaction is aborted. Conversely, a deletion request is NOT
* executed immediately, but is just entered in the list. When and if
* the transaction commits, we can delete the physical file.
*
* To handle subtransactions, every entry is marked with its transaction
* nesting level. At subtransaction commit, we reassign the subtransaction's
* entries to the parent nesting level. At subtransaction abort, we can
* immediately execute the abort-time actions for all entries of the current
* nesting level.
*
* NOTE: the list is kept in TopMemoryContext to be sure it won't disappear
* unbetimes. It'd probably be OK to keep it in TopTransactionContext,
* but I'm being paranoid.
*/
typedef struct PendingRelDelete
{
RelFileNode relnode; /* relation that may need to be deleted */ // 物理文件地址
BackendId backend; /* InvalidBackendId if not a temp rel */
bool atCommit; /* T=delete at commit; F=delete at abort */
int nestLevel; /* xact nesting level of request */
struct PendingRelDelete *next; /* linked-list link */ // 待删除文件链表
} PendingRelDelete;
案例:
postgres=# drop table test_1;
函数栈
(gdb) bt
#0 smgrDoPendingDeletes (isCommit=true) at storage.c:598
#1 0x000000000054ac74 in CommitTransaction () at xact.c:2270
#2 0x000000000054b7b4 in CommitTransactionCommand () at xact.c:2975
#3 0x00000000008fe53d in finish_xact_command () at postgres.c:2721
#4 0x00000000008fc199 in exec_simple_query (query_string=0x188b058 "drop table test_1;") at postgres.c:1239
#5 0x00000000009003f8 in PostgresMain (argc=1, argv=0x7ffdf4801c20, dbname=0x18b6c28 "postgres", username=0x1887bd8 "postgres")
at postgres.c:4486
#6 0x0000000000851ca3 in BackendRun (port=0x18ade30) at postmaster.c:4506
#7 0x0000000000851629 in BackendStartup (port=0x18ade30) at postmaster.c:4228
#8 0x000000000084dd48 in ServerLoop () at postmaster.c:1745
#9 0x000000000084d629 in PostmasterMain (argc=1, argv=0x1885ba0) at postmaster.c:1417
#10 0x000000000075ec02 in main (argc=1, argv=0x1885ba0) at main.c:209
源代码分析
1 smgrDoPendingDeletes接口介绍:
/*
* Likewise, dropping of files deleted during the transaction is best done
* after releasing relcache and buffer pins. (This is not strictly
* necessary during commit, since such pins should have been released
* already, but this ordering is definitely critical during abort.) Since
* this may take many seconds, also delay until after releasing locks.
* Other backends will observe the attendant catalog changes and not
* attempt to access affected files.
*/
// 在事务释放relcache和buffe pins后删除文件(在事务提交时顺序不必这么严格,因为之前获取的buffer
// pin 已经释放,但在事务abort时此顺序很关键)。基于第二点,将其推迟至锁释放。其他的backend将只会
// 到关于此表的无效元数据信息,不会访问此文件
smgrDoPendingDeletes(true);
具体实现:
/*
* smgrDoPendingDeletes() -- Take care of relation deletes at end of xact.
*
* This also runs when aborting a subxact; we want to clean up a failed
* subxact immediately.
*
* Note: It's possible that we're being asked to remove a relation that has
* no physical storage in any fork. In particular, it's possible that we're
* cleaning up an old temporary relation for which RemovePgTempFiles has
* already recovered the physical storage.
*/
void
smgrDoPendingDeletes(bool isCommit)
{
int nestLevel = GetCurrentTransactionNestLevel(); // 获取当前事务NestLevel
PendingRelDelete *pending;
PendingRelDelete *prev;
PendingRelDelete *next;
int nrels = 0,
maxrels = 0;
SMgrRelation *srels = NULL;
prev = NULL;
// pendingDeletes:链表结构,存放待删除的relation信息
for (pending = pendingDeletes; pending != NULL; pending = next)
{
next = pending->next;
if (pending->nestLevel < nestLevel)
{
/* outer-level entries should not be processed yet */
prev = pending;
}
else
{
/* unlink list entry first, so we don't retry on failure */
if (prev)
prev->next = next;
else
pendingDeletes = next;
/* do deletion if called for */
if (pending->atCommit == isCommit)
{
SMgrRelation srel;
srel = smgropen(pending->relnode, pending->backend); // 打开物理文件
// 如有必要初始化SMgrRelation数组,初始大小为8, 如实际大小超于此值,常规法 2倍 扩容
/* allocate the initial array, or extend it, if needed */
if (maxrels == 0)
{
maxrels = 8;
srels = palloc(sizeof(SMgrRelation) * maxrels);
}
else if (maxrels <= nrels)
{
maxrels *= 2;
srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
}
srels[nrels++] = srel;
}
/* must explicitly free the list entry */
pfree(pending);
/* prev does not change */
}
}
// 统计此事务删除文件数,调用 smgrdounlinkall
if (nrels > 0)
{
smgrdounlinkall(srels, nrels, false);
for (int i = 0; i < nrels; i++)
smgrclose(srels[i]);
pfree(srels);
}
}
2 smgrdounlinkall接口函数
/*
* smgrdounlinkall() -- Immediately unlink all forks of all given relations
*
* All forks of all given relations are removed from the store. This
* should not be used during transactional operations, since it can't be
* undone.
*
* If isRedo is true, it is okay for the underlying file(s) to be gone
* already.
*/
void
smgrdounlinkall(SMgrRelation *rels, int nrels, bool isRedo)
{
int i = 0;
RelFileNodeBackend *rnodes;
ForkNumber forknum;
if (nrels == 0)
return;
/*
* Get rid of any remaining buffers for the relations. bufmgr will just
* drop them without bothering to write the contents.
*/
// 首先释放涉及rels的所有数据缓冲区 buffer pool
DropRelFileNodesAllBuffers(rels, nrels);
/*
* create an array which contains all relations to be dropped, and close
* each relation's forks at the smgr level while at it
*/
// 创建包含所有待drop的relations 数组,并关闭涉及的所有分支文件 [普通表,FSM表, VM表]
rnodes = palloc(sizeof(RelFileNodeBackend) * nrels);
for (i = 0; i < nrels; i++)
{
RelFileNodeBackend rnode = rels[i]->smgr_rnode;
int which = rels[i]->smgr_which;
rnodes[i] = rnode;
/* Close the forks at smgr level */
for (forknum = 0; forknum <= MAX_FORKNUM; forknum++)
smgrsw[which].smgr_close(rels[i], forknum);
}
/*
* It'd be nice to tell the stats collector to forget them immediately,
* too. But we can't because we don't know the OIDs.
*/
/*
* Send a shared-inval message to force other backends to close any
* dangling smgr references they may have for these rels. We should do
* this before starting the actual unlinking, in case we fail partway
* through that step. Note that the sinval messages will eventually come
* back to this backend, too, and thereby provide a backstop that we
* closed our own smgr rel.
*/
// 向其他backend发送消息迫使其关闭任何与待删除relations的 smgr引用
for (i = 0; i < nrels; i++)
CacheInvalidateSmgr(rnodes[i]);
/*
* Delete the physical file(s).
*
* Note: smgr_unlink must treat deletion failure as a WARNING, not an
* ERROR, because we've already decided to commit or abort the current
* xact.
*/
// 调用smgr_unlink进行物理删除
for (i = 0; i < nrels; i++)
{
int which = rels[i]->smgr_which;
for (forknum = 0; forknum <= MAX_FORKNUM; forknum++)
smgrsw[which].smgr_unlink(rnodes[i], forknum, isRedo);
}
pfree(rnodes);
}
3 smgr_unlink函数进行了多次封装,其最终调用truncate系统函数实现物理删除,该函数的功能是将指定文件阶截断至字节数为0大小,文件其实还存在,只是大小为0;
/* Truncate FILE to LENGTH bytes. */
# ifndef __USE_FILE_OFFSET64
extern int truncate (const char *__file, __off_t __length)
__THROW __nonnull ((1)) __wur;
函数栈如下,有兴趣可以阅读相关源代码
(gdb) bt
#0 pg_truncate (path=0x18b0fe8 "base/13835/131111_vm", length=0) at fd.c:655
#1 0x00000000008f6e5a in do_truncate (path=0x18b0fe8 "base/13835/131111_vm") at md.c:298
#2 0x00000000008f6f2b in mdunlinkfork (rnode=..., forkNum=VISIBILITYMAP_FORKNUM, isRedo=false) at md.c:329
#3 0x00000000008f6e3b in mdunlink (rnode=..., forkNum=VISIBILITYMAP_FORKNUM, isRedo=false) at md.c:286
#4 0x00000000008f91fb in smgrdounlinkall (rels=0x18b2a28, nrels=1, isRedo=false) at smgr.c:445