读薄《PostgreSQL内核分析》:空闲空间映射表(FSM)机制

0. 前言

    主要记录对彭智勇和彭煜玮老师所著《PostgreSQL数据库内核分析》一书的观后总结,用于读薄这本经典著作,同时也方便自己时常温习,加深印象。

    本文主要对PG内部的空闲空间映射表机制进行了简析,结合源码,分析了其实现过程。

1. 空闲空间映射表(FreeSpaceMap)原理

FSM机制的必要性:

    随着表中不断插入和删除元组,文件块中必然会产生空闲空间(Free Space)。在插入元组时优先选择将其存放在空闲空间内是利用存储的好方法。如何管理这些空闲空间才能最大化地提高效率呢? PG8.4以前采用了一种全局FSM文件来记录所有表文件的空闲状况,复杂且低效。PG8.4以后采用了新的策略,即对于每个表文件(包括系统表在内),同时创建一个名为“关系表OID_fsm” 的文件,用于记录该表的空闲空间大小。

FSM机制的基本原理:

    FSM机制采用一个字节表示空闲空间的大小范围,我们将这个字节叫做FSM级别(category)。FSM级别和真实的FSM范围之间的映射关系如表3-1所示。
表3-1 FSM级别和空闲空间范围之间的映射关系

字节取值 表示的空闲空间范围(字节)
0 0 ~ 31
1 32 ~ 63
...... ......
255 8164 ~ 8192

    为了实现快速查找,FSM文件并不是简单使用数组顺序存储每个表块的空闲空间,而是使用了树结构。在FSM块之间使用了一种三层树结构,其中第0层和第1层是辅助层,第二层FSM块实际存储各表块的空闲空间级别(category)。第0层FSM块只有一个,作为树根;第1层FSM块可以有多个,作为第0层FSM块的子节点;第2层FSM块同理。每个FSM块内又构成一棵局部的最大堆二叉树,但每一层FSM块内的最大堆二叉树又略有不同。

  1. 第2层FSM块内大根堆二叉树的每一个叶子节点对应一个表块的空闲空间级别。按照从左到右的顺序,最左边FSM块的最左边叶子节点代表表文件的第一块,左边第二个叶子节点代表表文件的第二块。
  2. 第1层FSM块内大根堆二叉树的叶子节点从左到右对应第2层FSM块的根节点。
  3. 第0层FSM块内大根堆二叉树的叶子节点从左到右对应第1层FSM块的根节点。
    从上面的介绍可知,三层树结构形成了一个逻辑上的大根堆结构,其叶子节点从左到右依次对应表文件中的文件块。

    在单个FSM块内,使用大根堆能保证所有叶子节点的最大值被上升到根节点处。因此我们只要看单个FSM块内的根节点值就可指导此FSM块内空闲空间的上限值。

    每个FSM块大小为8KB,除去必要的页头信息,FSM块剩下的空间都用来存储块内的二叉树结构,每个叶子节点都用一个字节表示,因此FSM块内大约可以保存4000个叶子节点(实际为4069,计算方法见宏定义SlotsPerFSMPage,这里为了简化描述将其近似为4000)。这样,一个FSM文件如果采用三层树结构,大约可以记录4000^3 个叶子节点,对应于4000^3 个表块,远大于2^32 (单个表的最大块数,PG块号数据结构BlockIdData中定义块号长度为32,故单个表最多只能有(2^32-1)个表块)。因此,这样的三层树结构足以记录表文件所有文件块的空闲空间值。

    一个FSM文件内所包含的FSM块和表块的映射关系如图3-7所示。

图3-7 FSM物理块与逻辑地址对照.jpg

由上图可知:

  1. 第0层FSM块的叶子节点记录了第1层所有FSM块的根节点。
  2. 第1层FSM块的叶子节点记录了第2层所有FSM块的根节点。
  3. 第2层FSM块的叶子节点按从左到右的顺序就是对应表块在表文件中的顺序。
  4. FSM文件中第一个文件块(第0号FSM块)中二叉树根节点所存储的是所有表块空闲空间级别的最大值(图3-7中的120)。

2. FSM相关数据结构、宏和函数声明

1. FSM块的数据结构:FSMPageData
/*
 * FSM块的数据结构. See src/backend/storage/freespace/README for
 * details.
 */
