1 heamp的简介

前置知识:Relation的基本概念,Tuple的基本概念

1.1 heapam接口的功能

heapam是存储模块对上层提供的数据读写接口,这些接口通常是操作1个relation中的1个或多个tuple。
本文介绍以下4个最常用的接口,即如何向1个relation中写入、删除、更新、读取1个Tuple,接口定义如下:

/* 向relatiion中写入1条tuple */
heap_insert(Relation relation, HeapTuple tup, ...)
/* 从relation中删除1条tuple */
heap_delete(Relation relation, ItemPointer tid, ...)
/* 在relation中,将旧tuple标记删除,并写入1个新tuple */
heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, ...)
/* 从relation中读取数据,每次读取1条数据 */
HeapScanDesc heap_beginscan(Relation relation, ...)
HeapTuple heap_getnext(HeapScanDesc scan)

1.2 调用heapam接口

/*
 * 此处,以4个常见的SQL为例,介绍数据库如何调用heapam接口
 *  1. INSERT INTO t1 VALUES (1, 'data1');
 *  2. SELECT * FROM t1;
 *  3. DELETE FROM t1 WHERE c1 = 1;
 *  4. UPDATE t1 SET c2 = 'data2' WHERE c1 = 1;
 */
exec_simple_query("SQL语句") /* 该函数是所有SQL语句的统一处理入口  */
    PortalRun()
        PortalRunMulti()
            ProcessQuery()
                ExecutorRun()
                    standard_ExecutorRun()
                        ExecutePlan()
                            ExecProcNode() /* 从这里开始分叉,不同类型的SQL语句,由不同函数处理 */
                                ExecModifyTable()
                                    ExecInsert() /* 处理:INSERT INTO t1 VALUES (1, 'data1') */
                                        heap_insert(relation, tuple, ..)

1.3 heapam接口的使用场景

实际应用场景中,数据库的存储模块需满足以下关键需求:

  • 操作多:1个表需要支持数据写入、查询、更新、删除等操作
  • 数据多:1个表可能存储亿级以上的数据
  • 并发高:短时间内,可能上百个连接同时读写1个表
  • 可靠性高:突然发生断电等特殊情况,数据不会发生错乱
  • 性能高:使用一些机制,优化数据访问的速度,比如:缓存、锁等

明确存储接口该解决哪些问题后,了解heapam接口时,可大概清楚每一步的目的。

2 Heapam工作流程

2.1 heap_insert接口

heap_insert的工作流程分为4个阶段:

heap_insert
    GetCurrentTransactionId
    heap_prepare_insert         # 1. 获取当前事务id,并为Tuple设置事务信息
    RelationGetBufferForTuple   # 2. 读取1个合适的Page,用于存放Tuple,对Page加排他锁
    RelationPutHeapTuple        # 3. 将Tuple存至Page
    XLogBeginInsert             # 4. 生成WAL日志,并将WAL日志写入WAL Buffer中
    XLogInsert
    UnlockReleaseBuffer         #    解锁Page

各阶段中,详细流程如下:

  1. 获取当前事务id,并为Tuple设置事务信息

    GetCurrentTransactionId
    heap_prepare_insert
        HeapTupleHeaderSetXmin
        HeapTupleHeaderSetCmin
        HeapTupleHeaderSetXmax
    
  2. 从Buffer中,查找1个合适的Page

    heap_insert
        RelationGetBufferForTuple
            GetPageWithFreeSpace # 1 首先,根据Tuple的大小,查找fsm,选择合适的Page
                fsm_search
            LOOP:
                ReadBufferBI # 2 从Buffer中,查找指定Page
                    ReadBuffer
                        ReadBufferExtended
                            ReadBuffer_common
                                ...
                LockBuffer(BUFFER_LOCK_EXCLUSIVE) # 3 对Page加排他锁
                if pageFreeSpace < PageGetHeapFreeSpace # 4 对Page加锁后,检查Page的空闲空间,Page空闲空间够,则返回
                    return
                else: # 5 如果在1-3步中,其他进程页操作了Page,导致Page容量变小,则解锁Page,重新查找新Page
                    LockBuffer(BUFFER_LOCK_UNLOCK)
                    RecordAndGetPageWithFreeSpace # 6 记录当前Page的空闲空间,然后查找fms,选择合适的Page,循环到步骤2
                        fsm_set_and_search
                        fsm_search
    
            # 7 在循环中,未找到合适的Page,则初始化1个新的Page
            LockRelationForExtension(ExclusiveLock)
            ReadBufferBI(P_NEW)
                ReadBuffer
            PageInit
    
  3. 将Tuple存至Page(详见Page模块)

    RelationPutHeapTuple
        PageAddItem
    
  4. 生成WAL日志,并将WAL日志写入WAL Buffer中(详见WAL模块)

