PostgreSQL 源碼解讀(38)- 查詢語句#23(query_planner函數(shù)#1)

上一小節(jié)已介紹了grouping_planner函數(shù),該函數(shù)通過調用query_planner函數(shù)為掃描/連接部分生成最優(yōu)的未排序和預排序(presorted)路徑,本節(jié)主要介紹query_planner函數(shù)對簡單SQL如SELECT 2+2;的處理邏輯愚战。

一、重要的數(shù)據(jù)結構

RelOptInfo
查詢語句經過查詢重寫/表達式簡化/外連接消除等處理后,查詢樹Query已完成規(guī)范化,查詢樹中的RangeTblEntry(RTE)數(shù)據(jù)結構已完成其歷史使命,由此可進入邏輯優(yōu)化處理,在此階段使用RelOptInfo數(shù)據(jù)結構.

 typedef enum RelOptKind
 {
     RELOPT_BASEREL,//基本關系(如基表/子查詢等)
     RELOPT_JOINREL,//連接產生的關系,要注意的是通過連接等方式產生的結果亦可以視為關系
     RELOPT_OTHER_MEMBER_REL,
     RELOPT_OTHER_JOINREL,
     RELOPT_UPPER_REL,//上層的關系
     RELOPT_OTHER_UPPER_REL,
     RELOPT_DEADREL
 } RelOptKind;
 
 /*
  * Is the given relation a simple relation i.e a base or "other" member
  * relation?
  */
 #define IS_SIMPLE_REL(rel) \
     ((rel)->reloptkind == RELOPT_BASEREL || \
      (rel)->reloptkind == RELOPT_OTHER_MEMBER_REL)
 
 /* Is the given relation a join relation? */
 #define IS_JOIN_REL(rel)    \
     ((rel)->reloptkind == RELOPT_JOINREL || \
      (rel)->reloptkind == RELOPT_OTHER_JOINREL)
 
 /* Is the given relation an upper relation? */
 #define IS_UPPER_REL(rel)   \
     ((rel)->reloptkind == RELOPT_UPPER_REL || \
      (rel)->reloptkind == RELOPT_OTHER_UPPER_REL)
 
 /* Is the given relation an "other" relation? */
 #define IS_OTHER_REL(rel) \
     ((rel)->reloptkind == RELOPT_OTHER_MEMBER_REL || \
      (rel)->reloptkind == RELOPT_OTHER_JOINREL || \
      (rel)->reloptkind == RELOPT_OTHER_UPPER_REL)
 
 typedef struct RelOptInfo
 {
     NodeTag     type;//節(jié)點標識
 
     RelOptKind  reloptkind;//RelOpt類型
 
     /* all relations included in this RelOptInfo */
     Relids      relids;         /*Relids(rtindex)集合 set of base relids (rangetable indexes) */
 
     /* size estimates generated by planner */
     double      rows;           /*結果元組的估算數(shù)量 estimated number of result tuples */
 
     /* per-relation planner control flags */
     bool        consider_startup;   /*是否考慮啟動成本?是,需要保留啟動成本低的路徑 keep cheap-startup-cost paths? */
     bool        consider_param_startup; /*是否考慮參數(shù)化?的路徑 ditto, for parameterized paths? */
     bool        consider_parallel;  /*是否考慮并行處理路徑 consider parallel paths? */
 
     /* default result targetlist for Paths scanning this relation */
     struct PathTarget *reltarget;   /*掃描該Relation時默認的結果 list of Vars/Exprs, cost, width */
 
     /* materialization information */
     List       *pathlist;       /*訪問路徑鏈表 Path structures */
     List       *ppilist;        /*路徑鏈表中使用參數(shù)化路徑進行 ParamPathInfos used in pathlist */
     List       *partial_pathlist;   /* partial Paths */
     struct Path *cheapest_startup_path;//代價最低的啟動路徑
     struct Path *cheapest_total_path;//代價最低的整體路徑
     struct Path *cheapest_unique_path;//代價最低的獲取唯一值的路徑
     List       *cheapest_parameterized_paths;//代價最低的參數(shù)化?路徑鏈表
 
     /* parameterization information needed for both base rels and join rels */
     /* (see also lateral_vars and lateral_referencers) */
     Relids      direct_lateral_relids;  /*使用lateral語法,需依賴的Relids rels directly laterally referenced */
     Relids      lateral_relids; /* minimum parameterization of rel */
 
     /* information about a base rel (not set for join rels!) */
   //reloptkind=RELOPT_BASEREL時使用的數(shù)據(jù)結構
     Index       relid;      /* Relation ID */
     Oid         reltablespace;  /* 表空間 containing tablespace */
     RTEKind     rtekind;        /* 基表?子查詢?還是函數(shù)等等?RELATION, SUBQUERY, FUNCTION, etc */
     AttrNumber  min_attr;       /* 最小的屬性編號 smallest attrno of rel (often <0) */
     AttrNumber  max_attr;       /* 最大的屬性編號 largest attrno of rel */
     Relids     *attr_needed;    /* 數(shù)組 array indexed [min_attr .. max_attr] */
     int32      *attr_widths;    /* 屬性寬度 array indexed [min_attr .. max_attr] */
     List       *lateral_vars;   /* 關系依賴的Vars/PHVs LATERAL Vars and PHVs referenced by rel */
     Relids      lateral_referencers;    /*依賴該關系的Relids rels that reference me laterally */
     List       *indexlist;      /* 該關系的IndexOptInfo鏈表 list of IndexOptInfo */
     List       *statlist;       /* 統(tǒng)計信息鏈表 list of StatisticExtInfo */
     BlockNumber pages;          /* 塊數(shù) size estimates derived from pg_class */
     double      tuples;     /* 元組數(shù) */
     double      allvisfrac;   /* ? */
     PlannerInfo *subroot;       /* 如為子查詢,存儲子查詢的root if subquery */
     List       *subplan_params; /* 如為子查詢,存儲子查詢的參數(shù) if subquery */
     int         rel_parallel_workers;   /* 并行執(zhí)行,需要多少個workers? wanted number of parallel workers */
 
     /* Information about foreign tables and foreign joins */
   //FWD相關信息
     Oid         serverid;       /* identifies server for the table or join */
     Oid         userid;         /* identifies user to check access as */
     bool        useridiscurrent;    /* join is only valid for current user */
     /* use "struct FdwRoutine" to avoid including fdwapi.h here */
     struct FdwRoutine *fdwroutine;
     void       *fdw_private;
 
     /* cache space for remembering if we have proven this relation unique */
   //已知的,可保證唯一的Relids鏈表
     List       *unique_for_rels;    /* known unique for these other relid
                                      * set(s) */
     List       *non_unique_for_rels;    /* 已知的,不唯一的Relids鏈表 known not unique for these set(s) */
 
     /* used by various scans and joins: */
     List       *baserestrictinfo;   /* 如為基本關系,存儲約束條件 RestrictInfo structures (if base rel) */
     QualCost    baserestrictcost;   /* 解析約束表達式的成本? cost of evaluating the above */
     Index       baserestrict_min_security;  /* 最低安全等級 min security_level found in
                                              * baserestrictinfo */
     List       *joininfo;       /* 連接語句的約束條件信息 RestrictInfo structures for join clauses
                                  * involving this rel */
     bool        has_eclass_joins;   /* 是否存在等價類連接? T means joininfo is incomplete */
 
     /* used by partitionwise joins: */
     bool        consider_partitionwise_join;    /* 分區(qū)? consider partitionwise
                                                  * join paths? (if
                                                  * partitioned rel) */
     Relids      top_parent_relids;  /* Relids of topmost parents (if "other"
                                      * rel) */
 
     /* used for partitioned relations */
   //分區(qū)表使用
     PartitionScheme part_scheme;    /* 分區(qū)的schema Partitioning scheme. */
     int         nparts;         /* 分區(qū)數(shù) number of partitions */
     struct PartitionBoundInfoData *boundinfo;   /* 分區(qū)邊界信息 Partition bounds */
     List       *partition_qual; /* 分區(qū)約束 partition constraint */
     struct RelOptInfo **part_rels;  /* 分區(qū)的RelOptInfo數(shù)組 Array of RelOptInfos of partitions,
                                      * stored in the same order of bounds */
     List      **partexprs;      /* 非空分區(qū)鍵表達式 Non-nullable partition key expressions. */
     List      **nullable_partexprs; /* 可為空的分區(qū)鍵表達式 Nullable partition key expressions. */
     List       *partitioned_child_rels; /* RT Indexes鏈表 List of RT indexes. */
 } RelOptInfo;