typedef struct
{
    /*
     * fp_next_slot是一个整数,用于提示下一次开始查询二叉树时的叶子节点位置
     *(也就是一个满足请求的二叉树叶子节点序号,从零开始计数,用slot表示),
     * 若该FSM块是叶子节点,则将fp_next_slot置为 slot + 1, 否则置为slot。
     */
    int         fp_next_slot;

    /*
     * fp_nodes数组表示当前FSM块内存储的大根堆二叉树结构。
     */
    uint8       fp_nodes[FLEXIBLE_ARRAY_MEMBER];
} FSMPageData;

typedef FSMPageData *FSMPage;

2. 一个FSM块中可以保存的节点总数目(8192 - 24 - 4 = 8164 )
#define NodesPerPage (BLCKSZ - MAXALIGN(SizeOfPageHeaderData) - \
                      offsetof(FSMPageData, fp_nodes))

3. FSM块中保存的非叶子节点数目(8192/2 - 1 = 4095)
#define NonLeafNodesPerPage (BLCKSZ / 2 - 1)

4. FSM块中保存的叶子节点数目 (8164 - 4095 = 4069)
#define LeafNodesPerPage (NodesPerPage - NonLeafNodesPerPage)

5. FSM块中的slots个数(即FSM块中可以保存的叶子节点总数,4069)
#define SlotsPerFSMPage LeafNodesPerPage

6. fsmpage.c中的函数原型
/* Prototypes for functions in fsmpage.c */
extern int fsm_search_avail(Buffer buf, uint8 min_cat, bool advancenext,
                 bool exclusive_lock_held);
extern uint8 fsm_get_avail(Page page, int slot);
extern uint8 fsm_get_max_avail(Page page);
extern bool fsm_set_avail(Page page, int slot, uint8 value);
extern bool fsm_truncate_avail(Page page, int nslots);
extern bool fsm_rebuild_page(Page page);

\src\backend\storage\freespace\freespace.c
7. FSM相关宏定义
/*
 * 我们仅使用一个字节来存储文件块上的空闲空间,
 * 所以我们将空闲空间划分为256个级别,
 * 最高级别, 255, 表示一个文件块的空闲空间不少于
 * MaxFSMRequestSize 字节 , 第二高级别
 * 代表空闲空间大小从 254 * FSM_CAT_STEP 到
 * MaxFSMRequestSize字节,左闭右开区间.
 *
 * 对于默认的BLCKSZ 等于8k , MaxFSMRequestSize的值为8164 字节, 
 * 空闲空间级别的划分如下所示:
 *
 *
 * 范围        级别
 * 0    - 31   0
 * 32   - 63   1
 * ...    ...  ...
 * 8096 - 8127 253
 * 8128 - 8163 254
 * 8164 - 8192 255
 *
 */
事实上,大小在0~31字节的空闲空间将不会被使用,而当申请空闲空间大小在8164~8192字节之间时,系统将为请求分配MaxFSMRequestSize大小的空闲空间,也即一个空闲的表块。

//FSM空闲空间的级别总数(256)
#define FSM_CATEGORIES  256    

// FSM空闲空间级别的步长(8192/256 = 32)
#define FSM_CAT_STEP    (BLCKSZ / FSM_CATEGORIES)

// 可申请的空闲空间的最大值,就是表块内元组的最大大小(8164字节)
#define MaxFSMRequestSize   MaxHeapTupleSize

/*
 * FSM树结构的总深度,默认是3. 我们需要能够寻址2^32-1 个表块,
 * 而1626 是能满足 X^3 >= 2^32-1的最小的整数. 类似的,
 * 216 能满足 X^4 >= 2^32-1的最小的整数. 
 */
#define FSM_TREE_DEPTH  ((SlotsPerFSMPage >= 1626) ? 3 : 4)

// FSM三层树结构根节点所在的层号(3-1 =2)
#define FSM_ROOT_LEVEL  (FSM_TREE_DEPTH - 1)

// FSM三层树结构叶子节点所在的层号(0)
#define FSM_BOTTOM_LEVEL 0

/*
 * FSM块的逻辑地址数据结构. 
 * 可以调用fsm_logical_to_physical从逻辑地址计算出物理地址,也即该FSM块的物理块号
 */
typedef struct
{
    int         level;          /* 层号*/
    int         logpageno;      /* 当前层内的块号 */
} FSMAddress;

/* FSM三层树结构根节点的逻辑地址. */
static const FSMAddress FSM_ROOT_ADDRESS = {FSM_ROOT_LEVEL, 0};