2.2 heap_delete接口

heap_delete的工作流程分为4个阶段:

heap_delete
    GetCurrentTransactionId     # 1. 获取当前事务的id
    ReadBuffer                  # 2. 找到待删除Tuple所在的Page
    PageGetItemId
    HeapTupleSatisfiesUpdate    # 3. 判断Tuple是否可见

3

otherBuffer:update场景,包含旧数据
targetBuffer:插入的页面,默认从上次插入的页面接着插入

Oid heap_insert(Relation relation, HeapTuple tup, CommandId cid, int options, BulkInsertState bistate)
    HeapTuple heaptup = heap_prepare_insert(relation, tup, xid, cid, options)
    Buffer buffer = RelationGetBufferForTuple(relation, heaptup->t_len,InvalidBuffer, options, bistate, &vmbuffer, NULL)
    RelationPutHeapTuple(relation, buffer, heaptup)
    if PageIsAllVisible(BufferGetPage(buffer)):
        visibilitymap_clear(relation, ItemPointerGetBlockNumber(&(heaptup->t_self)), vmbuffer)
    MarkBufferDirty(buffer)

    if !(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation):
        /* 如果是系统表 */
        if RelationIsAccessibleInLogicalDecoding(relation):
            log_heap_new_cid(relation, heaptup)
                ...
                XLogInsert(RM_HEAP2_ID, XLOG_HEAP2_NEW_CID)

        if ItemPointerGetOffsetNumber(&(heaptup->t_self)) == FirstOffsetNumber:
            info |= XLOG_HEAP_INIT_PAGE
            bufflags |= REGBUF_WILL_INIT

        XLogBeginInsert()
        XLogRegisterData((char *) &xlrec, SizeOfHeapInsert)
        XLogRegisterBuffer(0, buffer, REGBUF_STANDARD | bufflags)
        XLogRegisterBufData(0, (char *) &xlhdr, SizeOfHeapHeader)
        XLogRegisterBufData(0, heaptup->t_data + SizeofHeapTupleHeader, heaptup->t_len - SizeofHeapTupleHeader)
        XLogIncludeOrigin()

        recptr = XLogInsert(RM_HEAP_ID, info)
        PageSetLSN(page, recptr)

    CacheInvalidateHeapTuple(relation, heaptup, NULL)
    return HeapTupleGetOid(tup)

init page时机

一共3处:

heap_insert()
    ...
    Buffer buffer = RelationGetBufferForTuple()
    ...
    RelationPutHeapTuple(relation, buffer, heaptup)
    ...
    if ItemPointerGetOffsetNumber(&(heaptup->t_self)) == FirstOffsetNumber:
        info |= XLOG_HEAP_INIT_PAGE

heap_xlog_insert()
    if XLogRecGetInfo(record) & XLOG_HEAP_INIT_PAGE:
        buffer = XLogInitBufferForRedo(record, 0)
            XLogReadBufferForRedoExtended(RBM_ZERO_AND_LOCK)
                if XLogRecHasBlockImage(record, block_id):
                    *buf = XLogReadBufferExtended()
                    page = BufferGetPage(*buf)
                    RestoreBlockImage(record, block_id, page)
                    MarkBufferDirty(*buf)
                else:
                    *buf = XLogReadBufferExtended()
        page = BufferGetPage(buffer)
        PageInit(page, BufferGetPageSize(buffer), 0)
    else:
        XLogReadBufferForRedo(record, 0, &buffer)

    data = XLogRecGetBlockData(record, 0, &datalen)
    htup = &tbuf.hdr
    htup->t_infomask2 = xlhdr.t_infomask2
    htup ... = ...
    PageAddItem(page, (Item) htup, newlen, xlrec->offnum)
    MarkBufferDirty(buffer)

heap_multi_insert(HeapTuple *tuples, int ntuples)
    ...
    while (ndone < ntuples)
        buffer = RelationGetBufferForTuple()
        RelationPutHeapTuple()
        for (nthispage = 1; ndone + nthispage < ntuples; nthispage++)
            RelationPutHeapTuple()
            log_heap_new_cid(relation, heaptup)
        MarkBufferDirty(buffer)

        init = (ItemPointerGetOffsetNumber(&(heaptuples[ndone]->t_self)) == FirstOffsetNumber &&
			PageGetMaxOffsetNumber(page) == FirstOffsetNumber + nthispage - 1)
        if init:
            info |= XLOG_HEAP_INIT_PAGE
        ...