PathCostComparison

 typedef enum
 {
     COSTS_EQUAL,                /* 近似相等 path costs are fuzzily equal */
     COSTS_BETTER1,              /* 第一個路徑成本較低 first path is cheaper than second */
     COSTS_BETTER2,              /* 第二個相對較低 second path is cheaper than first */
     COSTS_DIFFERENT             /* 不管是哪個路徑,成本上都不占優(yōu)勢 neither path dominates the other on cost */
 } PathCostComparison;
 

ResultPath

 /*
  * ResultPath represents use of a Result plan node to compute a variable-free
  * targetlist with no underlying tables (a "SELECT expressions" query).
  * The query could have a WHERE clause, too, represented by "quals".
  *
  * Note that quals is a list of bare clauses, not RestrictInfos.
  */
 typedef struct ResultPath //表示無基礎表的結果計劃節(jié)點
 {
     Path        path;//掃描路徑
     List       *quals;//where語句表達式,bare clauses, not RestrictInfos
 } ResultPath;

二、源碼解讀

 /*
  * query_planner
  *    Generate a path (that is, a simplified plan) for a basic query,
  *    which may involve joins but not any fancier features.
  *
  * 為一個基本的查詢(可能涉及連接)生成訪問路徑(也可以視為一個簡化的計劃).
  *
  * Since query_planner does not handle the toplevel processing (grouping,
  * sorting, etc) it cannot select the best path by itself.  Instead, it
  * returns the RelOptInfo for the top level of joining, and the caller
  * (grouping_planner) can choose among the surviving paths for the rel.
  *
  * query_planner不會處理頂層的處理過程(如最后的分組/排序等操作),因此,不能選擇最優(yōu)的訪問路徑
  * 該函數(shù)會返回RelOptInfo給最高層的連接,grouping_planner可以在剩下的路徑中進行選擇
  *
  * root describes the query to plan
  * tlist is the target list the query should produce
  *      (this is NOT necessarily root->parse->targetList!)
  * qp_callback is a function to compute query_pathkeys once it's safe to do so
  * qp_extra is optional extra data to pass to qp_callback
  *
  * root是計劃信息/tlist是投影列
  * qp_callback是計算query_pathkeys的函數(shù)/qp_extra是傳遞給qp_callback的函數(shù)
  *
  * Note: the PlannerInfo node also includes a query_pathkeys field, which
  * tells query_planner the sort order that is desired in the final output
  * plan.  This value is *not* available at call time, but is computed by
  * qp_callback once we have completed merging the query's equivalence classes.
  * (We cannot construct canonical pathkeys until that's done.)
  */
 RelOptInfo *
 query_planner(PlannerInfo *root, List *tlist,
               query_pathkeys_callback qp_callback, void *qp_extra)
 {
     Query      *parse = root->parse;//查詢樹
     List       *joinlist;
     RelOptInfo *final_rel;//結果
     Index       rti;//RTE的index
     double      total_pages;//總pages數(shù)
 
     /*
      * If the query has an empty join tree, then it's something easy like
      * "SELECT 2+2;" or "INSERT ... VALUES()".  Fall through quickly.
      */
     if (parse->jointree->fromlist == NIL)//簡單SQL,無FROM/WHERE語句
     {
         /* We need a dummy joinrel to describe the empty set of baserels */
         final_rel = build_empty_join_rel(root);//創(chuàng)建返回結果
 
         /*
          * If query allows parallelism in general, check whether the quals are
          * parallel-restricted.  (We need not check final_rel->reltarget
          * because it's empty at this point.  Anything parallel-restricted in
          * the query tlist will be dealt with later.)
          */
         if (root->glob->parallelModeOK)//并行模式?
             final_rel->consider_parallel =
                 is_parallel_safe(root, parse->jointree->quals);
 
         /* The only path for it is a trivial Result path */
         add_path(final_rel, (Path *)
                  create_result_path(root, final_rel,
                                     final_rel->reltarget,
                                     (List *) parse->jointree->quals));//添加訪問路徑
 
         /* Select cheapest path (pretty easy in this case...) */
         set_cheapest(final_rel);//選擇最優(yōu)的訪問路徑
 
         /*
          * We still are required to call qp_callback, in case it's something
          * like "SELECT 2+2 ORDER BY 1".
          */
         root->canon_pathkeys = NIL;
         (*qp_callback) (root, qp_extra);//回調函數(shù)
 
         return final_rel;//返回
     }
 
     //其他代碼
     ...
 }

