横表与竖表性能浅析

概念介绍

横表概念

 

横表就是普通的建表方式,每一个字段代表一个KPI指标。举个列子,一个学生的成绩表:学号、数学成绩、语文成绩、英语成绩、物理成绩、化学成绩......如下所示:

复制代码
SQL> DESC STUDENT_SCORE
Name             Type       Nullable Default Comments 
---------------- ---------- -------- ------- -------- 
STUDENT_NO       NUMBER(10)                  学号     
CHINESE_SCORE    NUMBER     Y                语文成绩 
ENGLISH_SCORE    NUMBER     Y                英语成绩 
MATH_SOCRE       NUMBER     Y                数学成绩 
PHYSICAL_SCORE   NUMBER     Y                物理成绩 
SPORTS_SCORE     NUMBER     Y                体育成绩 
CHEMICAL_SCORE   NUMBER     Y                化学成绩 
BIOLOGICAL_SCORE NUMBER     Y                生物成绩
复制代码

竖表概念

 

复制代码
SQL> DESC STUDENT_SOCRES;
Name         Type         Nullable Default Comments 
------------ ------------ -------- ------- -------- 
STUDENT_NO   NUMBER(10)   Y                学号     
SUBJECT_CODE VARCHAR2(12) Y                科目编码 
SUBJECT_NAME VARCHAR2(12) Y                科目名称 
SCORES       NUMBER       Y                成绩
复制代码

优劣比较

指标项目

横表

竖表

可扩展性

性能方面

优于竖表

比横表差

业务描述

代码复杂

代码简单

代码复杂,大部分时候需要进行转换

 

横表优点

1:业务描述:横表的好处是清晰可见,一目了然,数据描叙很清晰。每个字段就是一个KPI指标。

2:性能方面:横表从数据库映射到内存的速度比竖表要快很多。

3:代码复杂:横表不需要做行列转换,代码比较简单

横表缺点:

1:可扩展性:如果需求变更,例如需要增加一个指标,那么就必须修改表结构或重建表。对于需求不明确或变更频繁的情况,横表需要大的改动,涉及改动的脚本也较多。

竖表优点:

1:可扩展性:对于竖表来说,不必修改表结构,只需增加一条记录就可搞定。对于需求不明确或变更频繁的情况,竖表基本不用改动,涉及改动的脚本也较少。

竖表缺点:

1:业务描述:竖表的数据描叙很不清晰,举例说明:学生成绩表的竖表形式,成绩这个字段,即可是数学成绩、也可是语文成绩,不像横表形式数学成绩、语文成绩各成一个字段描述KPI指标来得清晰明了。

2:性能方面:系统展现的报表大部分是横表,这意味着竖表要进行转列。这样需要额外的性能开销。尤其是当报表进行聚合计算时,性能更糟糕。这是因为竖表从数据库映射到内存比横表要慢。

3:代码复杂:需要做行列转换,代码量、复杂性都会增加很多。

实验对比

 

横表STUDENT_SCORE有语文成绩、英语成绩等7个KPI指标,三个学生的三条记录。

SQL> SELECT * FROM STUDENT_SCORE;
 
 STUDENT_NO CHINESE_SCORE ENGLISH_SCORE MATH_SOCRE PHYSICAL_SCORE SPORTS_SCORE CHEMICAL_SCORE BIOLOGICAL_SCORE
----------- ------------- ------------- ---------- -------------- ------------ -------------- ----------------
      10001          87.4            63         92             86           75             85               89
      10002            91            89         98             62           76             82               73
      10006            74            63         57             42           76             59               67

对应于竖表,这三个学生的7个KPI指标需要21条记录才能描述清楚。

复制代码
SQL> SELECT * FROM STUDENT_SOCRES;
 
 STUDENT_NO SUBJECT_CODE SUBJECT_NAME     SCORES