/* FSM三层树结构内定位FSM块涉及到的相关函数声明 */
static FSMAddress fsm_get_child(FSMAddress parent, uint16 slot);
static FSMAddress fsm_get_parent(FSMAddress child, uint16 *slot);
static FSMAddress fsm_get_location(BlockNumber heapblk, uint16 *slot);
static BlockNumber fsm_get_heap_blk(FSMAddress addr, uint16 slot);
static BlockNumber fsm_logical_to_physical(FSMAddress addr);

static Buffer fsm_readbuf(Relation rel, FSMAddress addr, bool extend);
static void fsm_extend(Relation rel, BlockNumber fsm_nblocks);

/* functions to convert amount of free space to a FSM category */
static uint8 fsm_space_avail_to_cat(Size avail);
static uint8 fsm_space_needed_to_cat(Size needed);
static Size fsm_space_cat_to_avail(uint8 cat);

/* workhorse functions for various operations */
static int fsm_set_and_search(Relation rel, FSMAddress addr, uint16 slot,
                   uint8 newValue, uint8 minValue);
static BlockNumber fsm_search(Relation rel, uint8 min_cat);
static uint8 fsm_vacuum_page(Relation rel, FSMAddress addr, bool *eof);
static BlockNumber fsm_get_lastblckno(Relation rel, FSMAddress addr);
static void fsm_update_recursive(Relation rel, FSMAddress addr, uint8 new_cat);

2. 源码分析

2.1 FSM的创建

    FSM文件并不是在创建表文件的时候就被创建,而是推迟到需要使用FSM文件的时候,即执行VACUUM操作时或为了插入元组而第一次查找FSM文件时才创建。

    在创建FSM文件时,会预先创建三个FSM块:0号FSM块,1号FSM块,2号FSM块,分别对应第0层FSM块,第1层最左FSM块,第二层最左FSM块。在2号FSM块内叶子节点一次存储从0号开始的各表块的空闲空间级别,当2号FSM块填满后,会调用fsm_extend函数扩展一个新的FSM块

/*
 * 确保FSM文件至少有fsm_nblocks个FSM块,如不足,填充空块补齐。
 * 如果文件不存在,调用函数smgrcreate创建之。
 */
static void
fsm_extend(Relation rel, BlockNumber fsm_nblocks)
{
    BlockNumber fsm_nblocks_now;
    PGAlignedBlock pg;

    PageInit((Page) pg.data, BLCKSZ, 0);

    /*
     * 对关系rel上互斥锁,防止并发调用
     */
    LockRelationForExtension(rel, ExclusiveLock);

    RelationOpenSmgr(rel);

    /*
     * 如果FSM文件不存在,创建之
     */
    if ((rel->rd_smgr->smgr_fsm_nblocks == 0 ||
         rel->rd_smgr->smgr_fsm_nblocks == InvalidBlockNumber) &&
        !smgrexists(rel->rd_smgr, FSM_FORKNUM))
        smgrcreate(rel->rd_smgr, FSM_FORKNUM, false);

    // 获取当前FSM文件有多少FSM块
    fsm_nblocks_now = smgrnblocks(rel->rd_smgr, FSM_FORKNUM);

    // 如果FSM块不足,扩充FSM块
    while (fsm_nblocks_now < fsm_nblocks)
    {
        PageSetChecksumInplace((Page) pg.data, fsm_nblocks_now);

        smgrextend(rel->rd_smgr, FSM_FORKNUM, fsm_nblocks_now,
                   pg.data, false);
        fsm_nblocks_now++;
    }

    /* 更新本地缓存 */
    rel->rd_smgr->smgr_fsm_nblocks = fsm_nblocks_now;

    UnlockRelationForExtension(rel, ExclusiveLock);
}

依赖的函数:

2.1.1 PageInit

/*
 * PageInit
 *      初始化Page.
 *      注意这里我们不初始化pd_checksum
 */
void
PageInit(Page page, Size pageSize, Size specialSize)
{
    PageHeader  p = (PageHeader) page;

    specialSize = MAXALIGN(specialSize);

    Assert(pageSize == BLCKSZ);
    Assert(pageSize > specialSize + SizeOfPageHeaderData);

    /* 重置page中的所有比特位为0 */
    MemSet(p, 0, pageSize);

    p->pd_flags = 0;
    p->pd_lower = SizeOfPageHeaderData;
    p->pd_upper = pageSize - specialSize;
    p->pd_special = pageSize - specialSize;
    PageSetPageSizeAndVersion(page, pageSize, PG_PAGE_LAYOUT_VERSION);
    /* p->pd_prune_xid = InvalidTransactionId;      done by above MemSet */
}