add_path/set_cheapest
后續(xù)再行介紹

create_result_path

 /*
  * create_result_path
  *    Creates a path representing a Result-and-nothing-else plan.
  *
  * This is only used for degenerate cases, such as a query with an empty
  * jointree.
  */
 ResultPath *
 create_result_path(PlannerInfo *root, RelOptInfo *rel,
                    PathTarget *target, List *resconstantqual)
 {
     ResultPath *pathnode = makeNode(ResultPath);//結果
 
     pathnode->path.pathtype = T_Result;//掃描路徑類型
     pathnode->path.parent = rel;//路徑的partentbuild_empty_join_rel
     pathnode->path.pathtarget = target;//目標列
     pathnode->path.param_info = NULL;   /* there are no other rels... */
     pathnode->path.parallel_aware = false;
     pathnode->path.parallel_safe = rel->consider_parallel;
     pathnode->path.parallel_workers = 0;//并行workers數(shù)目,設置為0
     pathnode->path.pathkeys = NIL;//
     pathnode->quals = resconstantqual;//表達式
 
     /* Hardly worth defining a cost_result() function ... just do it */
     pathnode->path.rows = 1;//行數(shù)為1
     pathnode->path.startup_cost = target->cost.startup;
     pathnode->path.total_cost = target->cost.startup +
         cpu_tuple_cost + target->cost.per_tuple;
 
     /*
      * Add cost of qual, if any --- but we ignore its selectivity, since our
      * rowcount estimate should be 1 no matter what the qual is.
      */
     if (resconstantqual)
     {
         QualCost    qual_cost;
 
         cost_qual_eval(&qual_cost, resconstantqual, root);
         /* resconstantqual is evaluated once at startup */
         pathnode->path.startup_cost += qual_cost.startup + qual_cost.per_tuple;
         pathnode->path.total_cost += qual_cost.startup + qual_cost.per_tuple;
     }
 
     return pathnode;
 }