----------- ------------ ------------ ----------
      10001 CH           语文成绩           87.4
      10001 EG           英语成绩             63
      10001 MT           数学成绩             92
      10001 PH           物理成绩             86
      10001 SP           体育成绩             75
      10001 CE           化学成绩             85
      10001 BI           生物成绩             89
      10002 CH           语文成绩             91
      10002 EG           英语成绩             89
      10002 MT           数学成绩             98
      10002 PH           物理成绩             62
      10002 SP           体育成绩             76
      10002 CE           化学成绩             82
      10002 BI           生物成绩             73
      10006 CH           语文成绩             74
      10006 EG           英语成绩             63
      10006 MT           数学成绩             57
      10006 PH           物理成绩             42
      10006 SP           体育成绩             76
      10006 CE           化学成绩             59
      10006 BI           生物成绩             67
复制代码

所以我们从这个小实验中可以看到,横表转成竖表,对应的记录会翻倍增长,这对应于数据量大的表或宽表,都是一件不好的消息。很多时候,数据量上去了,性能问题就出来了。它们之间的记录关系如下所示:

 

竖表行数 = 横表行数 * KPI指标个数。

 

个数据量500万,KPI指标个数为10的横表,转成竖表后的记录数会飚增到5000万。

我们拿表ODS.TO_BUSS_WNMS_BSCPMHR来做实验,该表的表结构对应如下所示:

复制代码
SQL> DESC ODS.TO_BUSS_WNMS_BSCPMHR
Name                        Type       Nullable Default Comments                
--------------------------- ---------- -------- ------- ----------------------- 
COLLECT_DT                  NUMBER(8)                   采集日期                 
DATE_CD                     NUMBER(8)                   日期编码                 
HR_CD                       NUMBER(2)                   时段编码                 
CITY_ID                     NUMBER(10)                  地市标识                 
SYSTEM_ID                   NUMBER(10)                  网元编码                 
TBF_CLEAN_CNT               NUMBER     Y                TBF清空次数              
UPTBF_TRY_CNT               NUMBER     Y                上行TBF请求数            
UPTBF_SUCC_RAT              NUMBER     Y                上行TBF建立成功率        
DOWNTBF_SUCC_CNT            NUMBER     Y                下行TBF成功建立次数      
DOWNTBF_SUCC_RAT            NUMBER     Y                下行TBF建立成功率        
DOWNTBF_TRY_CNT             NUMBER     Y                下行TBF建立尝试次数      
UPTBF_SUCC_CNT              NUMBER     Y                上行TBF成功建立次数      
GPRSDOWNTBF_ABNM_CNT        NUMBER     Y                GPRS下行TBF异常中断次数  
GPRSDOWNTBF_SUCC_CNT        NUMBER     Y                GPRS下行TBF建立成功次数  
GPRSDOWNTBF_DROP_RAT        NUMBER     Y                GPRS下行TBF掉线率        
DROP_CALL_TCH               NUMBER     Y                TCH掉话总次数            
TCH_CALL_SEIZ               NUMBER     Y                话音信道占用总次数(含切换) 
GSMTCH_DROP_RAT             NUMBER     Y                TCH掉话率(GSM)          
TDTCH_DROP_PCT              NUMBER     Y                TCH话务掉话比            
TCH_ERL                     NUMBER     Y                TCH话务量               
TCH_CNT                     NUMBER     Y                TCH信道数               
TCH_GT_RAT                  NUMBER     Y                TCH接通率               
ATT_TCH_OVRFL               NUMBER     Y                话音信道溢出总次数(含切换) 
TCH_CALL_REQ                NUMBER     Y                话音信道试呼总次数(含切换) 
TCH_CONG_RAT                NUMBER     Y                TCH拥塞率               
DROP_CALL_SDCCH             NUMBER     Y                SDCCH掉话总次数          
GSMSDCCH_DROP_RAT           NUMBER     Y                SDCCH掉话率(GSM)        
SDCCH_ALLOT_SUCC_RAT        NUMBER     Y                SDCCH分配成功率          
SDCCH_GT_RAT                NUMBER     Y                SDCCH接通率             
ATT_SDCCH_OVRFL             NUMBER     Y                SDCCH溢出总次数          
SDCCH_CONG_RAT              NUMBER     Y                SDCCH拥塞率             
SDCCH_USE_CNT               NUMBER     Y                SDCCH占用次数            
SDCCH_TRY_CNT               NUMBER     Y                SDCCH试呼次数            
SDCCH_AV_HOLD_T             NUMBER     Y                SDCCH信道平均占用时长    
RLC_TRAFIC                  NUMBER     Y                RLC流量                 
EDGE_RLCTSTP_RAT_FZ         NUMBER     Y                EGPRS RLC层单时隙吞吐率-分子 
EDGE_RLCTSTP_RAT_FM         NUMBER     Y                EGPRS RLC层单时隙吞吐率-分母 
EGPRS_RLC_THRUPUT_RAT       NUMBER     Y                EGPRS RLC层单时隙吞吐率  
GPRS_RLCTSTP_RAT_FZ         NUMBER     Y                GPRS RLC层单时隙吞吐率-分子 
GPRS_RLCTSTP_RAT_FM         NUMBER     Y                GPRS RLC层单时隙吞吐率-分母 
GPRS_RLC_THRUPUT_RAT        NUMBER     Y                GPRS RLC层单时隙吞吐率   
EGPRS_RETRAN_RAT            NUMBER     Y                EGPRS重传率             
RLCDOWN_REPLYDATA_EGPRS_CNT NUMBER     Y                RLC层下行链路无线数据块重传数(EGPRS) 
EGPRS_RLC_CNT               NUMBER     Y                RLC层总块数(EGPRS)       
GPRS_RETRAN_RAT             NUMBER     Y                GPRS重传率              
RLCDOWN_REPLYDATA_GPRS_CNT  NUMBER     Y                RLC层下行链路无线数据块重传数(GPRS) 
GPRS_RLC_CNT                NUMBER     Y                RLC层总块数(GPRS)        
LOW_CODE_RAT                NUMBER     Y                低编码比例               
MID_CODE_RAT                NUMBER     Y                中编码比例               
USE_PDCH_AVG_CNT            NUMBER     Y                占用的PDCH的平均数       
PDCH_REUSE                  NUMBER     Y                PDCH复用度              
PDCH_ALLOT_SUCC_RAT         NUMBER     Y                PDCH信道分配成功率       
PDCH_CNT                    NUMBER     Y                PDCH信道数量             
GSL_MAX_CNT                 NUMBER     Y                PCU(GSL最大设备数)       
GSL_USERAT                  NUMBER     Y                GSL利用率               
PDCH_ALLOT_CNT              NUMBER     Y                PDCH信道分配次数         
PDCH_ALLOT_SUCC_CNT         NUMBER     Y                PDCH信道分配成功次数     
HO_REQ_CNT                  NUMBER     Y                切换请求总次数           
HO_SUCC_CNT                 NUMBER     Y                切换成功总次数           
AVAIL_TCH_NBR               NUMBER     Y                可配置信道数             
TCH_TRAFFIC_H               NUMBER     Y                半速率话务量             
CH_CNT_PDCH                 NUMBER     Y                总业务信道数             
TCH_SEIZE_NHO               NUMBER     Y                话音信道占用总次数(不含切换) 
AVG_DISTR_PDCH_CNT          NUMBER     Y                平均分配PDCH数           
UPIP_FLOW                   NUMBER     Y                上行IP层流量             
DOWNIP_FLOW                 NUMBER     Y                下行IP层流量
复制代码

建立这张横表对应的竖表TO_BUSS_WNMS_BSCPM_H_TEST

