Oracle Ask Tom分区学习系列: 面向开发者的分区(Partitioning)教程
Posted dingdingfish
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Oracle Ask Tom分区学习系列: 面向开发者的分区(Partitioning)教程相关的知识,希望对你有一定的参考价值。
Oracle Partitioning: A Step-by-Step Introduction for Developers是Oracle数据库开发者课程之一。
Development with Oracle Partitioning/使用 Oracle 分区进行开发
Partitioning in the database reflects the same way we handle large tasks in the real world. When a task gets too large to tackle in one hit, whether it be trimming a tree, taking a long drive, or washing the dishes, we can split the task up into smaller pieces to make them more manageable. It can be as simple as trying to relocate one’s catalog of Oracle technical books!
数据库中的分区反映了我们在现实世界中处理大型任务的方式。 当一项任务变得太大而无法一次解决时,无论是修剪树木、长途驾驶还是洗碗,我们都可以将任务拆分成更小的部分,以使其更易于管理。 它可以像尝试重新定位一个人的 Oracle 技术书籍目录一样简单!
在这个视频里,作者Connor Mcdonald可以将书按出版社分,按年代分,按Oracle数据库版本分…
Partitioning is that same thought applied to data stored in the database. As the demands to store more and more data in the database increase, the performance of operations against those large tables can suffer. And in applying the Pareto principle to the storage of data, typically it is only a subset of the entire dataset that is actively worked upon to satisfy the day to day needs of our businses users. Using the Partitioning option in the Oracle Database, data can be segmented into smaller, more manageable chunks to make maintenance tasks easier for Database Administrators, but also give scope for better application performance via more efficient execution of the queries being issued by Application Developers.
分区是应用于存储在数据库中的数据的相同思想。 随着在数据库中存储越来越多数据的需求增加,对这些大表的操作性能可能会受到影响。 在将 Pareto 原则应用于数据存储时,通常只有整个数据集的一个子集被积极处理以满足我们业务用户的日常需求。 使用 Oracle 数据库中的分区选项,可以将数据分割成更小、更易于管理的块,使数据库管理员的维护任务更容易,而且还可以通过更高效地执行应用程序开发人员发出的查询来提供更好的应用程序性能。
The Pareto principle states that for many outcomes, roughly 80% of consequences come from 20% of causes (the “vital few”).[1] Other names for this principle are the 80/20 rule, the law of the vital few, or the principle of factor sparsity.[2][3]
Partitioning Options/分区选项
There are different types of partitioning options available to cater for specific business requirements. There might be a requirement to break up SALES data into each calendar year. Or there might be information being gathered on popular SPORTS that will be separated because minimal cross-sport queries will ever be run. Or a table of cell phone CALLS might just be so large that it needs to be evenly scattered across smaller segments to keep them at a manageable size. All such options are possible with the Oracle Partitioning option.
有不同类型的分区选项可用于满足特定的业务需求。 可能需要将 SALES 数据分解为每个日历年。 或者可能会收集有关流行运动(如NBA, NFL, MLB, NHL, 即篮球,橄榄球,棒球和曲棍球)的信息,这些信息将被分开,因为跨运动查询比较少见。 或者,手机呼叫表可能太大,需要将其均匀分散在更小的段中,以将它们保持在可管理的大小。 所有这些选项都可以通过 Oracle Partitioning 选项实现。
There are other partitioning strategies as well for more esoteric requirements, including partitioning strategies between tables linked by referential integrity, and multi-dimensional forms of partitioning (partitions of partitions).
对于更深奥的需求,还有其他分区策略,包括通过参照完整性链接的表之间的分区策略,以及多维形式的分区(分区的分区)。
Getting started with Partitioning/分区入门
Get an Environment/获取环境
您只需转到Oracle 提供的名为livesql.oracle.com的免费服务即可开始使用。此服务允许您运行 SQL 并创建数据库对象,除了您的浏览器之外不需要任何软件。还有数百个关于众多主题的示例脚本和教程,可帮助您了解 Oracle 数据库
Oracle 分区也作为所有 Oracle 数据库云服务和内部部署 Oracle 数据库企业版的完全支持的特性提供。以下是 Oracle 副总裁 Mike Hichwa 对 LiveSQL 的快速入门:
Oracle LiveSQL 特性:
- 免费:LiveSQL 对任何用途都是完全免费的。注册免费、快速且简单。
- 脚本:您在 LiveSQL 中编写的 SQL 可以进行元数据标记、保存、与他人共享或与整个 Oracle 社区共享。
- 教程:LiveSQL 包含数百个由 Oracle Corporation 内部和外部专家编写的教程,可帮助您快速提高工作效率。
- 最新版本:LiveSQL 在最新版本的 Oracle 数据库上运行,因此您可以在升级自己的系统之前安全地测试新功能。
以下命令获取数据库当前版本:
select * from v$version;
A First Look at Partitioning Syntax/分区语法初探
Perhaps the most common example of partitioning in Oracle databases is to divide up a large data into partitions based on a time attribute. The largest tables in your applications are often a time-based record of critical business activities. It could be sales transactions for a retail business, mobile phone calls for a telecommunications business, or deposits and withdrawals for a banking institution. In all of these cases, there are a couple of common elements, namely each transaction (row) in the table has a time stamp of when the transaction occurred, and typically the volume of such transactions is high, making the table large in size in a short period of time. Partitioning is a natural fit for such tables, because queries often want to only peruse(随便翻阅,浏览) time-based subsets of the data, for example, transactions for the current month, or current week. Also, breaking the large table into smaller more manageable sized pieces is useful for adminstrators from the perspective of maintenance. Time is a continuous (analog) measurement, and thus, a large table would be segmented into time-based ranges, hence the term used for this opertaion is range based partitioning. This video walks you through the creation a simple range partitioned table.
关键点:
- 按范围分区:定义表已分区的关键字是 PARTITION BY,它遵循普通表列定义。BY RANGE 子句指定表将使用的分区方案的类型。
- 不包含上边界:范围分区没有给出下限和上限,只有上限。下限隐式定义为前一个分区的上限。一个分区可以包含不超过但不包括在 VALUES LESS THAN 子句中指定的值的值。(有点绕,其实就是大于等于下边界,并且小于上边界)
- 至少 1 个分区:在 PARTITION BY 子句之后,必须始终至少定义一个分区。(对于范围分区是这样的,而其它类型的分区不一定适用,如自动分区,但也是少数情况)
- USER_TAB_PARTITIONS:数据字典跟踪表的所有已定义分区。USER_TAB_PARTITIONS 为架构中的每个分区表的每个定义分区显示一行。
Performance Benefits/性能优势
Even with just a simple range partitioning example, we have enough tools at our disposal(由我们支配,任我们处置) to examine the potential performance benefits possible by partitioning a table. When SQL queries consume too much I/O, often the only resolution considered is to create indexes on the table. The premise(前提) of indexing is simple: locate data more quickly and avoid scanning data unnecessarily. Partitioning a table takes the same approach via a concept known as “pruning” or “elimination”. If a query on a partitioned table contains appropriately phrased predicates that include the partitioning column(s), the optimizer can generate an execution plan that will bypass those partitions that by definition, could not contain data relevant to the query. The following video shows a demonstration of this, including a comparison of the cost of partition pruning versus a conventional indexing strategy(索引策略指的是分区类型,例如范围分区,列表分区,哈希分区等).
关键点:
- 分区修剪:如果优化器可以消除分区的考虑,查询性能可以得到显着提高。在后面的视频中,您将看到如何解释优化器执行计划输出以确定是否对给定的 SQL 查询进行分区消除。
- 生成测试数据:您可以使用视频中的 DUAL 技术为任何表、分区表或其他表生成任意测试数据。根据视频,将单个 DUAL CONNECT BY 查询生成的行数保持在数万以内,如果需要扩展,请使用笛卡尔连接。请参阅Tanel Poder 的博客文章,了解这些查询如何影响 PGA 内存,了解您不应走极端的原因。
- 减少索引:在某些情况下,对表进行分区允许合并或删除现有索引,这可以减少整体数据库大小,并提高插入、更新和删除性能。
create table SALES
(
tstamp timestamp(6) not null,
sales_id number(10) not null,
amount number(12, 2) not null
);
-- timestamp之后的6表示秒的小数点的位数,6是默认值。
insert into sales
select
timestamp '2010-01-01 00:00:00' +
numtodsinterval(rownum*5, 'SECOND'),
rownum,
dbms_random.value(1,20)
from
(select 1 from dual connect by level <= 10000),
(select 1 from dual connect by level <= 10000)
where rownum <= 6000000;
commit;
-- 6000000 * 5 可换算为347.22天,所以时间戳都在2010年内,最大时间戳在12月中旬
set autotrace on
select max(amount)
from sales
where tstamp >= timestamp '2010-06-01 00:00:00'
and tstamp <= timestamp '2010-08-01 00:00:00';
输出如下,注意此时的consistent gets为19326:
MAX(AMOUNT)
-----------
20
Execution Plan
----------------------------------------------------------
Plan hash value: 1047182207
----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 26 | 5265 (1)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 26 | | |
|* 2 | TABLE ACCESS FULL| SALES | 1062K| 26M| 5265 (1)| 00:00:01 |
----------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("TSTAMP">=TIMESTAMP' 2010-06-01 00:00:00.000000000' AND
"TSTAMP"<=TIMESTAMP' 2010-08-01 00:00:00.000000000')
Note
-----
- dynamic statistics used: dynamic sampling (level=2)
Statistics
----------------------------------------------------------
41 recursive calls
13 db block gets
19326 consistent gets
2 physical reads
2576 redo size
553 bytes sent via SQL*Net to client
485 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
2 sorts (memory)
0 sorts (disk)
1 rows processed
创建索引:
create index sales_ix on sales(tstamp);
再次查询,其执行计划和统计信息如下。发现索引并没有帮助,仍然使用全表扫描:
MAX(AMOUNT)
-----------
20
Execution Plan
----------------------------------------------------------
Plan hash value: 1047182207
----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 26 | 5265 (1)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 26 | | |
|* 2 | TABLE ACCESS FULL| SALES | 1062K| 26M| 5265 (1)| 00:00:01 |
----------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("TSTAMP">=TIMESTAMP' 2010-06-01 00:00:00.000000000' AND
"TSTAMP"<=TIMESTAMP' 2010-08-01 00:00:00.000000000')
Note
-----
- dynamic statistics used: dynamic sampling (level=2)
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
19038 consistent gets
0 physical reads
0 redo size
553 bytes sent via SQL*Net to client
485 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
修改为分区表:
alter table sales
modify partition by range (tstamp)
(
partition p00 values less than (timestamp '2010-01-01 00:00:00'),
partition p01 values less than (timestamp '2010-02-01 00:00:00'),
partition p02 values less than (timestamp '2010-03-01 00:00:00'),
partition p03 values less than (timestamp '2010-04-01 00:00:00'),
partition p04 values less than (timestamp '2010-05-01 00:00:00'),
partition p05 values less than (timestamp '2010-06-01 00:00:00'),
partition p06 values less than (timestamp '2010-07-01 00:00:00'),
partition p07 values less than (timestamp '2010-08-01 00:00:00'),
partition p08 values less than (timestamp '2010-09-01 00:00:00'),
partition p09 values less than (timestamp '2010-10-01 00:00:00'),
partition p10 values less than (timestamp '2010-11-01 00:00:00'),
partition p11 values less than (timestamp '2010-12-01 00:00:00'),
partition p12 values less than (timestamp '2011-01-01 00:00:00')
);
再次执行查询,分区裁剪生效了。consistent gets降为5075:
---------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
---------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 15 | 1415 (1)| 00:00:01 | | |
| 1 | SORT AGGREGATE | | 1 | 15 | | | | |
| 2 | PARTITION RANGE ITERATOR| | 1054K| 15M| 1415 (1)| 00:00:01 | 7 | 9 |
|* 3 | TABLE ACCESS FULL | SALES | 1054K| 15M| 1415 (1)| 00:00:01 | 7 | 9 |
---------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - filter("TSTAMP"<=TIMESTAMP' 2010-08-01 00:00:00.000000000')
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
5075 consistent gets
0 physical reads
0 redo size
553 bytes sent via SQL*Net to client
485 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
最后,清理表:
drop table sales purge;
如果嫌之前的范围分区语法复杂,也可以用间隔分区。间隔分区是范围分区的扩展:
alter table sales
modify partition by range (tstamp) interval (numtoyminterval(1, 'MONTH'))
(partition p00 values less than (timestamp '2010-01-01 00:00:00'));
查看分区信息:
-- 对于间隔分区,PARTITION_COUNT总是1048575
col TABLE_NAME for a20
col name for a20
col column_name for a20
set lines 140
col PARTITION_NAME for a20
col HIGH_VALUE for a40
set pages 9999
select TABLE_NAME, PARTITIONING_TYPE, PARTITION_COUNT, STATUS from USER_PART_TABLES;
TABLE_NAME PARTITION PARTITION_COUNT STATUS
-------------------- --------- --------------- --------
SALES RANGE 1048575 VALID
exec dbms_stats.gather_table_stats(null, 'SALES');
-- 必须搜集统计信息,NUM_ROWS才会有显示
select PARTITION_NAME, HIGH_VALUE, NUM_ROWS from USER_TAB_PARTITIONS where TABLE_NAME='SALES';
PARTITION_NAME HIGH_VALUE NUM_ROWS
-------------------- ---------------------------------------- ----------
P00 TIMESTAMP' 2010-01-01 00:00:00' 0
SYS_P27320 TIMESTAMP' 2010-02-01 00:00:00' 535679
SYS_P27321 TIMESTAMP' 2010-03-01 00:00:00' 483840
SYS_P27322 TIMESTAMP' 2010-04-01 00:00:00' 535680
SYS_P27323 TIMESTAMP' 2010-05-01 00:00:00' 518400
SYS_P27324 TIMESTAMP' 2010-06-01 00:00:00' 535680
SYS_P27325 TIMESTAMP' 2010-07-01 00:00:00' 518400
SYS_P27326 TIMESTAMP' 2010-08-01 00:00:00' 535680
SYS_P27327 TIMESTAMP' 2010-09-01 00:00:00' 535680
SYS_P27328 TIMESTAMP' 2010-10-01 00:00:00' 518400
SYS_P27329 TIMESTAMP' 2010-11-01 00:00:00' 535680
SYS_P27330 TIMESTAMP' 2010-12-01 00:00:00' 518400
SYS_P27331 TIMESTAMP' 2011-01-01 00:00:00' 228481
13 rows selected.
select * from USER_PART_KEY_COLUMNS;
NAME OBJEC COLUMN_NAME COLUMN_POSITION COLLATED_COLUMN_ID
-------------------- ----- -------------------- --------------- ------------------
SALES TABLE TSTAMP 1
Multi-column Range Partitioning/多列范围分区
可以指定多个列作为分区键,列的顺序很重要。
create table SALES_DATA
(
yyyy number(4) not null,
mm number(2) not null,
sales_id varchar2(10) not null,
amount number(10, 2)
)
partition by range (yyyy, mm)
(
partition p2010_q1 values less than (2010, 04),
partition p2010_q2 values less than (2010, 07),
partition p2010_q3 values less than (2010, 10),
partition p2010_q4 values less than (2011, 01),
partition p2011_q1 values less than (2011, 04),
partition p2011_q2 values less than (2011, 07),
partition p2011_q3 values less than (2011, 10),
partition p2011_q4 values less than (2012, 01)
);
insert into sales_data values(2010, 03, 'Shoes', 27.10);
insert into sales_data values(2010, 02, 'Belt', 17.99);
insert into sales_data values(2010, 04, 'Hat', 42.40);
insert into sales_data values(2010, 09, 'Coffee', 3.50);
insert into sales_data values(2010, 10, 'Biscuits', 2.60);
exec dbms_stats.gather_table_stats('', 'SALES_DATA');
select partition_name, num_rows
from user_tab_partitions
where table_name = 'SALES_DATA';
输出为:
PARTITION_NAME NUM_ROWS
P2010_Q1 2
P2010_Q2 1
P2010_Q3 1
P2010_Q4 1
P2011_Q1 0
P2011_Q2 0
P2011_Q3 0
P2011_Q4 0
8 rows selected.
再看另一个例子:
create table mobile_phone
(
start_day date not null,
end_day date not null,
account_id varchar2(10) not null,
calls number(10)
)
partition by range(start_day, end_day)
(
partition p01 values less than (date '2010-02-01', date '2010-03-01'),
partition p02 values less than (date '2010-03-01', date '2010-04-01'),
partition p03 values less than (date '2010-04-01', date '2010-05-01')
);
insert into mobile_phone values('07-FEB-2010', '12-FEB-2010', 'Acct#1', 100);
insert into mobile_phone values('12-FEB-2010', '13-APR-2010', 'Acct#1', 175);
exec dbms_stats.gather_table_stats('', 'MOBILE_PHONE');
select partition_name, num_rows
from user_tab_partitions
where table_name = 'MOBILE_PHONE';
输出为:
PARTITION_NAME NUM_ROWS
P01 0
P02 2
P03 0
3 rows selected.
所有数据都进入第2个分区,这显然不是我们想要的。
关键点:
- 决胜局(tie-breaker)不是多维度的
分区定义中的第二和第三列仅用作“决胜局”值。将行插入多列范围分区表时,分区键中的第一列用于确定将行存储到的分区。如果多个分区的第一列具有相同的值,则使用第二个分区列(也就是加时赛或决胜局,以进一步确定存储的分区),依此类推。分区键中的多列不是“矩阵”或“n 维”结构的分区。 - 将日期存储为数字
视频中的示例之一使用 NUMBER 数据类型来存储基于日期的信息。正如视频所述,这通常是一个糟糕的设计理念。请参阅Richard Foote 的Storing Dates以获取更多示例,了解为什么您可能想要在数据库中重新考虑这种方法。 - 词典视图
与 USER_TABLES 包含各种列以反映当前优化器统计信息的方式相同,USER_TAB_PARTITIONS 也包含分区级别的此信息。因此,您可以使用 NUM_ROWS、BLOCKS 等来获取有关每个分区的卷的信息,准确性仅限于使用 DBMS_STATS 收集统计信息的时间/粒度。
以上两个例子的区别在于,例一的分区键是时间点,例二则是时间段,并且时间段有重叠,所以出现了问题。
Hash Partitioning/哈希分区
有时分区不是像范围分区那样基于表的属性对表进行逻辑分段。 当任何数据库表变得庞大时,管理员就更难管理,因为维护此类表通常会导致业务应用程序的停机时间更长。 哈希分区允许您根据应用于表的一个或多个列的哈希函数将表分割成大小相等的块。
创建哈希分区表很容易,但指定的哈希分区数很关键。
哈希分区对于DBA很重要;而哈希索引分区则对开发者很重要。
关键点:
- 2的幂
为使分区大小接近,分区数应为 2 的幂。 - ORA_HASH
哈希算法未记录在案,但观察到 ORA_HASH 函数返回的结果与哈希分区发生的数据分段一致。 - 分裂
可以使用 ALTER TABLE SPLIT PARTITION 命令拆分分区,该任务通常由数据库管理员完成。拆分分区是一项资源密集型活动,因为可能会移动整个分区的数据。
create table T
(
x number(10)
) partition by hash(x)
partitions 8;
select partition_name
from user_tab_partitions
where table_name = 'T';
输出为:
PARTITION_NAME
SYS_P492050
SYS_P492051
SYS_P492052
SYS_P492053
SYS_P492054
SYS_P492055
SYS_P492056
SYS_P492057
8 rows selected.
插入10万条数据:
insert into T
select level from dual
connect by level <= 100000;
查看rowid:
select rowid from T where rownum = 1;
输出如下,rowid中实际包含了对象ID:
ROWID
AJUIk+ADDAAABSTAAA
因此通过以下SQL可以得到每个分区的实际行数:
select dbms_rowid.rowid_object(rowid) ptn_obj, count(*)
from T
group by dbms_rowid.rowid_object(rowid)
order by 2;
输出如下,可以看出数据接近平均分布:
PTN_OBJ COUNT(*)
156272962 12342
156272963 12381
156272965 12382
156272961 12508
156272959 12575
156272960 12581
156272958 12603
156272964 12628
8 rows selected.
可以通过ORA_HASH函数验证,其中7表示从0到8:
select ora_hash(x, 7), count(*)
from t
group by ora_hash(x, 7)
order by 2;
输出如下:
ORA_HASH(X,7) COUNT(*)
4 12342
5 12381
7 12382
3 12508
1 12575
2 12581
0 12603
6 12628
8 rows selected.
哈希分区数应为2的幂,例如2,4,8,16…,否则分布可能会不均匀。
drop table t purge;
create table T
(
x number(10)
) partition by hash(x)
partitions 5;
insert into T
select level from dual
connect by level <= 100000;
select dbms_rowid.rowid_object(rowid) ptn_obj, count(*)
from T
group by dbms_rowid.rowid_object(rowid)
order by 2;
可以看到分布并不均匀:
PTN_OBJ COUNT(*)
---------- ----------
107682 12342
107678 12603
107681 24890
107679 24956
107680 25209
5 rows selected.
如何将5个分区调整为8个分区?先来看传统的做法,大多数行(本例接近88%)都需移动:
select count(*) from T
where ora_hash(x, 4) != ora_hash(x, 7);
COUNT(*)
87564
select count(*) from T
where ora_hash(x, 4) != ora_hash(x, 5);
COUNT(*)
83224
Oracle采用了更智能的算法,先看从5个分区变为6个分区:
alter table T add partition;
select dbms_rowid.rowid_object(rowid) ptn_obj, count(*)
from T
group by dbms_rowid.rowid_object(rowid)
order by 2;
PTN_OBJ COUNT(*)
156275768 12342
156276447 12381
156276446 12575
156275764 12603
156275767 24890
156275766 25209
6 rows selected.
可以看到添加哈希分区的效果,相对于拆分某一哈希分区。本例中,即将24956拆分为12381和12575。
重复以上过程,可以看到数据分布趋于平衡:
alter table T add partition;
select dbms_rowid.rowid_object(rowid) ptn_obj, count(*)
from T
group by dbms_rowid.rowid_object(rowid)
order by 2;
PTN_OBJ COUNT(*)
156275768 12342
156276447 12381
156276446 12575
156276791 12581
156275764 12603
156276792 12628
156275767 24890
7 rows selected.
alter table T add partition;
select dbms_rowid.rowid_object(rowid) ptn_obj, count(*)
from T
group by dbms_rowid.rowid_object(rowid)
order by 2;
PTN_OBJ COUNT(*)
156275768 12342
156276447 12381
156276843 12382
156276842 12508
156276446 12575
156276791 12581
156275764 12603
156276792 12628
8 rows selected.
哈希分区只能一个一个添加。添加分区的过程相当于split,也是数据重新哈希的过程。
减少哈希分区是通过coalesce操作实现的。
select dbms_rowid.rowid_object(rowid) ptn_obj, count(*)
from T
group by dbms_rowid.rowid_object(rowid)
order by 2;
PTN_OBJ COUNT(*)
---------- ----------
107682 12342
107684 12381
107688 12382
107687 12508
107683 12575
107685 12581
107678 12603
107686 12628
8 rows selectedOracle Ask Tom分区学习系列: 面向开发者的分区(Partitioning)教程
ORACLE分区表梳理系列- 分区表概述分类使用方法及注意事项