build_empty_join_rel

/*
  * build_empty_join_rel
  *      Build a dummy join relation describing an empty set of base rels.
  *
  * This is used for queries with empty FROM clauses, such as "SELECT 2+2" or
  * "INSERT INTO foo VALUES(...)".  We don't try very hard to make the empty
  * joinrel completely valid, since no real planning will be done with it ---
  * we just need it to carry a simple Result path out of query_planner().
  */
 RelOptInfo *
 build_empty_join_rel(PlannerInfo *root)
 {
     RelOptInfo *joinrel;
 
     /* The dummy join relation should be the only one ... */
     Assert(root->join_rel_list == NIL);
 
     joinrel = makeNode(RelOptInfo);
     joinrel->reloptkind = RELOPT_JOINREL;
     joinrel->relids = NULL;     /* empty set */
     joinrel->rows = 1;          /* we produce one row for such cases */
     joinrel->rtekind = RTE_JOIN;
     joinrel->reltarget = create_empty_pathtarget();
 
     root->join_rel_list = lappend(root->join_rel_list, joinrel);
 
     return joinrel;
 }

三浊猾、跟蹤分析

(gdb) b query_planner
Breakpoint 1 at 0x76942c: file planmain.c, line 57.
(gdb) c
Continuing.

Breakpoint 1, query_planner (root=0x275c878, tlist=0x277fd78, qp_callback=0x76e97d <standard_qp_callback>, 
    qp_extra=0x7ffdd435d490) at planmain.c:57