复制代码
CREATE TABLE TO_BUSS_WNMS_BSCPM_H_TEST
(
       COLLECT_DT     NUMBER(8)    ,
       DATE_CD        NUMBER(8)    ,
       HR_CD          NUMBER(2)    ,
       CITY_ID        NUMBER(10)  ,
       SYSTEM_ID      NUMBER(10)  ,
       KPI_CODE       VARCHAR2(32),
       KPI_NAM        VARCHAR2(32),
       KPI_VALUE      NUMBER      ,
       CONSTRAINT PK_TO_BUSS_WNMS_BSCPM_H_TEST PRIMARY KEY ("COLLECT_DT", "DATE_CD", "HR_CD", "CITY_ID", "SYSTEM_ID","KPI_CODE")
) PARTITION BY RANGE(COLLECT_DT) 
( 
  PARTITION "PART201111"  VALUES LESS THAN (20111199) TABLESPACE TBS_KFT_DATA, 
  PARTITION "PART201112"  VALUES LESS THAN (20111299) TABLESPACE TBS_KFT_DATA,
  PARTITION "PART201201"  VALUES LESS THAN (20120199) TABLESPACE TBS_KFT_DATA,
  PARTITION "PART201202"  VALUES LESS THAN (20120299) TABLESPACE TBS_KFT_DATA,
  PARTITION "PART201203"  VALUES LESS THAN (20120399) TABLESPACE TBS_KFT_DATA,
  PARTITION "PART201204"  VALUES LESS THAN (20120499) TABLESPACE TBS_KFT_DATA,
  PARTITION "PART201205"  VALUES LESS THAN (20120599) TABLESPACE TBS_KFT_DATA,
  PARTITION "PART201206"  VALUES LESS THAN (20120699) TABLESPACE TBS_KFT_DATA,
  PARTITION "PART201207"  VALUES LESS THAN (20120799) TABLESPACE TBS_KFT_DATA,
  PARTITION "PART201208"  VALUES LESS THAN (20120899) TABLESPACE TBS_KFT_DATA,
  PARTITION "PART201209"  VALUES LESS THAN (20120999) TABLESPACE TBS_KFT_DATA,
  PARTITION "PART201210"  VALUES LESS THAN (20121099) TABLESPACE TBS_KFT_DATA,
  PARTITION "PART201211"  VALUES LESS THAN (20121199) TABLESPACE TBS_KFT_DATA,
  PARTITION "PART201212"  VALUES LESS THAN (20121299) TABLESPACE TBS_KFT_DATA,
  PARTITION "PART201301"  VALUES LESS THAN (20130199) TABLESPACE TBS_KFT_DATA
)
复制代码

把ODS.TO_BUSS_WNMS_BSCPMHR的2012年12月1号以后的数据导入到TO_BUSS_WNMS_BSCPM_H_TEST,然后收集统计该表的相关信息。另外新建横表TO_BUSS_WNMS_BSCPMHR_S_TEST(表结构和ODS.TO_BUSS_WNMS_BSCPMHR一样),把2012年12月1号以后的数据导入到TO_BUSS_WNMS_BSCPMHR_S_TEST

SQL> exec dbms_stats.gather_table_stats(ownname=>'DWKONGLINGBO',tabname=>'TO_BUSS_WNMS_BSCPMHR_S_TEST',partname=>'PART201212',granularity=>'PARTITION',estimate_percent=> dbms_stats.auto_sample_size,force=>true,degree=>8);

PL/SQL procedure successfully completed

SQL> exec dbms_stats.gather_table_stats(ownname=>'DWKONGLINGBO',tabname=>'TO_BUSS_WNMS_BSCPMHR_S_TEST',partname=>'PART201301',granularity=>'PARTITION',estimate_percent=> dbms_stats.auto_sample_size,force=>true,degree=>8);

PL/SQL procedure successfully completed

SQL> exec dbms_stats.gather_table_stats(ownname=>'DWKONGLINGBO',tabname=>'TO_BUSS_WNMS_BSCPM_H_TEST',partname=>'PART201212',granularity=>'PARTITION',estimate_percent=> dbms_stats.auto_sample_size,force=>true,degree=>8);