2.1.2 smgrextend

/*
 *  smgrextend() -- 为文件添加文件块.
 */
void
smgrextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,
           char *buffer, bool skipFsync)
{
    (*(smgrsw[reln->smgr_which].smgr_extend)) (reln, forknum, blocknum,
                                               buffer, skipFsync);
}

2.2 FSM的查找

    函数fsm_search利用FSM文件查找一个具有指定空闲空间级别的表块。

fsm_search函数的源码分析如下:

/*
 * 在三层树结构中寻找至少具有min_cat FSM级别的表块
 * 入参:
*           rel: 缓存中的表的基本信息
*           min_cat:最小空闲空间级别
* 出参:
*           BlockNumber:满足条件的表块的块号
 */
static BlockNumber
fsm_search(Relation rel, uint8 min_cat)
{
    int         restarts = 0;
    FSMAddress  addr = FSM_ROOT_ADDRESS;

    for (;;)
    {
        int         slot;
        Buffer      buf;
        uint8       max_avail = 0;

        /* 将FSM块装入缓冲区. */
        buf = fsm_readbuf(rel, addr, false);

        /* 在当前FSM块内的大根堆内搜索满足条件的叶子节点的序号slot */
        // 主要调用函数fsm_search_avail完成块内搜索
        if (BufferIsValid(buf))
        {
            LockBuffer(buf, BUFFER_LOCK_SHARE);
            slot = fsm_search_avail(buf, min_cat,
                                    (addr.level == FSM_BOTTOM_LEVEL),
                                    false);
          //如果当前块内无法找到满足条件的叶子节点,记录当前块内
          //最大的FSM级别为max_avil
            if (slot == -1)
                max_avail = fsm_get_max_avail(BufferGetPage(buf));
            UnlockReleaseBuffer(buf);
        }
        else
            slot = -1;

        //如果在当前FSM内能找到满足条件的叶子节点,
        if (slot != -1)
        {
        //并且如果当前的块指针
        //已经到达了三层树结构的底层,调用fsm_get_heap_blk函数计算
        //出slot对应的表块号作为返回值返回
            if (addr.level == FSM_BOTTOM_LEVEL)
                return fsm_get_heap_blk(addr, slot);
                //如果当前块指针还未到达三层树结构底层,
         // 说明还在第0层或第1层辅助层,调用fsm_get_child函数获取下一个要搜索的FSM块,重新开始循环
            addr = fsm_get_child(addr, slot);
        }
          //如果在当前FSM内不能找到满足条件的叶子节点,且已到达三层
          //树结构的底层,说明整个FSM文件都没有满足要求的,返回 
          //InvalidBlockNumber
        else if (addr.level == FSM_ROOT_LEVEL)
        {
            return InvalidBlockNumber;
        }
          //如果在当前FSM内不能找到满足条件的叶子节点,且未到达三层
          //树结构的底层,这种情况有可能是因为更低层的FSM块的最大空闲
          //空间值发生变化后没有反应到高层的FSM块中去,这时需要更新
          //相关FSM的最大空闲空间值。更新后将块指针重新指向第0块FSM 
          //块,再次从头开始搜索。设置一个计数器,如果从头开始搜索失败
          //的趟数超过10000次,返回无效块号。
        else
        {
            uint16      parentslot;
            FSMAddress  parent;

            parent = fsm_get_parent(addr, &parentslot);
            fsm_set_and_search(rel, parent, parentslot, max_avail, 0);

            if (restarts++ > 10000)
                return InvalidBlockNumber;

            /* Start search all over from the root */
            addr = FSM_ROOT_ADDRESS;
        }
    }
}

依赖的函数:

2.2.1 fsm_readbuf

/*
 * 读取指定FSM块逻辑地址addr对应的FSM块.
 *
 * 如果FSM块不存在,返回InvalidBuffer is returned。如果指定了'extend' 
 * 扩展FSM文件.
 */