57    Query    *parse = root->parse;
(gdb) n
67    if (parse->jointree->fromlist == NIL)
(gdb) 
70      final_rel = build_empty_join_rel(root);
(gdb) 
78      if (root->glob->parallelModeOK)
#創(chuàng)建的空RELOPT_JOINREL
(gdb) p *final_rel
$4 = {type = T_RelOptInfo, reloptkind = RELOPT_JOINREL, relids = 0x0, rows = 1, consider_startup = false, 
  consider_param_startup = false, consider_parallel = false, reltarget = 0x277fda8, pathlist = 0x0, ppilist = 0x0, 
  partial_pathlist = 0x0, cheapest_startup_path = 0x0, cheapest_total_path = 0x0, cheapest_unique_path = 0x0, 
  cheapest_parameterized_paths = 0x0, direct_lateral_relids = 0x0, lateral_relids = 0x0, relid = 0, reltablespace = 0, 
  rtekind = RTE_JOIN, min_attr = 0, max_attr = 0, attr_needed = 0x0, attr_widths = 0x0, lateral_vars = 0x0, 
  lateral_referencers = 0x0, indexlist = 0x0, statlist = 0x0, pages = 0, tuples = 0, allvisfrac = 0, subroot = 0x0, 
  subplan_params = 0x0, rel_parallel_workers = 0, serverid = 0, userid = 0, useridiscurrent = false, fdwroutine = 0x0, 
  fdw_private = 0x0, unique_for_rels = 0x0, non_unique_for_rels = 0x0, baserestrictinfo = 0x0, baserestrictcost = {
    startup = 0, per_tuple = 0}, baserestrict_min_security = 0, joininfo = 0x0, has_eclass_joins = false, 
  top_parent_relids = 0x0, part_scheme = 0x0, nparts = 0, boundinfo = 0x0, partition_qual = 0x0, part_rels = 0x0, 
  partexprs = 0x0, nullable_partexprs = 0x0, partitioned_child_rels = 0x0}
...
(gdb) step
add_path (parent_rel=0x275cc88, new_path=0x275c498) at pathnode.c:424
424   bool    accept_new = true;  /* unless we find a superior old path */
#創(chuàng)建的path(ResultPath)
(gdb) p *new_path
$6 = {type = T_ResultPath, pathtype = T_Result, parent = 0x275cc88, pathtarget = 0x277fda8, param_info = 0x0, 
  parallel_aware = false, parallel_safe = true, parallel_workers = 0, rows = 1, startup_cost = 0, total_cost = 0.01, 
  pathkeys = 0x0}
(gdb) finish
Run till exit from #0  add_path (parent_rel=0x275cc88, new_path=0x275c498) at pathnode.c:425
query_planner (root=0x275c878, tlist=0x277fd78, qp_callback=0x76e97d <standard_qp_callback>, qp_extra=0x7ffdd435d490)
    at planmain.c:89
89      set_cheapest(final_rel);
...
98      return final_rel;
(gdb) 
267 }
#返回值
(gdb) p *final_rel
$8 = {type = T_RelOptInfo, reloptkind = RELOPT_JOINREL, relids = 0x0, rows = 1, consider_startup = false, 
  consider_param_startup = false, consider_parallel = true, reltarget = 0x277fda8, pathlist = 0x277fe68, ppilist = 0x0, 
  partial_pathlist = 0x0, cheapest_startup_path = 0x275c498, cheapest_total_path = 0x275c498, cheapest_unique_path = 0x0, 
  cheapest_parameterized_paths = 0x277feb8, direct_lateral_relids = 0x0, lateral_relids = 0x0, relid = 0, 
  reltablespace = 0, rtekind = RTE_JOIN, min_attr = 0, max_attr = 0, attr_needed = 0x0, attr_widths = 0x0, 
  lateral_vars = 0x0, lateral_referencers = 0x0, indexlist = 0x0, statlist = 0x0, pages = 0, tuples = 0, allvisfrac = 0, 
  subroot = 0x0, subplan_params = 0x0, rel_parallel_workers = 0, serverid = 0, userid = 0, useridiscurrent = false, 
  fdwroutine = 0x0, fdw_private = 0x0, unique_for_rels = 0x0, non_unique_for_rels = 0x0, baserestrictinfo = 0x0, 
  baserestrictcost = {startup = 0, per_tuple = 0}, baserestrict_min_security = 0, joininfo = 0x0, has_eclass_joins = false, 
  top_parent_relids = 0x0, part_scheme = 0x0, nparts = 0, boundinfo = 0x0, partition_qual = 0x0, part_rels = 0x0, 
  partexprs = 0x0, nullable_partexprs = 0x0, partitioned_child_rels = 0x0}