PL/SQL procedure successfully completed

SQL> exec dbms_stats.gather_table_stats(ownname=>'DWKONGLINGBO',tabname=>'TO_BUSS_WNMS_BSCPM_H_TEST',partname=>'PART201301',granularity=>'PARTITION',estimate_percent=> dbms_stats.auto_sample_size,force=>true,degree=>8);

PL/SQL procedure successfully completed

查看表的相关信息:

复制代码
SELECT TABLE_OWNER,
       TABLE_NAME,
       PARTITION_NAME,
       NUM_ROWS,
       BLOCKS,
       AVG_ROW_LEN,
       LAST_ANALYZED
  FROM DBA_TAB_PARTITIONS
 WHERE TABLE_NAME = 'TO_BUSS_WNMS_BSCPMHR_S_TEST'
   AND PARTITION_NAME IN ('PART201212', 'PART201301')
复制代码

clip_image001

 

复制代码
SELECT TABLE_OWNER,
       TABLE_NAME,
       PARTITION_NAME,
       NUM_ROWS,
       BLOCKS,
       AVG_ROW_LEN,
       LAST_ANALYZED
  FROM DBA_TAB_PARTITIONS
 WHERE TABLE_NAME = 'TO_BUSS_WNMS_BSCPM_H_TEST'
   AND PARTITION_NAME IN ('PART201212', 'PART201301')
复制代码

clip_image002

对比上面统计信息,即可发现:

复制代码
SQL> SELECT 43926464/721062 FROM DUAL;
 
    43926464/721062
    ---------------
    60.919122072720

SQL> SELECT 26921828/443940 FROM DUAL;
 
    26921828/443940
    ---------------
    60.642942740009
复制代码

可见数据量翻了60~61倍,数据所占存储空间增长了7倍,也就说数据冗余大量增加。可见在存储方面,横表要比竖表有优势。如果系统大量使用竖表,存储浪费就比较严重了。

复制代码
SQL> SELECT (102240-12753)/12753 FROM DUAL;
 
(102240-12753)/12753
--------------------
    7.01693719124912

SQL> SELECT (63303 -7622)/7622 FROM DUAL;
 
(63303-7622)/7622
-----------------
 7.30530044607715
复制代码

查询脚本对比

从下面的执行计划,以及实际执行结果可以看出,横表比竖表的性能要优很多。我测试了好几次。都是如此,而且脚本越复杂,执行效率差异越大。

SQL> SET AUTOTRACE TRACEONLY

SQL> SET AUTOTRACE TRACEONLY
SQL> SELECT A.COLLECT_DT AS COLLECT_DT,
       A.DATE_CD AS DATE_CD,
       A.HR_CD AS HR_CD,
       A.CITY_ID AS CITY_ID,
       A.SYSTEM_ID AS BSC_ID,
       B.MSC_ID AS MSC_ID,
       MAX(DECODE(A.KPI_CODE, 'HO_REQ_CNT', KPI_VALUE, 0)) AS HO_REQ,
       MAX(DECODE(A.KPI_CODE, 'HO_SUCC_CNT', KPI_VALUE, 0)) AS S_HO
  FROM dwkonglingbo.TO_BUSS_WNMS_BSCPM_H_TEST A, REF.TR_WGG_BSC_INFO B
 WHERE A.SYSTEM_ID = B.BSC_ID
   AND A.COLLECT_DT = 20121218
   AND (KPI_CODE = 'HO_REQ_CNT' OR KPI_CODE = 'HO_SUCC_CNT')
 GROUP BY A.COLLECT_DT,
          A.DATE_CD,
          A.HR_CD,
          A.CITY_ID,
          A.SYSTEM_ID,
          B.MSC_ID;

22728 rows selected.


Execution Plan
----------------------------------------------------------
Plan hash value: 2342193993