static Buffer
fsm_readbuf(Relation rel, FSMAddress addr, bool extend)
{
    BlockNumber blkno = fsm_logical_to_physical(addr);
    Buffer      buf;

    RelationOpenSmgr(rel);

    /*
     * If we haven't cached the size of the FSM yet, check it first.  Also
     * recheck if the requested block seems to be past end, since our cached
     * value might be stale.  (We send smgr inval messages on truncation, but
     * not on extension.)
     */
    if (rel->rd_smgr->smgr_fsm_nblocks == InvalidBlockNumber ||
        blkno >= rel->rd_smgr->smgr_fsm_nblocks)
    {
        if (smgrexists(rel->rd_smgr, FSM_FORKNUM))
            rel->rd_smgr->smgr_fsm_nblocks = smgrnblocks(rel->rd_smgr,
                                                         FSM_FORKNUM);
        else
            rel->rd_smgr->smgr_fsm_nblocks = 0;
    }

    /* Handle requests beyond EOF */
    if (blkno >= rel->rd_smgr->smgr_fsm_nblocks)
    {
        if (extend)
            fsm_extend(rel, blkno + 1);
        else
            return InvalidBuffer;
    }

    buf = ReadBufferExtended(rel, FSM_FORKNUM, blkno, RBM_ZERO_ON_ERROR, NULL);
    if (PageIsNew(BufferGetPage(buf)))
    {
        LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
        if (PageIsNew(BufferGetPage(buf)))
            PageInit(BufferGetPage(buf), BLCKSZ, 0);
        LockBuffer(buf, BUFFER_LOCK_UNLOCK);
    }
    return buf;
}

2.2.2 fsm_logical_to_physical

/*
 * 根据FSM块的逻辑地址计算其物理块号
 */
static BlockNumber
fsm_logical_to_physical(FSMAddress addr)
{
    BlockNumber pages;
    int         leafno;
    int         l;

    /*
     * 计算给定FSM块的第一个叶子节点对应的表块的逻辑编号
     */
    leafno = addr.logpageno;
    for (l = 0; l < addr.level; l++)
        leafno *= SlotsPerFSMPage;

    /* Count upper level nodes required to address the leaf page */
    pages = 0;
    for (l = 0; l < FSM_TREE_DEPTH; l++)
    {
        pages += leafno + 1;
        leafno /= SlotsPerFSMPage;
    }

    /*
     * If the page we were asked for wasn't at the bottom level, subtract the
     * additional lower level pages we counted above.
     */
    pages -= addr.level;

    /* Turn the page count into 0-based block number */
    return pages - 1;
}

2.2.3 fsm_search_avail

/*
 * 在指定FSM块内的大根堆查找一个空闲空间级别不小于指定值的叶子节点,
 * 返回叶子节点在fp_nodes[]数组中的编号或-1(无法找到叶子节点)
 *
 * 调用者至少需持有对此FSM块的共享锁。
 *
 * 如果advancenext被设置为false,fp_next_slot =slot , 否则
 * fp_next_slot = slot+1.
 */