(gdb) p *final_rel->cheapest_total_path
$9 = {type = T_ResultPath, pathtype = T_Result, parent = 0x275cc88, pathtarget = 0x277fda8, param_info = 0x0, 
  parallel_aware = false, parallel_safe = true, parallel_workers = 0, rows = 1, startup_cost = 0, total_cost = 0.01, 
  pathkeys = 0x0}
(gdb) 
#DONE!

四陨界、參考資料

planmain.c

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末帮毁,一起剝皮案震驚了整個濱河市幌氮,隨后出現(xiàn)的幾起案子眷蜓,更是在濱河造成了極大的恐慌,老刑警劉巖想鹰,帶你破解...
    沈念sama閱讀 222,252評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件紊婉,死亡現(xiàn)場離奇詭異,居然都是意外死亡辑舷,警方通過查閱死者的電腦和手機喻犁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來何缓,“玉大人肢础,你說我怎么就攤上這事÷道” “怎么了传轰?”我有些...
    開封第一講書人閱讀 168,814評論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長谷婆。 經常有香客問我慨蛙,道長,這世上最難降的妖魔是什么纪挎? 我笑而不...
    開封第一講書人閱讀 59,869評論 1 299
  • 正文 為了忘掉前任期贫,我火速辦了婚禮,結果婚禮上异袄,老公的妹妹穿的比我還像新娘通砍。我一直安慰自己,他們只是感情好隙轻,可當我...
    茶點故事閱讀 68,888評論 6 398
  • 文/花漫 我一把揭開白布埠帕。 她就那樣靜靜地躺著,像睡著了一般玖绿。 火紅的嫁衣襯著肌膚如雪敛瓷。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,475評論 1 312
  • 那天斑匪,我揣著相機與錄音呐籽,去河邊找鬼。 笑死蚀瘸,一個胖子當著我的面吹牛狡蝶,可吹牛的內容都是我干的。 我是一名探鬼主播贮勃,決...
    沈念sama閱讀 41,010評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼贪惹,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了寂嘉?” 一聲冷哼從身側響起奏瞬,我...
    開封第一講書人閱讀 39,924評論 0 277
  • 序言:老撾萬榮一對情侶失蹤枫绅,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后硼端,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體并淋,經...
    沈念sama閱讀 46,469評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,552評論 3 342
  • 正文 我和宋清朗相戀三年珍昨,在試婚紗的時候發(fā)現(xiàn)自己被綠了县耽。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,680評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡镣典,死狀恐怖兔毙,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情兄春,我是刑警寧澤瞒御,帶...
    沈念sama閱讀 36,362評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站神郊,受9級特大地震影響肴裙,放射性物質發(fā)生泄漏。R本人自食惡果不足惜涌乳,卻給世界環(huán)境...
    茶點故事閱讀 42,037評論 3 335
  • 文/蒙蒙 一蜻懦、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧夕晓,春花似錦宛乃、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至躬贡,卻和暖如春谆奥,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背拂玻。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評論 1 274
  • 我被黑心中介騙來泰國打工酸些, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人檐蚜。 一個月前我還...
    沈念sama閱讀 49,099評論 3 378
  • 正文 我出身青樓魄懂,卻偏偏與公主長得像,于是被迫代替她去往敵國和親闯第。 傳聞我的和親對象是個殘疾皇子市栗,可洞房花燭夜當晚...
    茶點故事閱讀 45,691評論 2 361

推薦閱讀更多精彩內容