---------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                            | Name                         | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     | Pstart| Pstop |
---------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                     |                              | 46458 |  2722K|       |   239   (3)| 00:00:06 |       |       |
|   1 |  HASH GROUP BY                       |                              | 46458 |  2722K|  7328K|   239   (3)| 00:00:06 |       |       |
|*  2 |   HASH JOIN                          |                              | 46458 |  2722K|       |     9  (45)| 00:00:01 |       |       |
|   3 |    TABLE ACCESS FULL                 | TR_WGG_BSC_INFO              |  1963 | 21593 |       |     2   (0)| 00:00:01 |       |       |
|   4 |    TABLE ACCESS BY GLOBAL INDEX ROWID| TO_BUSS_WNMS_BSCPM_H_TEST    | 46458 |  2223K|       |     7  (58)| 00:00:01 |    14 |    14 |
|*  5 |     INDEX RANGE SCAN                 | PK_TO_BUSS_WNMS_BSCPM_H_TEST | 46458 |       |       |     6  (67)| 00:00:01 |       |       |
---------------------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("A"."SYSTEM_ID"="B"."BSC_ID")
   5 - access("A"."COLLECT_DT"=20121218)
       filter("KPI_CODE"='HO_REQ_CNT' OR "KPI_CODE"='HO_SUCC_CNT')


Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
      49816  consistent gets
         89  physical reads
       6408  redo size
    1177246  bytes sent via SQL*Net to client
      17157  bytes received via SQL*Net from client
       1517  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
      22728  rows processed

SQL> SELECT A.COLLECT_DT  AS COLLECT_DT,
  2         A.DATE_CD     AS DATE_CD,
  3         A.HR_CD       AS HR_CD,
  4         A.CITY_ID     AS CITY_ID,
  5         A.SYSTEM_ID   AS BSC_ID,
  6         B.MSC_ID      AS MSC_ID,
  7         A.HO_REQ_CNT  AS HO_REQ,             
  8         A.HO_SUCC_CNT AS S_HO     
  9    FROM dwkonglingbo.TO_BUSS_WNMS_BSCPMHR_S_TEST A, REF.TR_WGG_BSC_INFO B
 10   WHERE A.SYSTEM_ID = B.BSC_ID
 11     AND A.COLLECT_DT = 20121218;

22728 rows selected.


Execution Plan
----------------------------------------------------------
Plan hash value: 2525980157

--------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                           | Name                           | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
--------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |                                | 23260 |  1044K|    31   (4)| 00:00:01 |       |       |
|*  1 |  HASH JOIN                          |                                | 23260 |  1044K|    31   (4)| 00:00:01 |       |       |
|   2 |   TABLE ACCESS FULL                 | TR_WGG_BSC_INFO                |  1963 | 21593 |     2   (0)| 00:00:01 |       |       |
|   3 |   TABLE ACCESS BY GLOBAL INDEX ROWID| TO_BUSS_WNMS_BSCPMHR_S_TEST    | 23260 |   795K|    28   (0)| 00:00:01 |     2 |     2 |
|*  4 |    INDEX RANGE SCAN                 | PK_TO_BUSS_WNMS_BSCPMHR_S_TEST | 23260 |       |     2   (0)| 00:00:01 |       |       |
--------------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - access("A"."SYSTEM_ID"="B"."BSC_ID")
   4 - access("A"."COLLECT_DT"=20121218)


Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
       3449  consistent gets
        434  physical reads
          0  redo size
    1175708  bytes sent via SQL*Net to client
      17157  bytes received via SQL*Net from client
       1517  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
      22728  rows processed

SQL>
$??1

clip_image003

clip_image004

结论总结:(上述测试列子比较少,也没有排除其他因素的影响,但是足以说明实质问题

竖表只适合数据量少,需求变更比较频繁或配置比较灵活的报表,例如概览视图等。不适合数据量大的表。也不适合在数据仓库中大量存在。

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/22887814/viewspace-756122/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/22887814/viewspace-756122/

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值