int
fsm_search_avail(Buffer buf, uint8 minvalue, bool advancenext,
                 bool exclusive_lock_held)
{
    Page        page = BufferGetPage(buf);
    FSMPage     fsmpage = (FSMPage) PageGetContents(page);
    int         nodeno;
    int         target;
    uint16      slot;

restart:

    /*
     * 首先检查此FSM块内大根堆的根节点,如果其级别小于指定的minvalue,
     * 说明整棵大根堆二叉树所有节点的空闲空闲都不符合要求,直接返回-1并退出。
     */
    if (fsmpage->fp_nodes[0] < minvalue)
        return -1;

    /*
     * 根据fsmpage->fp_next_slot的值,设置当前节点指针nodeno = NonLeafNodesPerPage +
     * fsmpage->fp_next_slot。
     */
    target = fsmpage->fp_next_slot;
    if (target < 0 || target >= LeafNodesPerPage)
        target = 0;
    target += NonLeafNodesPerPage;

    
    // 以target为起点,沿着大根堆二叉树,寻找满足条件的叶子节点
    nodeno = target;
    while (nodeno > 0)
    {
                // 如果当前节点的FSM级别满足要求(不低于指定的值),直接跳出循环。
        if (fsmpage->fp_nodes[nodeno] >= minvalue)
            break;

        /*
         * 设置当前节点指针nodeno为当前节点右兄弟(可回环的,同层最右节点的右兄弟是本层最左节点)的父节点,继续循环
         */
        nodeno = parentof(rightneighbor(nodeno));
    }

    /*
     * 从上面找到的节点指针nodeno开始,向下寻找一条通往叶子节点的路径。
     * 在向下寻径过程中,如果左右子节点都符合条件,优先选取左子节点。该路径最终到达的叶子节点即为最终的结果,将其序号记录为slot。
     */
    while (nodeno < NonLeafNodesPerPage)
    {
        int         childnodeno = leftchild(nodeno);

        if (childnodeno < NodesPerPage &&
            fsmpage->fp_nodes[childnodeno] >= minvalue)
        {
            nodeno = childnodeno;
            continue;
        }
        childnodeno++;          /* point to right child */
        if (childnodeno < NodesPerPage &&
            fsmpage->fp_nodes[childnodeno] >= minvalue)
        {
            nodeno = childnodeno;
        }
        else
        {
            /*
             * 左右子节点FSM级别都不满足条件,可能是在写FSM块时发生了崩溃导致
             *
             * 重建此崩溃FSM块并重新开始.
             */
            RelFileNode rnode;
            ForkNumber  forknum;
            BlockNumber blknum;

            BufferGetTag(buf, &rnode, &forknum, &blknum);
            elog(DEBUG1, "fixing corrupt FSM block %u, relation %u/%u/%u",
                 blknum, rnode.spcNode, rnode.dbNode, rnode.relNode);

            /* make sure we hold an exclusive lock */
            if (!exclusive_lock_held)
            {
                LockBuffer(buf, BUFFER_LOCK_UNLOCK);
                LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
                exclusive_lock_held = true;
            }
            fsm_rebuild_page(page);
            MarkBufferDirtyHint(buf, false);
            goto restart;
        }
    }

    /* 已到达最底层,从nodeno指针中减去非叶子节点个数,得到叶子节点的索引号. */
    slot = nodeno - NonLeafNodesPerPage;

        //如果advancenext 为true,fp_next_slot= slot+1, 否则fp_next_slot = slot
    fsmpage->fp_next_slot = slot + (advancenext ? 1 : 0);

    return slot;
}

2.2.4 大根堆二叉树相关函数

/* Macros to navigate the tree within a page. Root has index zero. */
#define leftchild(x)    (2 * (x) + 1)
#define rightchild(x)   (2 * (x) + 2)
#define parentof(x)     (((x) - 1) / 2)

/*
 * 在大根堆中寻找编号为x的右兄弟编号
 */
static int
rightneighbor(int x)
{
    /*
     * Move right. This might wrap around, stepping to the leftmost node at
     * the next level.
     */
    x++;

    /*
     * Check if we stepped to the leftmost node at next level, and correct if
     * so. The leftmost nodes at each level are numbered x = 2^level - 1, so
     * check if (x + 1) is a power of two, using a standard
     * twos-complement-arithmetic trick.
     */
    if (((x + 1) & x) == 0)
        x = parentof(x);

    return x;
}

2.2.5 fsm_get_parent

/*
 * Given a logical address of a child page, get the logical page number of
 * the parent, and the slot within the parent corresponding to the child.
 */
static FSMAddress
fsm_get_parent(FSMAddress child, uint16 *slot)
{
    FSMAddress  parent;

    Assert(child.level < FSM_ROOT_LEVEL);

    parent.level = child.level + 1;
    parent.logpageno = child.logpageno / SlotsPerFSMPage;
    *slot = child.logpageno % SlotsPerFSMPage;

    return parent;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,657评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,662评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,143评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,732评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,837评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,036评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,126评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,868评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,315评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,641评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,773评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,859评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,584评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,676评论 2 351

推荐阅读更多精彩内容

  • feisky云计算、虚拟化与Linux技术笔记posts - 1014, comments - 298, trac...
    不排版阅读 3,833评论 0 5
  • 0. 前言 主要记录对彭智勇和彭煜玮老师所著《PostgreSQL数据库内核分析》一书的观后总结,用于读薄这本经典...
    Kop李小阳阅读 875评论 0 0
  • 1、文件和文件系统 文件管理:把所管理的程序和数据组织成一系列的文件,并能进行合理的存储、使用等操作。 1 )基本...
    盆栽木只阅读 1,326评论 0 0
  • MySql--InnoDB的表空间 具体细节 请去掘金购买《MySQL 是怎样运行的:从根儿上理解 MySQL》 ...
    简书徐小耳阅读 2,512评论 0 1
  • 想法产生感觉,感觉产生行动,行动产生结果。 记得妈妈常常对我们说,只要你们身体健康,安安稳稳的过日子就可以了。大富...
    哈伦2019阅读 129评论 0 0