PostgreSQL数据库统计信息——analyze流程对不同表的处理
创建分区表举例
使用手册提供的例子说明问题:假定我们正在为一个大型的冰激凌公司构建数据库。该公司每天测量最高温度以及每个区域的冰激凌销售情况。概念上,我们想要一个这样的表。
CREATE TABLE measurement (
city_id int not null,
logdate date not null,
peaktemp int,
unitsales int
);
我们知道大部分查询只会访问上周的、上月的或者上季度的数据,因为这个表的主要用途是为管理层准备在线报告。为了减少需要被存放的旧数据量,我们决定只保留最近3年的数据。在每个月的开始我们将去除掉最早的那个月的数据。在这种情况下我们可以使用分区技术来帮助我们满足对measurement表的所有不同需求。要在这种情况下使用声明式分区,可采用下面的步骤:
通过指定PARTITION BY子句把measurement表创建为分区表,该子句包括分区方法(这个例子中是RANGE)以及用作分区键的列列表。
CREATE TABLE measurement (
city_id int not null,
logdate date not null,
peaktemp int,
unitsales int
) PARTITION BY RANGE (logdate);
我们可以通过select relkind, relispartition, relhassubclass from pg_class where relname = 'measurement'
来查看该表目前的相关属性。
select relkind, relispartition, relhassubclass from pg_class where relname = 'measurement';
| relkind | relispartition | relhassubclass |
| p | f | f | # measurement为分区表,该表不是子分区,目前没有下级子表
你可能需要决定在分区键中使用多列进行范围分区。当然,这通常会导致较大数量的分区,其中每一个个体都比较小。另一方面,使用较少的列可能会导致粗粒度的分区策略得到较少数量的分区。如果条件涉及这些列中的一部分或者全部,访问分区表的查询将不得不扫描较少的分区。例如,考虑一个使用列lastname和firstname(按照这样的顺序)作为分区键进行范围分区的表。
创建分区。每个分区的定义必须指定对应于父表的分区方法和分区键的边界。注意,如果指定的边界使得新分区的值会与已有分区中的值重叠,则会导致错误。向父表中插入无法映射到任何现有分区的数据将会导致错误,这种情况下应该手工增加一个合适的分区。分区以普通PostgreSQL表(或者可能是外部表)的方式创建。可以为每个分区单独指定表空间和存储参数。
CREATE TABLE measurement_y2006m02 PARTITION OF measurement
FOR VALUES FROM ('2006-02-01') TO ('2006-03-01');
CREATE TABLE measurement_y2006m03 PARTITION OF measurement
FOR VALUES FROM ('2006-03-01') TO ('2006-04-01');
...
CREATE TABLE measurement_y2007m11 PARTITION OF measurement
FOR VALUES FROM ('2007-11-01') TO ('2007-12-01');
CREATE TABLE measurement_y2007m12 PARTITION OF measurement
FOR VALUES FROM ('2007-12-01') TO ('2008-01-01')
TABLESPACE fasttablespace;
CREATE TABLE measurement_y2008m01 PARTITION OF measurement
FOR VALUES FROM ('2008-01-01') TO ('2008-02-01')
WITH (parallel_workers = 4)
TABLESPACE fasttablespace;
我们可以通过select relkind, relispartition, relhassubclass from pg_class where relname = 'measurement_y2007m11'
来查看该表目前的相关属性。
select relkind, relispartition, relhassubclass from pg_class where relname = 'measurement';
| relkind | relispartition | relhassubclass |
| p | f | t | # measurement为分区表,该表不是子分区,有下级子表
select relkind, relispartition, relhassubclass from pg_class where relname = 'measurement_y2006m02';
| relkind | relispartition | relhassubclass |
| r | t | f | # measurement为普通表,该表是子分区,没有下级子表
select relkind, relispartition, relhassubclass from pg_class where relname = 'measurement_y2006m03';
| relkind | relispartition | relhassubclass |
| r | t | f | # measurement为普通表,该表是子分区,没有下级子表
...
为了实现子分区,在创建分区的命令中指定PARTITION BY子句,例如:
CREATE TABLE measurement_y2006m01 PARTITION OF measurement
FOR VALUES FROM ('2006-01-01') TO ('2006-02-01')
PARTITION BY RANGE (peaktemp);
在创建了measurement_y2006m01的分区之后,任何被插入到measurement中且被映射到measurement_y2006m01的数据(或者直接被插入到measurement_y2006m01的数据,假定它满足这个分区的分区约束)将被基于peaktemp列进一步重定向到measurement_y2006m01的一个分区。指定的分区键可以与父亲的分区键重叠,不过在指定子分区的边界时要注意它接受的数据集合是分区自身边界允许的数据集合的一个子集,系统不会尝试检查事情情况是否如此。
我们可以通过select relkind, relispartition, relhassubclass from pg_class where relname = 'measurement_y2006m01来查看该表目前的相关属性。
select relkind, relispartition, relhassubclass from pg_class where relname = 'measurement';
| relkind | relispartition | relhassubclass |
| p | f | t | # measurement为分区表,该表不是子分区,有下级子表
select relkind, relispartition, relhassubclass from pg_class where relname = 'measurement_y2006m01';
| relkind | relispartition | relhassubclass |
| p | t | f | # measurement为分区表,该表是子分区,没有下级子表(还没创建子分区)
select relkind, relispartition, relhassubclass from pg_class where relname = 'measurement_y2006m02';
| relkind | relispartition | relhassubclass |
| r | t | f | # measurement为普通表,该表是子分区,没有下级子表
上述流程创建的表的关系如下所示:
表名 relkind relispartition relhassubclass
measurement 分区表 父表(非分区) 有下级子表
| - measurement_y2006m01 分区表 子分区 无下级子表
| - measurement_y2006m02 普通表 子分区 无下级子表
| - measurement_y2006m03 普通表 子分区 无下级子表
| - measurement_y2006m04 普通表 子分区 无下级子表
创建继承表举例
虽然内建的声明式分区适合于大部分常见的用例,但还是有一些场景需要更加灵活的方法。分区可以使用表继承来实现,这能够带来一些声明式分区不支持的特性,例如:
对声明式分区来说,分区必须具有和分区表正好相同的列集合,而在表继承中,子表可以有父表中没有出现过的额外列。
表继承允许多继承。
声明式分区仅支持范围、列表以及哈希分区,而表继承允许数据按照用户的选择来划分(不过注意,如果约束排除不能有效地剪枝子表,查询性能可能会很差)。
在使用声明式分区时,一些操作比使用表继承时要求更长的持锁时间。例如,向分区表中增加分区或者从分区表移除分区要求在父表上取得一个ACCESS EXCLUSIVE锁,而在常规继承的情况下一个SHARE UPDATE EXCLUSIVE锁就足够了。
我们使用上面用过的同一个measurement表。我们可以通过select relkind, relispartition, relhassubclass from pg_class where relname = 'measurement'
来查看该表目前的相关属性。
select relkind, relispartition, relhassubclass from pg_class where relname = 'measurement';
| relkind | relispartition | relhassubclass |
| r | f | f | # measurement为普通,该表不是子分区,目前没有下级子表
为了使用继承实现分区,可使用下面的步骤:创建“主”表,所有的“子”表都将从它继承。这个表将不包含数据。不要在这个表上定义任何检查约束,除非想让它们应用到所有的子表上。同样,在这个表上定义索引或者唯一约束也没有意义。对于我们的例子来说,主表是最初定义的measurement表。创建数个“子”表,每一个都从主表继承。通常,这些表将不会在从主表继承的列集合之外增加任何列。正如声明性分区那样,这些表就是普通的PostgreSQL表(或者外部表)。
CREATE TABLE measurement_y2006m02 () INHERITS (measurement);
CREATE TABLE measurement_y2006m03 () INHERITS (measurement);
...
CREATE TABLE measurement_y2007m11 () INHERITS (measurement);
CREATE TABLE measurement_y2007m12 () INHERITS (measurement);
CREATE TABLE measurement_y2008m01 () INHERITS (measurement);
我们可以通过select relkind, relispartition, relhassubclass from pg_class where relname = 'measurement'
来查看该表目前的相关属性。
select relkind, relispartition, relhassubclass from pg_class where relname = 'measurement';
| relkind | relispartition | relhassubclass |
| r | f | t | # measurement为普通,该表不是子分区,有下级子表
select relkind, relispartition, relhassubclass from pg_class where relname = 'measurement_y2006m02';
| relkind | relispartition | relhassubclass |
| r | f | f | # measurement_y2006m02为普通,该表不是子分区,目前没有下级子表
如果我们再创建一个表measurement_y2006m01继承自measurement,创建一个表measurement_y2006m01d01继承自measurement_y2006m01。我们通过select relkind, relispartition, relhassubclass from pg_class where relname = 'measurement'来查看该表目前的相关属性。
CREATE TABLE measurement_y2006m01 () INHERITS (measurement);
CREATE TABLE measurement_y2006m01d01 () INHERITS (measurement_y2006m01);
select relkind, relispartition, relhassubclass from pg_class where relname = 'measurement';
| relkind | relispartition | relhassubclass |
| r | f | t | # measurement为普通,该表不是子分区,有下级子表
select relkind, relispartition, relhassubclass from pg_class where relname = 'measurement_y2006m01';
| relkind | relispartition | relhassubclass |
| r | f | t | # measurement_y2006m01为普通表,该表不是子分区,有下级子表
select relkind, relispartition, relhassubclass from pg_class where relname = 'measurement_y2006m01d01';
| relkind | relispartition | relhassubclass |
| r | f | f | # measurement_y2006m01d01为普通表,该表不是子分区,目前没有下级子表
为子表增加不重叠的表约束来定义每个分区允许的键值。确保约束能保证不同子表允许的键值之间没有重叠。典型的例子是:
CHECK ( x = 1 )
CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire', 'Warwickshire' ))
CHECK ( outletID >= 100 AND outletID < 200 )
像下面这样创建子表会更好:
CREATE TABLE measurement_y2006m02 (
CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' )
) INHERITS (measurement);
CREATE TABLE measurement_y2006m03 (
CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' )
) INHERITS (measurement);
...
CREATE TABLE measurement_y2007m11 (
CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' )
) INHERITS (measurement);
CREATE TABLE measurement_y2007m12 (
CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' )
) INHERITS (measurement);
CREATE TABLE measurement_y2008m01 (
CHECK ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' )
) INHERITS (measurement);
上述流程创建的表的关系如下所示:
表名 relkind relispartition relhassubclass
measurement 普通表 非分区 有下级子表
| - measurement_y2006m01 普通表 非分区 有下级子表
| - measurement_y2006m01d01 普通表 非分区 无下级子表
| - measurement_y2006m02 普通表 非分区 无下级子表
| - measurement_y2006m03 普通表 非分区 无下级子表
| - measurement_y2006m04 普通表 非分区 无下级子表
ExecVacuum流程对不同表的处理
首先我们向void ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)函数传入VacuumStmt结构体,该函数首先会处理解析VacuumStmt->options选项列表,设置VacuumParams params结构体。最后调用vacuum(vacstmt->rels, ¶ms, NULL, isTopLevel)函数。vacstmt->rels成员存放的是VacuumRelation结构体列表,代表的是需要进行analyze的表relation和列va_cols。
void vacuum(List *relations, VacuumParams *params, BufferAccessStrategy bstrategy, bool isTopLevel)函数的形参relations如果没有设置为NIL,那它就是要进行vacuum的表(a list of VacuumRelation to process);否则就需要处理数据库中所有相关的表。vacuum前期工作是:进行事务块检测、不可重入校验、为跨事务存储创建特殊内存上下文、建立要处理的relation列表和决定是否需要启动/提交自己的事务。这里我们关注建立要处理的relation列表流程。
* Build list of relation(s) to process, putting any new data in vac_context for safekeeping. */
if (relations != NIL) {
List *newrels = NIL; ListCell *lc;
foreach(lc, relations) { / 遍历上图中的VacuumStmt->rels列表
VacuumRelation *vrel = lfirst_node(VacuumRelation, lc);
List *sublist;
MemoryContext old_context;
sublist = expand_vacuum_rel(vrel, params->options); // 调用expand_vacuum_rel函数获取VacuumRelation代表的表所关联的表
old_context = MemoryContextSwitchTo(vac_context);
newrels = list_concat(newrels, sublist);
MemoryContextSwitchTo(old_context);
}
relations = newrels;
} else relations = get_all_vacuum_rels(params->options); // 需要获取数据库中所有相关的表
get_all_vacuum_rels函数流程比较简单,扫描pg_class系统表,把RELKIND_RELATION、RELKIND_MATVIEW、RELKIND_PARTITIONED_TABLE类型的表做成VacuumRelation列表返回。
expand_vacuum_rel函数获取VacuumRelation代表的表所关联的哪些表呢?
如果VacuumRelation->oid被指定了,也就是明确指定分析该表,直接返回。
如果VacuumRelation->oid需要分析表的oid没有被指定(通常直接执行analyze table流程中,需要分析表的oid是没有被指定)。取relid = RangeVarGetRelidExtended(vrel->relation, AccessShareLock, rvr_opts, NULL, NULL)需要分析表的oid;通过通过需要分析的表oid从syscahce查找对应的pg_class条目,通过classForm->relkind对应字段,如果为RELKIND_PARTITIONED_TABLE)【除了分区叶子子表,其他分区表都属于该类,如上例子分析所示】,设置include_parts为true;如果include_parts为true,则需要调用find_all_inheritors函数(上一篇我们介绍过这个函数)查找以relid为根节点表的继承树上的所有子表;最终返回VacuumRelation列表,每个元素代表一个需要分析的表。
比如:分析measurement和measurement_y2006m01表,include_parts就应该设置为true,也就是分区表;其他measurement_y2006m02、measurement_y2006m03和measurement_y2006m04是普通表。如果分析measurement表,如下继承树上的所有子表都将作为VacuumRelation结构体返回;如果分析measurement_y2006m01表,仅仅将measurement_y2006m01表作为VacuumRelation结构体返回。
表名 relkind relispartition relhassubclass
measurement 分区表 父表(非分区) 有下级子表
| - measurement_y2006m01 分区表 子分区 无下级子表
| - measurement_y2006m02 普通表 子分区 无下级子表
| - measurement_y2006m03 普通表 子分区 无下级子表
| - measurement_y2006m04 普通表 子分区 无下级子表
static List *expand_vacuum_rel(VacuumRelation *vrel, int options){
List *vacrels = NIL;
MemoryContext oldcontext;
if (OidIsValid(vrel->oid)) { /* If caller supplied OID, there's nothing we need do here. */
oldcontext = MemoryContextSwitchTo(vac_context);
vacrels = lappend(vacrels, vrel);
MemoryContextSwitchTo(oldcontext);
}else{ /* Process a specific relation, and possibly partitions thereof */
Oid relid; HeapTuple tuple; Form_pg_class classForm;
bool include_parts; int rvr_opts;
/* We transiently take AccessShareLock to protect the syscache lookup below, as well as find_all_inheritors's expectation that the caller holds some lock on the starting relation. */ // 我们暂时使用AccessShareLock来保护下面的syscache查找,以及find_all_inheriters对调用方持有起始关系的某些锁的期望
rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0;
relid = RangeVarGetRelidExtended(vrel->relation, AccessShareLock, rvr_opts, NULL, NULL);
/* If the lock is unavailable, emit the same log statement that vacuum_rel() and analyze_rel() would. */ // 如果锁不可用,则发出与vacuum_rel和analyze_rel相同的日志语句。
if (!OidIsValid(relid)){
if (options & VACOPT_VACUUM) ereport(WARNING,(errcode(ERRCODE_LOCK_NOT_AVAILABLE),errmsg("skipping vacuum of \"%s\" --- lock not available",vrel->relation->relname)));
else ereport(WARNING,(errcode(ERRCODE_LOCK_NOT_AVAILABLE),errmsg("skipping analyze of \"%s\" --- lock not available",vrel->relation->relname)));
return vacrels;
}
/* To check whether the relation is a partitioned table and its ownership, fetch its syscache entry. */ // 要检查关系是否是分区表及其所有权,请获取其syscache条目
tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); // 通过需要分析的表oid从syscahce查找对应的pg_class条目
if (!HeapTupleIsValid(tuple)) elog(ERROR, "cache lookup failed for relation %u", relid);
classForm = (Form_pg_class) GETSTRUCT(tuple);
if (vacuum_is_relation_owner(relid, classForm, options)){ /* Make a returnable VacuumRelation for this rel if user is a proper owner. */
oldcontext = MemoryContextSwitchTo(vac_context);
vacrels = lappend(vacrels, makeVacuumRelation(vrel->relation,relid,vrel->va_cols)); // 将VacuumRelation结构体的信息、需要分析的表oid和需要分析的列制作成VacuumRelation结构体
MemoryContextSwitchTo(oldcontext);
}
include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE); // 除了分区叶子子表,其他分区表都属于该类
ReleaseSysCache(tuple);
/* If it is, make relation list entries for its partitions. Note that the list returned by find_all_inheritors() includes the passed-in OID, so we have to skip that. There's no point in taking locks on the individual partitions yet, and doing so would just add unnecessary deadlock risk. For this last reason we do not check yet the ownership of the partitions, which get added to the list to process. Ownership will be checked later on anyway. */
if (include_parts){
List *part_oids = find_all_inheritors(relid, NoLock, NULL); ListCell *part_lc;
foreach(part_lc, part_oids){
Oid part_oid = lfirst_oid(part_lc);
if (part_oid == relid) continue; /* ignore original table */ // 将指定分析的表剔除,因为find_all_inheritors返回的列表第一个元素就是分析的表剔除
oldcontext = MemoryContextSwitchTo(vac_context);
vacrels = lappend(vacrels, makeVacuumRelation(NULL, part_oid, vrel->va_cols)); /* We omit a RangeVar since it wouldn't be appropriate to complain about failure to open one of these relations later. */
MemoryContextSwitchTo(oldcontext);
}
}
UnlockRelationOid(relid, AccessShareLock);
}
return vacrels;
}
analyze流程对不同表的处理
void analyze_rel(Oid relid, RangeVar *relation, VacuumParams *params, List *va_cols, bool in_outer_xact, BufferAccessStrategy bstrategy)函数分析单个表,形参relid标识要分析的relation oid。如果提供了形参relation,则使用其中的名称报告打开/锁定表的任何失败;一旦我们成功打开表,就不要使用它,因为它可能已过时。我们知道analyze命令可以分析整个数据库、指定的某几个表、指定表的某几个列,所以这里我们可以传入va_cols代表表的某几个列。
void analyze_rel(Oid relid, RangeVar *relation, VacuumParams *params, List *va_cols, bool in_outer_xact, BufferAccessStrategy bstrategy) {
Relation onerel; int elevel;
AcquireSampleRowsFunc acquirefunc = NULL; BlockNumber relpages = 0;
...
onerel = vacuum_open_relation(relid, relation, params->options & ~(VACOPT_VACUUM), params->log_min_duration >= 0, ShareUpdateExclusiveLock);
if (!onerel)return; /* leave if relation could not be opened or locked */
if (!vacuum_is_relation_owner(RelationGetRelid(onerel),onerel->rd_rel, params->options & VACOPT_ANALYZE)){ relation_close(onerel, ShareUpdateExclusiveLock);return; }
if (RELATION_IS_OTHER_TEMP(onerel)){ relation_close(onerel, ShareUpdateExclusiveLock);return; }
/* We can ANALYZE any table except pg_statistic. See update_attstats */
if (RelationGetRelid(onerel) == StatisticRelationId){ relation_close(onerel, ShareUpdateExclusiveLock);return; }
if (onerel->rd_rel->relkind == RELKIND_RELATION || onerel->rd_rel->relkind == RELKIND_MATVIEW) { /* Check that it's of an analyzable relkind, and set up appropriately. */
acquirefunc = acquire_sample_rows; /* Regular table, so we'll use the regular row acquisition function */
relpages = RelationGetNumberOfBlocks(onerel); /* Also get regular table's size */
}else if (onerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE){ /* For a foreign table, call the FDW's hook function to see whether it supports analysis. */
FdwRoutine *fdwroutine; bool ok = false;
fdwroutine = GetFdwRoutineForRelation(onerel, false);
if (fdwroutine->AnalyzeForeignTable != NULL)
ok = fdwroutine->AnalyzeForeignTable(onerel, &acquirefunc,&relpages);
if (!ok){ ereport(WARNING,(errmsg("skipping \"%s\" --- cannot analyze this foreign table",RelationGetRelationName(onerel)))); relation_close(onerel, ShareUpdateExclusiveLock); return; }
} else if (onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) {
/* For partitioned tables, we want to do the recursive ANALYZE below. */ }
else { /* No need for a WARNING if we already complained during VACUUM */
if (!(params->options & VACOPT_VACUUM)) ereport(WARNING,(errmsg("skipping \"%s\" --- cannot analyze non-tables or special system tables",RelationGetRelationName(onerel))));
relation_close(onerel, ShareUpdateExclusiveLock); return;
}
...
if (onerel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE) /* Do the normal non-recursive ANALYZE. We can skip this for partitioned tables, which don't contain any rows. */
do_analyze_rel(onerel, params, va_cols, acquirefunc, relpages, false, in_outer_xact, elevel);
if (onerel->rd_rel->relhassubclass) /* If there are child tables, do recursive ANALYZE. */
do_analyze_rel(onerel, params, va_cols, acquirefunc, relpages, true, in_outer_xact, elevel);
...
}
如果分析的表是measurement_y2006m02子分区(普通表),那么只要执行analyze_rel(measurement_y2006m02_oid, …),采样函数使用acquire_sample_rows,表页数使用RelationGetNumberOfBlocks(onerel)获取;另外,如果分析的表为FDW(FDW表直接定义使用或者作为分区表的叶子分区使用),调用fdwroutine->AnalyzeForeignTable(onerel, &acquirefunc,&relpages)获取采样函数和表页数;然后直接进入第一处的do_analyze_rel(onerel, params, va_cols, acquirefunc, relpages, false, in_outer_xact, elevel)。如果分析的表是measurement表,则需要顺序执行analyze_rel对measurement继承树进行广度遍历的的所有子分区的表进行分析;首先会对measurement表进行分析,该表是分区表且有下级子表,走第二个分支do_analyze_rel(onerel, params, va_cols, acquirefunc, relpages, true, in_outer_xact, elevel);当执行到measurement_y2006m01分区表时,由于其是分区表,不会走第一个do_analyze_rel,又因为其无下级子表,也不会走第二个do_analyze_rel,所以该表不会进行分析;当执行到measurement_y2006m02,由于是普通表且无下级子表,采样函数使用acquire_sample_rows,表页数使用RelationGetNumberOfBlocks(onerel)获取,执行第一个do_analyze_rel。如果measurement_y2006m01表下有子分区的话,其relhassubclass为true,也就是有下级子表,其会走第二个do_analyze_rel,因此第二个分支是用于处理有下级子表的分区表的流程。
表名 relkind relispartition relhassubclass
measurement 分区表 父表(非分区) 有下级子表
| - measurement_y2006m01 分区表 子分区 无下级子表
| - measurement_y2006m02 普通表 子分区 无下级子表
| - measurement_y2006m03 普通表 子分区 无下级子表
| - measurement_y2006m04 普通表 子分区 无下级子表
如果分析的表是measurement_y2006m02,和上述流程相似,采样函数使用acquire_sample_rows,表页数使用RelationGetNumberOfBlocks(onerel)获取,走第一个do_analyze_rel分支。如果分析的表是measurement,则需要顺序执行analyze_rel对measurement继承树进行广度遍历的的所有子分区的表进行分析;对于measurement_y2006m01表由于是普通表,所以会进入第一个do_analyze_rel分支,由于又有下级子表,又会进入第二个do_analyze_rel分支;对于measurement_y2006m02、measurement_y2006m03、measurement_y2006m04这些普通表,则直接进入第一个分支。
表名 relkind relispartition relhassubclass
measurement 普通表 非分区 有下级子表
| - measurement_y2006m01 普通表 非分区 有下级子表
| - measurement_y2006m01d01 普通表 非分区 无下级子表
| - measurement_y2006m02 普通表 非分区 无下级子表
| - measurement_y2006m03 普通表 非分区 无下级子表
| - measurement_y2006m04 普通表 非分区 无下级子表
免责声明:
1、本站资源由自动抓取工具收集整理于网络。
2、本站不承担由于内容的合法性及真实性所引起的一切争议和法律责任。
3、电子书、小说等仅供网友预览使用,书籍版权归作者或出版社所有。
4、如作者、出版社认为资源涉及侵权,请联系本站,本站将在收到通知书后尽快删除您认为侵权的作品。
5、如果您喜欢本资源,请您支持作者,购买正版内容。
6、资源失效,请下方留言,欢迎分享资源链接
文章评论